prr-kit 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +235 -226
- package/package.json +60 -60
- package/src/prr/workflows/2-analyze/describe-pr/templates/pr-description.template.md +51 -51
- package/src/prr/workflows/3-review/architecture-review/workflow.yaml +18 -18
- package/src/prr/workflows/3-review/general-review/workflow.yaml +18 -18
- package/src/prr/workflows/3-review/performance-review/workflow.yaml +18 -18
- package/src/prr/workflows/3-review/security-review/workflow.yaml +19 -19
- package/src/prr/workflows/4-improve/improve-code/workflow.yaml +18 -18
- package/src/prr/workflows/6-report/generate-report/templates/review-report.template.md +78 -78
- package/tools/cli/installers/lib/core/installer.js +162 -162
- package/tools/cli/installers/lib/ide/_config-driven.js +70 -0
- package/tools/cli/installers/lib/ide/codex.js +128 -0
- package/tools/cli/installers/lib/ide/github-copilot.js +262 -0
- package/tools/cli/installers/lib/ide/kilo.js +132 -0
- package/tools/cli/installers/lib/ide/manager.js +37 -1
- package/tools/cli/installers/lib/ide/platform-codes.yaml +104 -4
- package/tools/cli/installers/lib/ide/templates/combined/antigravity-agent.md +15 -0
- package/tools/cli/installers/lib/ide/templates/combined/antigravity-workflow.md +8 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-agent.md +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/gemini-workflow.md +7 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-agent.md +16 -0
- package/tools/cli/installers/lib/ide/templates/combined/kiro-workflow.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-agent.md +15 -0
- package/tools/cli/installers/lib/ide/templates/combined/opencode-workflow.md +6 -0
- package/tools/cli/installers/lib/ide/templates/combined/rovodev-agent.md +10 -0
- package/tools/cli/installers/lib/ide/templates/combined/rovodev-workflow.md +9 -0
- package/tools/cli/installers/lib/ide/templates/combined/trae-agent.md +11 -0
- package/tools/cli/installers/lib/ide/templates/combined/trae-workflow.md +9 -0
- package/tools/cli/prr-cli.js +36 -36
|
@@ -1,162 +1,162 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Installer — main installation orchestrator
|
|
3
|
-
* Main installation orchestrator for PR Review
|
|
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 };
|
|
1
|
+
/**
|
|
2
|
+
* Installer — main installation orchestrator
|
|
3
|
+
* Main installation orchestrator for PR Review Kit
|
|
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 };
|
|
@@ -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 };
|