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.
Files changed (86) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/docs/assets/banner.svg +179 -0
  4. package/package.json +60 -0
  5. package/src/core/agents/prr-master.agent.yaml +80 -0
  6. package/src/core/module.yaml +19 -0
  7. package/src/core/tasks/help.md +37 -0
  8. package/src/core/tasks/workflow.xml +22 -0
  9. package/src/core/workflows/party-mode/steps/step-01-load-reviewers.md +68 -0
  10. package/src/core/workflows/party-mode/steps/step-02-discussion.md +125 -0
  11. package/src/core/workflows/party-mode/workflow.md +35 -0
  12. package/src/prr/agents/architecture-reviewer.agent.yaml +45 -0
  13. package/src/prr/agents/general-reviewer.agent.yaml +48 -0
  14. package/src/prr/agents/performance-reviewer.agent.yaml +45 -0
  15. package/src/prr/agents/security-reviewer.agent.yaml +43 -0
  16. package/src/prr/data/review-types.csv +39 -0
  17. package/src/prr/module.yaml +38 -0
  18. package/src/prr/workflows/0-setup/collect-project-context/steps/step-01-scan-configs.md +106 -0
  19. package/src/prr/workflows/0-setup/collect-project-context/steps/step-02-extract-rules.md +131 -0
  20. package/src/prr/workflows/0-setup/collect-project-context/steps/step-03-ask-context.md +194 -0
  21. package/src/prr/workflows/0-setup/collect-project-context/steps/step-04-save-context.md +161 -0
  22. package/src/prr/workflows/0-setup/collect-project-context/workflow.md +58 -0
  23. package/src/prr/workflows/1-discover/select-pr/steps/step-01-fetch.md +68 -0
  24. package/src/prr/workflows/1-discover/select-pr/steps/step-02-list-branches.md +95 -0
  25. package/src/prr/workflows/1-discover/select-pr/steps/step-03-select.md +127 -0
  26. package/src/prr/workflows/1-discover/select-pr/steps/step-04-load-diff.md +79 -0
  27. package/src/prr/workflows/1-discover/select-pr/steps/step-05-confirm.md +76 -0
  28. package/src/prr/workflows/1-discover/select-pr/workflow.md +36 -0
  29. package/src/prr/workflows/2-analyze/describe-pr/steps/step-01-load-context.md +37 -0
  30. package/src/prr/workflows/2-analyze/describe-pr/steps/step-02-classify.md +50 -0
  31. package/src/prr/workflows/2-analyze/describe-pr/steps/step-03-walkthrough.md +41 -0
  32. package/src/prr/workflows/2-analyze/describe-pr/steps/step-04-output.md +50 -0
  33. package/src/prr/workflows/2-analyze/describe-pr/templates/pr-description.template.md +51 -0
  34. package/src/prr/workflows/2-analyze/describe-pr/workflow.md +28 -0
  35. package/src/prr/workflows/3-review/architecture-review/checklist.md +22 -0
  36. package/src/prr/workflows/3-review/architecture-review/instructions.xml +68 -0
  37. package/src/prr/workflows/3-review/architecture-review/workflow.yaml +18 -0
  38. package/src/prr/workflows/3-review/general-review/checklist.md +23 -0
  39. package/src/prr/workflows/3-review/general-review/instructions.xml +68 -0
  40. package/src/prr/workflows/3-review/general-review/workflow.yaml +18 -0
  41. package/src/prr/workflows/3-review/performance-review/checklist.md +22 -0
  42. package/src/prr/workflows/3-review/performance-review/instructions.xml +68 -0
  43. package/src/prr/workflows/3-review/performance-review/workflow.yaml +18 -0
  44. package/src/prr/workflows/3-review/security-review/checklist.md +25 -0
  45. package/src/prr/workflows/3-review/security-review/data/owasp-checklist.csv +19 -0
  46. package/src/prr/workflows/3-review/security-review/instructions.xml +70 -0
  47. package/src/prr/workflows/3-review/security-review/workflow.yaml +19 -0
  48. package/src/prr/workflows/4-improve/improve-code/checklist.md +18 -0
  49. package/src/prr/workflows/4-improve/improve-code/instructions.xml +59 -0
  50. package/src/prr/workflows/4-improve/improve-code/workflow.yaml +18 -0
  51. package/src/prr/workflows/5-ask/ask-code/steps/step-01-load-context.md +37 -0
  52. package/src/prr/workflows/5-ask/ask-code/steps/step-02-answer.md +36 -0
  53. package/src/prr/workflows/5-ask/ask-code/workflow.md +31 -0
  54. package/src/prr/workflows/6-report/generate-report/steps/step-01-collect.md +42 -0
  55. package/src/prr/workflows/6-report/generate-report/steps/step-02-organize.md +38 -0
  56. package/src/prr/workflows/6-report/generate-report/steps/step-03-write.md +44 -0
  57. package/src/prr/workflows/6-report/generate-report/templates/review-report.template.md +78 -0
  58. package/src/prr/workflows/6-report/generate-report/workflow.md +26 -0
  59. package/src/prr/workflows/6-report/post-comments/steps/step-01-format.md +166 -0
  60. package/src/prr/workflows/6-report/post-comments/steps/step-02-post.md +97 -0
  61. package/src/prr/workflows/6-report/post-comments/workflow.md +45 -0
  62. package/src/prr/workflows/quick/workflow.md +244 -0
  63. package/tools/cli/commands/install.js +66 -0
  64. package/tools/cli/commands/status.js +36 -0
  65. package/tools/cli/commands/uninstall.js +38 -0
  66. package/tools/cli/installers/lib/core/config-collector.js +47 -0
  67. package/tools/cli/installers/lib/core/detector.js +46 -0
  68. package/tools/cli/installers/lib/core/installer.js +162 -0
  69. package/tools/cli/installers/lib/core/manifest-generator.js +172 -0
  70. package/tools/cli/installers/lib/core/manifest.js +62 -0
  71. package/tools/cli/installers/lib/ide/_base-ide.js +36 -0
  72. package/tools/cli/installers/lib/ide/_config-driven.js +167 -0
  73. package/tools/cli/installers/lib/ide/manager.js +97 -0
  74. package/tools/cli/installers/lib/ide/platform-codes.yaml +76 -0
  75. package/tools/cli/installers/lib/ide/shared/path-utils.js +11 -0
  76. package/tools/cli/installers/lib/ide/templates/combined/default-agent.md +16 -0
  77. package/tools/cli/installers/lib/ide/templates/combined/default-workflow.md +7 -0
  78. package/tools/cli/installers/lib/ide/templates/combined/windsurf-workflow.md +5 -0
  79. package/tools/cli/lib/agent/compiler.js +123 -0
  80. package/tools/cli/lib/agent/template-engine.js +73 -0
  81. package/tools/cli/lib/cli-utils.js +32 -0
  82. package/tools/cli/lib/prompts.js +15 -0
  83. package/tools/cli/lib/ui.js +132 -0
  84. package/tools/cli/lib/xml-utils.js +24 -0
  85. package/tools/cli/prr-cli.js +36 -0
  86. package/tools/prr-npx-wrapper.js +6 -0
