prr-kit 1.1.0 → 1.1.2
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 -235
- 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/github-copilot.js +262 -262
- package/tools/cli/prr-cli.js +36 -36
|
@@ -1,262 +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
|
|
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 };
|
|
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 };
|
package/tools/cli/prr-cli.js
CHANGED
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* PR Review CLI — Main entry point
|
|
4
|
-
* Usage: node tools/cli/prr-cli.js <command> [options]
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const { program } = require('commander');
|
|
8
|
-
const path = require('node:path');
|
|
9
|
-
const fs = require('node:fs');
|
|
10
|
-
|
|
11
|
-
const packageJson = require('../../package.json');
|
|
12
|
-
|
|
13
|
-
// Dynamically load all command modules from commands/
|
|
14
|
-
const commandsPath = path.join(__dirname, 'commands');
|
|
15
|
-
const commandFiles = fs.readdirSync(commandsPath).filter((f) => f.endsWith('.js'));
|
|
16
|
-
|
|
17
|
-
for (const file of commandFiles) {
|
|
18
|
-
const cmd = require(path.join(commandsPath, file));
|
|
19
|
-
|
|
20
|
-
const command = program
|
|
21
|
-
.command(cmd.command)
|
|
22
|
-
.description(cmd.description);
|
|
23
|
-
|
|
24
|
-
for (const option of cmd.options || []) {
|
|
25
|
-
command.option(...option);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
command.action(cmd.action);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
program
|
|
32
|
-
.name('pr-review')
|
|
33
|
-
.version(packageJson.version)
|
|
34
|
-
.description('PR Review
|
|
35
|
-
|
|
36
|
-
program.parse(process.argv);
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PR Review CLI — Main entry point
|
|
4
|
+
* Usage: node tools/cli/prr-cli.js <command> [options]
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const { program } = require('commander');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const fs = require('node:fs');
|
|
10
|
+
|
|
11
|
+
const packageJson = require('../../package.json');
|
|
12
|
+
|
|
13
|
+
// Dynamically load all command modules from commands/
|
|
14
|
+
const commandsPath = path.join(__dirname, 'commands');
|
|
15
|
+
const commandFiles = fs.readdirSync(commandsPath).filter((f) => f.endsWith('.js'));
|
|
16
|
+
|
|
17
|
+
for (const file of commandFiles) {
|
|
18
|
+
const cmd = require(path.join(commandsPath, file));
|
|
19
|
+
|
|
20
|
+
const command = program
|
|
21
|
+
.command(cmd.command)
|
|
22
|
+
.description(cmd.description);
|
|
23
|
+
|
|
24
|
+
for (const option of cmd.options || []) {
|
|
25
|
+
command.option(...option);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
command.action(cmd.action);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
program
|
|
32
|
+
.name('pr-review')
|
|
33
|
+
.version(packageJson.version)
|
|
34
|
+
.description('PR Review Kit — AI-driven code review agent system');
|
|
35
|
+
|
|
36
|
+
program.parse(process.argv);
|