prr-kit 1.0.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/LICENSE +21 -0
- package/README.md +226 -0
- package/docs/assets/banner.svg +179 -0
- package/package.json +60 -0
- package/src/core/agents/prr-master.agent.yaml +80 -0
- package/src/core/module.yaml +19 -0
- package/src/core/tasks/help.md +37 -0
- package/src/core/tasks/workflow.xml +22 -0
- package/src/core/workflows/party-mode/steps/step-01-load-reviewers.md +68 -0
- package/src/core/workflows/party-mode/steps/step-02-discussion.md +125 -0
- package/src/core/workflows/party-mode/workflow.md +35 -0
- package/src/prr/agents/architecture-reviewer.agent.yaml +45 -0
- package/src/prr/agents/general-reviewer.agent.yaml +48 -0
- package/src/prr/agents/performance-reviewer.agent.yaml +45 -0
- package/src/prr/agents/security-reviewer.agent.yaml +43 -0
- package/src/prr/data/review-types.csv +39 -0
- package/src/prr/module.yaml +38 -0
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-01-scan-configs.md +106 -0
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-02-extract-rules.md +131 -0
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-03-ask-context.md +194 -0
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-04-save-context.md +161 -0
- package/src/prr/workflows/0-setup/collect-project-context/workflow.md +58 -0
- package/src/prr/workflows/1-discover/select-pr/steps/step-01-fetch.md +68 -0
- package/src/prr/workflows/1-discover/select-pr/steps/step-02-list-branches.md +95 -0
- package/src/prr/workflows/1-discover/select-pr/steps/step-03-select.md +127 -0
- package/src/prr/workflows/1-discover/select-pr/steps/step-04-load-diff.md +79 -0
- package/src/prr/workflows/1-discover/select-pr/steps/step-05-confirm.md +76 -0
- package/src/prr/workflows/1-discover/select-pr/workflow.md +36 -0
- package/src/prr/workflows/2-analyze/describe-pr/steps/step-01-load-context.md +37 -0
- package/src/prr/workflows/2-analyze/describe-pr/steps/step-02-classify.md +50 -0
- package/src/prr/workflows/2-analyze/describe-pr/steps/step-03-walkthrough.md +41 -0
- package/src/prr/workflows/2-analyze/describe-pr/steps/step-04-output.md +50 -0
- package/src/prr/workflows/2-analyze/describe-pr/templates/pr-description.template.md +51 -0
- package/src/prr/workflows/2-analyze/describe-pr/workflow.md +28 -0
- package/src/prr/workflows/3-review/architecture-review/checklist.md +22 -0
- package/src/prr/workflows/3-review/architecture-review/instructions.xml +68 -0
- package/src/prr/workflows/3-review/architecture-review/workflow.yaml +18 -0
- package/src/prr/workflows/3-review/general-review/checklist.md +23 -0
- package/src/prr/workflows/3-review/general-review/instructions.xml +68 -0
- package/src/prr/workflows/3-review/general-review/workflow.yaml +18 -0
- package/src/prr/workflows/3-review/performance-review/checklist.md +22 -0
- package/src/prr/workflows/3-review/performance-review/instructions.xml +68 -0
- package/src/prr/workflows/3-review/performance-review/workflow.yaml +18 -0
- package/src/prr/workflows/3-review/security-review/checklist.md +25 -0
- package/src/prr/workflows/3-review/security-review/data/owasp-checklist.csv +19 -0
- package/src/prr/workflows/3-review/security-review/instructions.xml +70 -0
- package/src/prr/workflows/3-review/security-review/workflow.yaml +19 -0
- package/src/prr/workflows/4-improve/improve-code/checklist.md +18 -0
- package/src/prr/workflows/4-improve/improve-code/instructions.xml +59 -0
- package/src/prr/workflows/4-improve/improve-code/workflow.yaml +18 -0
- package/src/prr/workflows/5-ask/ask-code/steps/step-01-load-context.md +37 -0
- package/src/prr/workflows/5-ask/ask-code/steps/step-02-answer.md +36 -0
- package/src/prr/workflows/5-ask/ask-code/workflow.md +31 -0
- package/src/prr/workflows/6-report/generate-report/steps/step-01-collect.md +42 -0
- package/src/prr/workflows/6-report/generate-report/steps/step-02-organize.md +38 -0
- package/src/prr/workflows/6-report/generate-report/steps/step-03-write.md +44 -0
- package/src/prr/workflows/6-report/generate-report/templates/review-report.template.md +78 -0
- package/src/prr/workflows/6-report/generate-report/workflow.md +26 -0
- package/src/prr/workflows/6-report/post-comments/steps/step-01-format.md +166 -0
- package/src/prr/workflows/6-report/post-comments/steps/step-02-post.md +97 -0
- package/src/prr/workflows/6-report/post-comments/workflow.md +45 -0
- package/src/prr/workflows/quick/workflow.md +244 -0
- package/tools/cli/commands/install.js +66 -0
- package/tools/cli/commands/status.js +36 -0
- package/tools/cli/commands/uninstall.js +38 -0
- package/tools/cli/installers/lib/core/config-collector.js +47 -0
- package/tools/cli/installers/lib/core/detector.js +46 -0
- package/tools/cli/installers/lib/core/installer.js +162 -0
- package/tools/cli/installers/lib/core/manifest-generator.js +172 -0
- package/tools/cli/installers/lib/core/manifest.js +62 -0
- package/tools/cli/installers/lib/ide/_base-ide.js +36 -0
- package/tools/cli/installers/lib/ide/_config-driven.js +167 -0
- package/tools/cli/installers/lib/ide/manager.js +97 -0
- package/tools/cli/installers/lib/ide/platform-codes.yaml +76 -0
- package/tools/cli/installers/lib/ide/shared/path-utils.js +11 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-agent.md +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/default-workflow.md +7 -0
- package/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md +5 -0
- package/tools/cli/lib/agent/compiler.js +123 -0
- package/tools/cli/lib/agent/template-engine.js +73 -0
- package/tools/cli/lib/cli-utils.js +32 -0
- package/tools/cli/lib/prompts.js +15 -0
- package/tools/cli/lib/ui.js +132 -0
- package/tools/cli/lib/xml-utils.js +24 -0
- package/tools/cli/prr-cli.js +36 -0
- package/tools/prr-npx-wrapper.js +6 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Compiler — transforms agent YAML to compiled XML/Markdown format
|
|
3
|
+
* Compiles agent YAML files to XML/Markdown launcher format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const yaml = require('yaml');
|
|
7
|
+
const { processAgentYaml, extractInstallConfig, stripInstallConfig, getDefaultValues } = require('./template-engine');
|
|
8
|
+
const { escapeXml } = require('../xml-utils');
|
|
9
|
+
|
|
10
|
+
function buildFrontmatter(metadata, agentName) {
|
|
11
|
+
const name = agentName || metadata.name || 'agent';
|
|
12
|
+
const description = metadata.title || 'PR Review Agent';
|
|
13
|
+
const noLauncher = metadata.no_launcher ? '\nno-launcher: true' : '';
|
|
14
|
+
return `---\nname: "${name.replaceAll('-', ' ')}"\ndescription: "${description}"${noLauncher}\n---\n\nYou must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.\n\n`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function buildPersonaXml(persona) {
|
|
18
|
+
if (!persona) return '';
|
|
19
|
+
let xml = ' <persona>\n';
|
|
20
|
+
if (persona.role) xml += ` <role>${escapeXml(persona.role.trim().replaceAll(/\s+/g, ' '))}</role>\n`;
|
|
21
|
+
if (persona.identity) xml += ` <identity>${escapeXml(persona.identity.trim().replaceAll(/\s+/g, ' '))}</identity>\n`;
|
|
22
|
+
if (persona.communication_style) xml += ` <communication_style>${escapeXml(persona.communication_style.trim().replaceAll(/\s+/g, ' '))}</communication_style>\n`;
|
|
23
|
+
if (persona.principles) {
|
|
24
|
+
const text = Array.isArray(persona.principles)
|
|
25
|
+
? persona.principles.join(' ')
|
|
26
|
+
: persona.principles.trim().replaceAll(/\n+/g, ' ');
|
|
27
|
+
xml += ` <principles>${escapeXml(text)}</principles>\n`;
|
|
28
|
+
}
|
|
29
|
+
xml += ' </persona>\n';
|
|
30
|
+
return xml;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function buildMemoriesXml(memories) {
|
|
34
|
+
if (!memories || memories.length === 0) return '';
|
|
35
|
+
let xml = ' <memories>\n';
|
|
36
|
+
for (const m of memories) xml += ` <memory>${escapeXml(String(m))}</memory>\n`;
|
|
37
|
+
xml += ' </memories>\n';
|
|
38
|
+
return xml;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildMenuXml(menuItems) {
|
|
42
|
+
let xml = ' <menu>\n';
|
|
43
|
+
xml += ` <item cmd="MH or fuzzy match on menu or help">[MH] Redisplay Menu Help</item>\n`;
|
|
44
|
+
xml += ` <item cmd="CH or fuzzy match on chat">[CH] Chat with the Reviewer about anything</item>\n`;
|
|
45
|
+
|
|
46
|
+
if (menuItems && menuItems.length > 0) {
|
|
47
|
+
for (const item of menuItems) {
|
|
48
|
+
if (!item.trigger) continue;
|
|
49
|
+
const attrs = [`cmd="${item.trigger}"`];
|
|
50
|
+
if (item.workflow) attrs.push(`workflow="${item.workflow}"`);
|
|
51
|
+
if (item.exec) attrs.push(`exec="${item.exec}"`);
|
|
52
|
+
if (item.action) attrs.push(`action="${item.action}"`);
|
|
53
|
+
xml += ` <item ${attrs.join(' ')}>${escapeXml(item.description || '')}</item>\n`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
xml += ` <item cmd="PM or fuzzy match on party-mode" exec="{project-root}/_prr/core/workflows/party-mode/workflow.md">[PM] Start Party Mode (multi-reviewer discussion)</item>\n`;
|
|
58
|
+
xml += ` <item cmd="DA or fuzzy match on exit, leave, goodbye or dismiss agent">[DA] Dismiss Reviewer</item>\n`;
|
|
59
|
+
xml += ' </menu>\n';
|
|
60
|
+
return xml;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildActivationXml(meta) {
|
|
64
|
+
return ` <activation>
|
|
65
|
+
<step>Greet the user by name if known, introduce yourself as ${meta.name || 'PR Reviewer'} (${meta.title || 'Code Reviewer'})</step>
|
|
66
|
+
<step>Display your menu using numbered list format</step>
|
|
67
|
+
<step>Wait for user to select an option</step>
|
|
68
|
+
<step>Load and execute the corresponding workflow or task</step>
|
|
69
|
+
</activation>\n`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function compileToXml(agentYaml, agentName = '') {
|
|
73
|
+
const agent = agentYaml.agent;
|
|
74
|
+
const meta = agent.metadata;
|
|
75
|
+
|
|
76
|
+
let xml = buildFrontmatter(meta, agentName || meta.name);
|
|
77
|
+
xml += '```xml\n';
|
|
78
|
+
|
|
79
|
+
const agentAttrs = [
|
|
80
|
+
`id="${meta.id || ''}"`,
|
|
81
|
+
`name="${meta.name || ''}"`,
|
|
82
|
+
`title="${meta.title || ''}"`,
|
|
83
|
+
`icon="${meta.icon || '🔍'}"`,
|
|
84
|
+
];
|
|
85
|
+
if (meta.capabilities) agentAttrs.push(`capabilities="${escapeXml(meta.capabilities)}"`);
|
|
86
|
+
|
|
87
|
+
xml += `<agent ${agentAttrs.join(' ')}>\n`;
|
|
88
|
+
xml += buildActivationXml(meta);
|
|
89
|
+
|
|
90
|
+
if (agent.critical_actions && agent.critical_actions.length > 0) {
|
|
91
|
+
xml += ' <critical_actions>\n';
|
|
92
|
+
for (const action of agent.critical_actions) {
|
|
93
|
+
xml += ` <action>${escapeXml(action)}</action>\n`;
|
|
94
|
+
}
|
|
95
|
+
xml += ' </critical_actions>\n';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
xml += buildPersonaXml(agent.persona);
|
|
99
|
+
if (agent.memories && agent.memories.length > 0) xml += buildMemoriesXml(agent.memories);
|
|
100
|
+
xml += buildMenuXml(agent.menu || []);
|
|
101
|
+
xml += '</agent>\n```\n';
|
|
102
|
+
|
|
103
|
+
return xml;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '') {
|
|
107
|
+
let agentYaml = yaml.parse(yamlContent);
|
|
108
|
+
|
|
109
|
+
const installConfig = extractInstallConfig(agentYaml);
|
|
110
|
+
let finalAnswers = answers;
|
|
111
|
+
if (installConfig) {
|
|
112
|
+
const defaults = getDefaultValues(installConfig);
|
|
113
|
+
finalAnswers = { ...defaults, ...answers };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const processedYaml = processAgentYaml(agentYaml, finalAnswers);
|
|
117
|
+
const cleanYaml = stripInstallConfig(processedYaml);
|
|
118
|
+
const xml = await compileToXml(cleanYaml, agentName, targetPath);
|
|
119
|
+
|
|
120
|
+
return { xml, metadata: cleanYaml.agent.metadata, processedYaml: cleanYaml };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = { compileAgent, compileToXml, buildFrontmatter, buildPersonaXml, buildMenuXml, escapeXml };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template Engine — processes agent YAML with variable substitution
|
|
3
|
+
* Handles variable substitution for agent YAML configuration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Recursively replace {variable} placeholders in strings/objects/arrays
|
|
8
|
+
* @param {any} value - Value to process
|
|
9
|
+
* @param {Object} vars - Variable map
|
|
10
|
+
* @returns {any} Processed value
|
|
11
|
+
*/
|
|
12
|
+
function replacePlaceholders(value, vars) {
|
|
13
|
+
if (typeof value === 'string') {
|
|
14
|
+
return value.replaceAll(/\{(\w+)\}/g, (match, key) => {
|
|
15
|
+
return Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : match;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return value.map((item) => replacePlaceholders(item, vars));
|
|
20
|
+
}
|
|
21
|
+
if (value && typeof value === 'object') {
|
|
22
|
+
const result = {};
|
|
23
|
+
for (const [k, v] of Object.entries(value)) {
|
|
24
|
+
result[k] = replacePlaceholders(v, vars);
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Extract install_config section from agent YAML
|
|
33
|
+
*/
|
|
34
|
+
function extractInstallConfig(agentYaml) {
|
|
35
|
+
return agentYaml.install_config || null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Strip install_config from processed YAML
|
|
40
|
+
*/
|
|
41
|
+
function stripInstallConfig(agentYaml) {
|
|
42
|
+
const { install_config, ...rest } = agentYaml;
|
|
43
|
+
return rest;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get default values from install_config questions
|
|
48
|
+
*/
|
|
49
|
+
function getDefaultValues(installConfig) {
|
|
50
|
+
const defaults = {};
|
|
51
|
+
if (!installConfig || !installConfig.questions) return defaults;
|
|
52
|
+
for (const q of installConfig.questions) {
|
|
53
|
+
if (q.id && q.default !== undefined) {
|
|
54
|
+
defaults[q.id] = q.default;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return defaults;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Process agent YAML with variable substitution
|
|
62
|
+
*/
|
|
63
|
+
function processAgentYaml(agentYaml, answers = {}) {
|
|
64
|
+
return replacePlaceholders(agentYaml, answers);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
processAgentYaml,
|
|
69
|
+
extractInstallConfig,
|
|
70
|
+
stripInstallConfig,
|
|
71
|
+
getDefaultValues,
|
|
72
|
+
replacePlaceholders,
|
|
73
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI utilities — common helper functions
|
|
3
|
+
*/
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Resolve a directory path: use provided path or cwd
|
|
8
|
+
*/
|
|
9
|
+
function resolveDirectory(dir) {
|
|
10
|
+
if (!dir) return process.cwd();
|
|
11
|
+
return path.resolve(dir);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse comma-separated string into array of trimmed non-empty values
|
|
16
|
+
*/
|
|
17
|
+
function parseList(str) {
|
|
18
|
+
if (!str) return [];
|
|
19
|
+
return str
|
|
20
|
+
.split(',')
|
|
21
|
+
.map((s) => s.trim())
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get OS username as default fallback
|
|
27
|
+
*/
|
|
28
|
+
function getSystemUsername() {
|
|
29
|
+
return process.env.USERNAME || process.env.USER || 'Reviewer';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = { resolveDirectory, parseList, getSystemUsername };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompts — wrapper around @clack/prompts for consistent CLI output
|
|
3
|
+
*/
|
|
4
|
+
const clack = require('@clack/prompts');
|
|
5
|
+
|
|
6
|
+
const log = {
|
|
7
|
+
info: async (msg) => clack.log.info(msg),
|
|
8
|
+
success: async (msg) => clack.log.success(msg),
|
|
9
|
+
warn: async (msg) => clack.log.warn(msg),
|
|
10
|
+
error: async (msg) => clack.log.error(msg),
|
|
11
|
+
message: async (msg) => clack.log.message(msg),
|
|
12
|
+
step: async (msg) => clack.log.step(msg),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
module.exports = { ...clack, log };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI — Interactive prompts for installation
|
|
3
|
+
* Interactive CLI UI for PR Review installer
|
|
4
|
+
*/
|
|
5
|
+
const clack = require('@clack/prompts');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { parseList, resolveDirectory, getSystemUsername } = require('./cli-utils');
|
|
8
|
+
const { Detector } = require('../installers/lib/core/detector');
|
|
9
|
+
const { IdeManager } = require('../installers/lib/ide/manager');
|
|
10
|
+
|
|
11
|
+
const detector = new Detector();
|
|
12
|
+
const ideManager = new IdeManager();
|
|
13
|
+
|
|
14
|
+
class UI {
|
|
15
|
+
/**
|
|
16
|
+
* Prompt user through the full install configuration
|
|
17
|
+
* @param {Object} cliOptions - Parsed CLI options
|
|
18
|
+
* @returns {Object} config object for Installer
|
|
19
|
+
*/
|
|
20
|
+
async promptInstall(cliOptions) {
|
|
21
|
+
await ideManager.ensureInitialized();
|
|
22
|
+
|
|
23
|
+
clack.intro('PR Review — AI-driven Code Review Framework');
|
|
24
|
+
|
|
25
|
+
const projectDir = resolveDirectory(cliOptions.directory);
|
|
26
|
+
const dirName = path.basename(projectDir);
|
|
27
|
+
|
|
28
|
+
// Check for existing installation
|
|
29
|
+
const existing = await detector.detect(projectDir);
|
|
30
|
+
let actionType = cliOptions.action || null;
|
|
31
|
+
|
|
32
|
+
if (existing.installed && !actionType) {
|
|
33
|
+
if (cliOptions.yes) {
|
|
34
|
+
actionType = 'update';
|
|
35
|
+
} else {
|
|
36
|
+
actionType = await clack.select({
|
|
37
|
+
message: `PR Review v${existing.version} is already installed. What would you like to do?`,
|
|
38
|
+
options: [
|
|
39
|
+
{ value: 'update', label: 'Full Update — reinstall all modules' },
|
|
40
|
+
{ value: 'quick-update', label: 'Quick Update — refresh files, keep settings' },
|
|
41
|
+
{ value: 'cancel', label: 'Cancel' },
|
|
42
|
+
],
|
|
43
|
+
});
|
|
44
|
+
if (clack.isCancel(actionType)) return { actionType: 'cancel' };
|
|
45
|
+
}
|
|
46
|
+
} else if (!actionType) {
|
|
47
|
+
actionType = 'install';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (actionType === 'cancel') return { actionType: 'cancel' };
|
|
51
|
+
if (actionType === 'quick-update') {
|
|
52
|
+
return { actionType: 'quick-update', projectDir, existing };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Collect core config
|
|
56
|
+
const userName = cliOptions.userName
|
|
57
|
+
|| (!cliOptions.yes && await this._ask('What should reviewers call you?', getSystemUsername()))
|
|
58
|
+
|| getSystemUsername();
|
|
59
|
+
|
|
60
|
+
const communicationLanguage = cliOptions.communicationLanguage
|
|
61
|
+
|| (!cliOptions.yes && await this._ask('Language for reviewer agents?', 'English'))
|
|
62
|
+
|| 'English';
|
|
63
|
+
|
|
64
|
+
const outputFolder = cliOptions.outputFolder
|
|
65
|
+
|| (!cliOptions.yes && await this._ask('Output folder (relative to project root)?', '_prr-output'))
|
|
66
|
+
|| '_prr-output';
|
|
67
|
+
|
|
68
|
+
// PRR module config
|
|
69
|
+
const targetRepo = cliOptions.targetRepo
|
|
70
|
+
|| (!cliOptions.yes && await this._ask('Path to the git repo to review?', '.'))
|
|
71
|
+
|| '.';
|
|
72
|
+
|
|
73
|
+
const githubRepo = cliOptions.githubRepo
|
|
74
|
+
|| (!cliOptions.yes && await this._ask('GitHub repo for posting comments? (owner/repo, blank to skip)', ''))
|
|
75
|
+
|| '';
|
|
76
|
+
|
|
77
|
+
// Modules (default: prr)
|
|
78
|
+
const modulesInput = cliOptions.modules
|
|
79
|
+
? parseList(cliOptions.modules)
|
|
80
|
+
: ['prr'];
|
|
81
|
+
|
|
82
|
+
// IDEs
|
|
83
|
+
let selectedIdes = cliOptions.tools === 'none'
|
|
84
|
+
? []
|
|
85
|
+
: cliOptions.tools
|
|
86
|
+
? parseList(cliOptions.tools)
|
|
87
|
+
: null;
|
|
88
|
+
|
|
89
|
+
if (selectedIdes === null && !cliOptions.yes) {
|
|
90
|
+
const availableIdes = ideManager.getAvailableIdes();
|
|
91
|
+
const choices = await clack.multiselect({
|
|
92
|
+
message: 'Which IDEs should PR Review be configured for?',
|
|
93
|
+
options: availableIdes.map((ide) => ({
|
|
94
|
+
value: ide.value,
|
|
95
|
+
label: ide.name,
|
|
96
|
+
hint: ide.preferred ? 'recommended' : undefined,
|
|
97
|
+
})),
|
|
98
|
+
required: false,
|
|
99
|
+
});
|
|
100
|
+
selectedIdes = clack.isCancel(choices) ? [] : choices;
|
|
101
|
+
} else if (selectedIdes === null) {
|
|
102
|
+
selectedIdes = ideManager.getPreferredIdes().map((ide) => ide.value);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
clack.outro('Configuration complete — starting installation...');
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
actionType,
|
|
109
|
+
projectDir,
|
|
110
|
+
userName,
|
|
111
|
+
communicationLanguage,
|
|
112
|
+
outputFolder,
|
|
113
|
+
targetRepo,
|
|
114
|
+
githubRepo,
|
|
115
|
+
selectedModules: modulesInput,
|
|
116
|
+
selectedIdes,
|
|
117
|
+
existing,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async _ask(message, defaultValue) {
|
|
122
|
+
const result = await clack.text({
|
|
123
|
+
message,
|
|
124
|
+
placeholder: defaultValue,
|
|
125
|
+
defaultValue,
|
|
126
|
+
});
|
|
127
|
+
if (clack.isCancel(result)) return defaultValue;
|
|
128
|
+
return result || defaultValue;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { UI };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML utilities — escape/unescape for agent XML generation
|
|
3
|
+
*/
|
|
4
|
+
function escapeXml(str) {
|
|
5
|
+
if (typeof str !== 'string') return String(str ?? '');
|
|
6
|
+
return str
|
|
7
|
+
.replaceAll('&', '&')
|
|
8
|
+
.replaceAll('<', '<')
|
|
9
|
+
.replaceAll('>', '>')
|
|
10
|
+
.replaceAll('"', '"')
|
|
11
|
+
.replaceAll("'", ''');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function unescapeXml(str) {
|
|
15
|
+
if (typeof str !== 'string') return str;
|
|
16
|
+
return str
|
|
17
|
+
.replaceAll(''', "'")
|
|
18
|
+
.replaceAll('"', '"')
|
|
19
|
+
.replaceAll('>', '>')
|
|
20
|
+
.replaceAll('<', '<')
|
|
21
|
+
.replaceAll('&', '&');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { escapeXml, unescapeXml };
|
|
@@ -0,0 +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 Framework — AI-driven code review agent system');
|
|
35
|
+
|
|
36
|
+
program.parse(process.argv);
|