@@ -0,0 +1,244 @@
1
+ ---
2
+ name: quick
3
+ description: "Full PR review pipeline in one command: select → describe → review → report"
4
+ main_config: '{project-root}/_prr/prr/config.yaml'
5
+ ---
6
+
7
+ # Quick Review — Full Pipeline
8
+
9
+ **Goal:** Run the complete PR review pipeline end-to-end with minimal interruptions.
10
+ Only pause for user input when selecting the branch. Everything else runs automatically.
11
+
12
+ ## INITIALIZATION
13
+
14
+ Load config from {main_config}: `user_name`, `communication_language`, `target_repo`, `review_output`, `project_context`.
15
+
16
+ Set `date` = today's date (YYYY-MM-DD).
17
+
18
+ If `project_context` file exists at `{review_output}/project-context.yaml`, load it silently.
19
+ If it does not exist, notify: "⚠️ No project context found — run [CP] Collect Project Context first for better reviews. Continuing without it."
20
+
21
+ ---
22
+
23
+ ## PHASE 1 — SELECT PR
24
+ *Execute steps 1a–1b automatically. Pause only at 1c.*
25
+
26
+ ### 1a. Fetch latest
27
+ ```bash
28
+ git -C {target_repo} fetch origin --prune
29
+ ```
30
+ Show: `✓ Fetched latest from remote`
31
+
32
+ ### 1b. List open PRs (primary) + recent branches (secondary)
33
+
34
+ **Primary — GitHub PRs** (if `{github_repo}` is configured):
35
+ ```bash
36
+ gh pr list --repo {github_repo} --state open \
37
+ --json number,title,headRefName,baseRefName,author,createdAt,isDraft --limit 20
38
+ ```
39
+ Display as a table: `#N | title | head → base | author | age`
40
+
41
+ **Secondary — recent branches** (always):
42
+ ```bash
43
+ git -C {target_repo} branch -r --sort=-committerdate \
44
+ --format="%(refname:short) | %(objectname:short) | %(committerdate:relative) | %(contents:subject)" \
45
+ | head -15
46
+ ```
47
+
48
+ ### 1c. Select PR ← **ONLY USER INPUT IN THIS WORKFLOW**
49
+
50
+ **If `{github_repo}` is configured** — ask:
51
+ > Select a PR to review:
52
+ > Enter PR number (e.g. `44`) or branch name (e.g. `feature/my-feature`):
53
+
54
+ Wait for response.
55
+
56
+ **If PR number entered:**
57
+ ```bash
58
+ gh pr view {pr_number} --repo {github_repo} \
59
+ --json number,title,headRefName,baseRefName,author,headRefOid
60
+ ```
61
+ Set `target_branch` = `headRefName`, `base_branch` = `baseRefName` ← **exact from GitHub, not assumed**.
62
+ Set `pr_head_sha` = `headRefOid`.
63
+
64
+ **If branch name entered:**
65
+ Check if a PR exists for it via `gh pr list --head {branch}`.
66
+ If yes: use PR's `baseRefName`. If no: detect `origin/main` or `origin/master`.
67
+
68
+ ---
69
+
70
+ **If `{github_repo}` is NOT configured** — ask two separate questions:
71
+
72
+ First, display EXACTLY:
73
+ ```
74
+ 🎯 Head branch (the branch to review)?
75
+
76
+ You can:
77
+ • Enter a number from the list (e.g., 1)
78
+ • Type the branch name directly (e.g., feature/my-feature)
79
+ ```
80
+ Wait for response. Set `target_branch` = input.
81
+
82
+ Then display EXACTLY:
83
+ ```
84
+ 🎯 Base branch (what to diff against)?
85
+
86
+ You can:
87
+ • Press Enter to use the default [main]
88
+ • Type the branch name directly (e.g., develop)
89
+ ```
90
+ Wait for response. If empty → detect `origin/main` or `origin/master`.
91
+ Set `base_branch` = input or detected default.
92
+ Set `diff_range` = `{base_branch}...{target_branch}`.
93
+
94
+ ### 1d. Load diff
95
+ ```bash
96
+ # If PR number available (preferred):
97
+ gh pr diff {pr_number} --repo {github_repo}
98
+
99
+ # Otherwise:
100
+ git -C {target_repo} diff {base_branch}...{target_branch} --stat
101
+ git -C {target_repo} diff {base_branch}...{target_branch}
102
+ ```
103
+ Store diff in memory. Count files changed, lines added/removed.
104
+
105
+ ### 1e. Save PR context
106
+ Write `{review_output}/current-pr-context.yaml`:
107
+ ```yaml
108
+ target_branch: "{target_branch}"
109
+ base_branch: "{base_branch}"
110
+ date: "{date}"
111
+ ```
112
+
113
+ Show summary:
114
+ ```
115
+ ✓ PR selected: {target_branch}
116
+ Files changed: X | +Y / -Z lines
117
+ ```
118
+
119
+ ---
120
+
121
+ ## PHASE 2 — DESCRIBE PR
122
+ *Execute automatically, no user input.*
123
+
124
+ ### 2a. Classify PR type
125
+ Analyze the diff and classify as one of: `bugfix` | `feature` | `refactor` | `docs` | `test` | `chore` | `hotfix`
126
+
127
+ ### 2b. Generate walkthrough
128
+ For each changed file, write a 1-2 sentence summary of what changed and why.
129
+ Group by: new files | modified files | deleted files | renamed files.
130
+
131
+ ### 2c. Output description
132
+ Print to screen:
133
+ ```
134
+ ## PR Description
135
+
136
+ **Type:** {pr_type}
137
+ **Branch:** {target_branch}
138
+ **Summary:** {2-3 sentence overall summary}
139
+
140
+ ### Files Changed
141
+ {walkthrough table}
142
+ ```
143
+
144
+ ---
145
+
146
+ ## PHASE 3 — REVIEW
147
+ *Execute all review types automatically, one by one.*
148
+
149
+ For each review, read the corresponding instructions file and apply it to `{pr_diff}`.
150
+
151
+ ### 3a. General Review
152
+ Load and follow: `{project-root}/_prr/prr/workflows/3-review/general-review/instructions.xml`
153
+
154
+ Collect findings as `{general_findings}`.
155
+ Print section header: `## 👁️ General Review`
156
+
157
+ ### 3b. Security Review
158
+ Load and follow: `{project-root}/_prr/prr/workflows/3-review/security-review/instructions.xml`
159
+
160
+ Collect findings as `{security_findings}`.
161
+ Print section header: `## 🔒 Security Review`
162
+
163
+ ### 3c. Performance Review
164
+ Load and follow: `{project-root}/_prr/prr/workflows/3-review/performance-review/instructions.xml`
165
+
166
+ Collect findings as `{performance_findings}`.
167
+ Print section header: `## ⚡ Performance Review`
168
+
169
+ ### 3d. Architecture Review
170
+ Load and follow: `{project-root}/_prr/prr/workflows/3-review/architecture-review/instructions.xml`
171
+
172
+ Collect findings as `{architecture_findings}`.
173
+ Print section header: `## 🏗️ Architecture Review`
174
+
175
+ ---
176
+
177
+ ## PHASE 4 — GENERATE REPORT
178
+ *Execute automatically.*
179
+
180
+ Compile all findings from phases 3a–3d.
181
+
182
+ Sort by severity: 🔴 Blockers first → 🟡 Warnings → 🟢 Suggestions → 📌 Questions.
183
+
184
+ Count totals:
185
+ - `{blocker_count}` = number of 🔴 findings
186
+ - `{warning_count}` = number of 🟡 findings
187
+ - `{suggestion_count}` = number of 🟢 findings
188
+
189
+ Generate report filename: `review-{target_branch_slug}-{date}.md`
190
+ where `{target_branch_slug}` = branch name with `/` replaced by `-`.
191
+
192
+ Write report to: `{review_output}/review-{target_branch_slug}-{date}.md`
193
+
194
+ Report format:
195
+ ```markdown
196
+ # PR Review: {target_branch}
197
+ **Date:** {date} | **Reviewer:** AI Review Framework
198
+ **Type:** {pr_type} | **Files:** X | **Lines:** +Y/-Z
199
+
200
+ ## Executive Summary
201
+ {2-3 sentence overall quality assessment}
202
+
203
+ **Totals:** 🔴 {blocker_count} blockers | 🟡 {warning_count} warnings | 🟢 {suggestion_count} suggestions
204
+
205
+ ## Blockers 🔴
206
+ {all blocker findings}
207
+
208
+ ## Warnings 🟡
209
+ {all warning findings}
210
+
211
+ ## Suggestions 🟢
212
+ {all suggestion findings}
213
+
214
+ ## Questions 📌
215
+ {all questions}
216
+
217
+ ## Files Reviewed
218
+ {file list}
219
+ ```
220
+
221
+ ---
222
+
223
+ ## PHASE 5 — DONE
224
+
225
+ Print completion summary:
226
+ ```
227
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
228
+ ✅ Quick Review Complete
229
+
230
+ Branch: {target_branch}
231
+ Report: {review_output}/review-{target_branch_slug}-{date}.md
232
+
233
+ 🔴 Blockers: {blocker_count}
234
+ 🟡 Warnings: {warning_count}
235
+ 🟢 Suggestions: {suggestion_count}
236
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
237
+ ```
238
+
239
+ Then ask:
240
+ > Post these findings as inline comments to GitHub? (requires `gh` CLI and `github_repo` configured)
241
+ > Type **PC** to post, or **Enter** to finish.
242
+
243
+ If user types `PC`, load and follow: `{project-root}/_prr/prr/workflows/6-report/post-comments/workflow.md`
244
+ Otherwise, end session.
@@ -0,0 +1,66 @@
1
+ const { Installer } = require('../installers/lib/core/installer');
2
+ const { UI } = require('../lib/ui');
3
+ const prompts = require('../lib/prompts');
4
+
5
+ const installer = new Installer();
6
+ const ui = new UI();
7
+
8
+ module.exports = {
9
+ command: 'install',
10
+ description: 'Install PR Review agents and workflows into your project',
11
+ options: [
12
+ ['-d, --debug', 'Enable debug output for manifest generation'],
13
+ ['--directory <path>', 'Installation directory (default: current directory)'],
14
+ ['--modules <modules>', 'Comma-separated list of module IDs to install (e.g., "prr")'],
15
+ ['--tools <tools>', 'Comma-separated list of IDE IDs to configure (e.g., "claude-code,cursor"). Use "none" to skip.'],
16
+ ['--action <type>', 'Action type: install, update, or quick-update'],
17
+ ['--user-name <name>', 'Name for agents to use'],
18
+ ['--communication-language <lang>', 'Language for agent communication (default: English)'],
19
+ ['--output-folder <path>', 'Output folder relative to project root (default: _prr-output)'],
20
+ ['--target-repo <path>', 'Path to git repository to review (default: .)'],
21
+ ['--github-repo <owner/repo>', 'GitHub repository for posting comments (optional)'],
22
+ ['-y, --yes', 'Accept all defaults and skip prompts'],
23
+ ],
24
+ action: async (options) => {
25
+ try {
26
+ if (options.debug) {
27
+ process.env.PRR_DEBUG_MANIFEST = 'true';
28
+ await prompts.log.info('Debug mode enabled');
29
+ }
30
+
31
+ const config = await ui.promptInstall(options);
32
+
33
+ if (config.actionType === 'cancel') {
34
+ await prompts.log.warn('Installation cancelled.');
35
+ process.exit(0);
36
+ }
37
+
38
+ if (config.actionType === 'quick-update') {
39
+ const result = await installer.quickUpdate(config);
40
+ await prompts.log.success('Quick update complete!');
41
+ await prompts.log.info(`Updated ${result.moduleCount} modules (${result.modules.join(', ')})`);
42
+ process.exit(0);
43
+ }
44
+
45
+ const result = await installer.install(config);
46
+
47
+ if (result && result.cancelled) {
48
+ process.exit(0);
49
+ }
50
+
51
+ if (result && result.success) {
52
+ process.exit(0);
53
+ }
54
+ } catch (error) {
55
+ if (error.fullMessage) {
56
+ await prompts.log.error(error.fullMessage);
57
+ } else {
58
+ await prompts.log.error(`Installation failed: ${error.message}`);
59
+ }
60
+ if (error.stack && process.env.PRR_DEBUG_MANIFEST) {
61
+ await prompts.log.message(error.stack);
62
+ }
63
+ process.exit(1);
64
+ }
65
+ },
66
+ };
@@ -0,0 +1,36 @@
1
+ const { Installer } = require('../installers/lib/core/installer');
2
+ const prompts = require('../lib/prompts');
3
+
4
+ const installer = new Installer();
5
+
6
+ module.exports = {
7
+ command: 'status',
8
+ description: 'Show current PR Review installation status',
9
+ options: [
10
+ ['--directory <path>', 'Project directory (default: current directory)'],
11
+ ],
12
+ action: async (options) => {
13
+ try {
14
+ const projectDir = options.directory
15
+ ? require('node:path').resolve(options.directory)
16
+ : process.cwd();
17
+
18
+ const status = await installer.status(projectDir);
19
+
20
+ if (!status.installed) {
21
+ await prompts.log.warn('PR Review is not installed in this directory.');
22
+ await prompts.log.info('Run `npx pr-review install` to get started.');
23
+ process.exit(0);
24
+ }
25
+
26
+ await prompts.log.success(`PR Review v${status.version} installed`);
27
+ await prompts.log.info(`Modules: ${status.modules.join(', ')}`);
28
+ await prompts.log.info(`IDEs configured: ${status.ides.join(', ') || 'none'}`);
29
+ await prompts.log.info(`Install date: ${status.installDate}`);
30
+ process.exit(0);
31
+ } catch (error) {
32
+ await prompts.log.error(`Status check failed: ${error.message}`);
33
+ process.exit(1);
34
+ }
35
+ },
36
+ };
@@ -0,0 +1,38 @@
1
+ const { Installer } = require('../installers/lib/core/installer');
2
+ const prompts = require('../lib/prompts');
3
+
4
+ const installer = new Installer();
5
+
6
+ module.exports = {
7
+ command: 'uninstall',
8
+ description: 'Remove PR Review installation from a project',
9
+ options: [
10
+ ['--directory <path>', 'Project directory (default: current directory)'],
11
+ ['-y, --yes', 'Skip confirmation prompt'],
12
+ ],
13
+ action: async (options) => {
14
+ try {
15
+ const projectDir = options.directory
16
+ ? require('node:path').resolve(options.directory)
17
+ : process.cwd();
18
+
19
+ if (!options.yes) {
20
+ const { confirm } = require('@clack/prompts');
21
+ const ok = await confirm({
22
+ message: `Remove PR Review from ${projectDir}?`,
23
+ });
24
+ if (!ok) {
25
+ await prompts.log.warn('Uninstall cancelled.');
26
+ process.exit(0);
27
+ }
28
+ }
29
+
30
+ await installer.uninstall(projectDir);
31
+ await prompts.log.success('PR Review uninstalled successfully.');
32
+ process.exit(0);
33
+ } catch (error) {
34
+ await prompts.log.error(`Uninstall failed: ${error.message}`);
35
+ process.exit(1);
36
+ }
37
+ },
38
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Config Collector — writes config.yaml files for each module
3
+ */
4
+ const path = require('node:path');
5
+ const fs = require('fs-extra');
6
+ const yaml = require('yaml');
7
+
8
+ class ConfigCollector {
9
+ /**
10
+ * Write module config files based on user answers
11
+ * @param {string} prrDir - _prr directory path
12
+ * @param {Object} config - User-provided config values
13
+ */
14
+ async writeConfigs(prrDir, config) {
15
+ // Core config
16
+ const coreConfigPath = path.join(prrDir, 'core', 'config.yaml');
17
+ await fs.ensureDir(path.dirname(coreConfigPath));
18
+
19
+ const coreConfig = {
20
+ user_name: config.userName || 'Reviewer',
21
+ communication_language: config.communicationLanguage || 'English',
22
+ output_folder: config.outputFolder || '_prr-output',
23
+ };
24
+ await fs.writeFile(coreConfigPath, yaml.stringify(coreConfig, { indent: 2 }), 'utf8');
25
+
26
+ // PRR module config
27
+ if (config.selectedModules && config.selectedModules.includes('prr')) {
28
+ const prrConfigPath = path.join(prrDir, 'prr', 'config.yaml');
29
+ await fs.ensureDir(path.dirname(prrConfigPath));
30
+
31
+ const outputFolderAbs = path.join(config.projectDir, config.outputFolder || '_prr-output');
32
+ const reviewOutput = path.join(outputFolderAbs, 'reviews').replaceAll('\\', '/');
33
+
34
+ const prrConfig = {
35
+ ...coreConfig,
36
+ project_name: config.projectName || path.basename(config.projectDir),
37
+ target_repo: config.targetRepo || '.',
38
+ platform: config.platform || 'auto',
39
+ platform_repo: config.platformRepo || config.githubRepo || '',
40
+ review_output: reviewOutput,
41
+ };
42
+ await fs.writeFile(prrConfigPath, yaml.stringify(prrConfig, { indent: 2 }), 'utf8');
43
+ }
44
+ }
45
+ }
46
+
47
+ module.exports = { ConfigCollector };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Detector — detects existing PR Review installations
3
+ */
4
+ const path = require('node:path');
5
+ const fs = require('fs-extra');
6
+ const yaml = require('yaml');
7
+
8
+ const PRR_FOLDER_NAME = '_prr';
9
+
10
+ class Detector {
11
+ /**
12
+ * Check if PR Review is installed in the given directory
13
+ * @param {string} projectDir
14
+ * @returns {Object} { installed, version, modules, prrDir }
15
+ */
16
+ async detect(projectDir) {
17
+ const prrDir = path.join(projectDir, PRR_FOLDER_NAME);
18
+
19
+ if (!(await fs.pathExists(prrDir))) {
20
+ return { installed: false, prrDir };
21
+ }
22
+
23
+ const manifestPath = path.join(prrDir, '_config', 'manifest.yaml');
24
+ if (!(await fs.pathExists(manifestPath))) {
25
+ return { installed: false, prrDir };
26
+ }
27
+
28
+ try {
29
+ const content = await fs.readFile(manifestPath, 'utf8');
30
+ const data = yaml.parse(content);
31
+ const modules = (data.modules || []).map((m) => (typeof m === 'string' ? m : m.name));
32
+ return {
33
+ installed: true,
34
+ version: data.installation?.version || 'unknown',
35
+ installDate: data.installation?.installDate,
36
+ modules,
37
+ ides: data.ides || [],
38
+ prrDir,
39
+ };
40
+ } catch {
41
+ return { installed: false, prrDir };
42
+ }
43
+ }
44
+ }
45
+
46
+ module.exports = { Detector, PRR_FOLDER_NAME };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Installer — main installation orchestrator
3
+ * Main installation orchestrator for PR Review Framework
4
+ */
5
+ const path = require('node:path');
6
+ const fs = require('fs-extra');
7
+ const { Manifest } = require('./manifest');
8
+ const { ManifestGenerator } = require('./manifest-generator');
9
+ const { ConfigCollector } = require('./config-collector');
10
+ const { Detector, PRR_FOLDER_NAME } = require('./detector');
11
+ const { IdeManager } = require('../ide/manager');
12
+ const { compileAgent } = require('../../../lib/agent/compiler');
13
+ const prompts = require('../../../lib/prompts');
14
+ const packageJson = require('../../../../../package.json');
15
+
16
+ const SRC_DIR = path.join(__dirname, '..', '..', '..', '..', '..', 'src');
17
+ const TEXT_EXTENSIONS = new Set(['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.csv', '.xml', '.toml']);
18
+
19
+ class Installer {
20
+ constructor() {
21
+ this.manifest = new Manifest();
22
+ this.manifestGenerator = new ManifestGenerator();
23
+ this.configCollector = new ConfigCollector();
24
+ this.detector = new Detector();
25
+ this.ideManager = new IdeManager();
26
+ }
27
+
28
+ async install(config) {
29
+ const { projectDir, selectedModules = ['prr'], selectedIdes = [], actionType = 'install' } = config;
30
+
31
+ await this.ideManager.ensureInitialized();
32
+
33
+ const prrDir = path.join(projectDir, PRR_FOLDER_NAME);
34
+ await fs.ensureDir(prrDir);
35
+
36
+ await prompts.log.step(`Installing PR Review to ${projectDir}...`);
37
+
38
+ // Copy module files from src/
39
+ const modulesToInstall = ['core', ...selectedModules.filter((m) => m !== 'core')];
40
+ for (const mod of modulesToInstall) {
41
+ const srcModDir = path.join(SRC_DIR, mod === 'core' ? 'core' : mod);
42
+ const dstModDir = path.join(prrDir, mod);
43
+
44
+ if (!(await fs.pathExists(srcModDir))) {
45
+ await prompts.log.warn(`Module '${mod}' not found in src/ — skipping`);
46
+ continue;
47
+ }
48
+
49
+ await fs.remove(dstModDir);
50
+ await this.copyModuleFiles(srcModDir, dstModDir);
51
+ await prompts.log.info(` ✓ Module '${mod}' installed`);
52
+ }
53
+
54
+ // Compile agents (YAML → XML/MD)
55
+ await this.compileModuleAgents(prrDir, modulesToInstall);
56
+
57
+ // Write config files
58
+ await this.configCollector.writeConfigs(prrDir, config);
59
+ await prompts.log.info(' ✓ Configuration written');
60
+
61
+ // Create output directories
62
+ const outputFolderAbs = path.join(projectDir, config.outputFolder || '_prr-output');
63
+ await fs.ensureDir(path.join(outputFolderAbs, 'reviews'));
64
+ await prompts.log.info(' ✓ Output directories created');
65
+
66
+ // Generate manifests
67
+ const cfgDir = path.join(prrDir, '_config');
68
+ await fs.ensureDir(cfgDir);
69
+ await this.manifestGenerator.generateManifests(prrDir, selectedModules);
70
+ await this.manifest.create(prrDir, {
71
+ version: packageJson.version,
72
+ modules: modulesToInstall,
73
+ ides: selectedIdes,
74
+ });
75
+ await prompts.log.info(' ✓ Manifests generated');
76
+
77
+ // Configure IDEs
78
+ for (const ide of selectedIdes) {
79
+ const result = await this.ideManager.setup(ide, projectDir, prrDir, { selectedModules: modulesToInstall });
80
+ if (result.success) {
81
+ await prompts.log.info(` ✓ ${ide} configured (${result.detail || 'done'})`);
82
+ }
83
+ }
84
+
85
+ await prompts.log.success('PR Review installed successfully!');
86
+ await prompts.log.info('Open your AI IDE and use the reviewer agents to start reviewing PRs.');
87
+
88
+ return { success: true };
89
+ }
90
+
91
+ async copyModuleFiles(srcDir, dstDir) {
92
+ await fs.ensureDir(dstDir);
93
+ const entries = await fs.readdir(srcDir, { withFileTypes: true });
94
+
95
+ for (const entry of entries) {
96
+ const srcPath = path.join(srcDir, entry.name);
97
+ const dstPath = path.join(dstDir, entry.name);
98
+
99
+ if (entry.isDirectory()) {
100
+ await this.copyModuleFiles(srcPath, dstPath);
101
+ } else {
102
+ const ext = path.extname(entry.name).toLowerCase();
103
+ if (TEXT_EXTENSIONS.has(ext)) {
104
+ const content = await fs.readFile(srcPath, 'utf8');
105
+ await fs.ensureDir(path.dirname(dstPath));
106
+ await fs.writeFile(dstPath, content, 'utf8');
107
+ } else {
108
+ await fs.copy(srcPath, dstPath, { overwrite: true });
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ async compileModuleAgents(prrDir, modules) {
115
+ for (const mod of modules) {
116
+ const agentsYamlDir = path.join(prrDir, mod, 'agents');
117
+ if (!(await fs.pathExists(agentsYamlDir))) continue;
118
+
119
+ const files = await fs.readdir(agentsYamlDir);
120
+ for (const file of files) {
121
+ if (!file.endsWith('.agent.yaml')) continue;
122
+
123
+ const yamlPath = path.join(agentsYamlDir, file);
124
+ const baseName = file.replace('.agent.yaml', '');
125
+ const outputPath = path.join(agentsYamlDir, `${baseName}.md`);
126
+
127
+ try {
128
+ const yamlContent = await fs.readFile(yamlPath, 'utf8');
129
+ const { xml } = await compileAgent(yamlContent, {}, baseName, `_prr/${mod}/agents/${baseName}.md`);
130
+ await fs.writeFile(outputPath, xml, 'utf8');
131
+ } catch (err) {
132
+ await prompts.log.warn(` Warning: Could not compile agent ${file}: ${err.message}`);
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ async uninstall(projectDir) {
139
+ const prrDir = path.join(projectDir, PRR_FOLDER_NAME);
140
+ if (await fs.pathExists(prrDir)) {
141
+ await fs.remove(prrDir);
142
+ }
143
+ // Also clean IDE configurations
144
+ await this.ideManager.ensureInitialized();
145
+ await this.ideManager.cleanup(projectDir);
146
+ }
147
+
148
+ async quickUpdate(config) {
149
+ const { projectDir } = config;
150
+ const existing = await this.detector.detect(projectDir);
151
+ if (!existing.installed) {
152
+ throw new Error('PR Review is not installed in this directory.');
153
+ }
154
+ return this.install({ ...config, selectedModules: existing.modules, selectedIdes: existing.ides });
155
+ }
156
+
157
+ async status(projectDir) {
158
+ return this.detector.detect(projectDir);
159
+ }
160
+ }
161
+
162
+ module.exports = { Installer };