swift-code-reviewer-skill 1.3.0 → 1.4.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/bin/install.js CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ 'use strict';
2
3
 
3
4
  const fs = require('fs');
4
5
  const path = require('path');
@@ -6,7 +7,6 @@ const os = require('os');
6
7
 
7
8
  const SKILL_NAME = 'swift-code-reviewer-skill';
8
9
 
9
- // Colors for terminal output
10
10
  const colors = {
11
11
  reset: '\x1b[0m',
12
12
  bright: '\x1b[1m',
@@ -14,7 +14,7 @@ const colors = {
14
14
  yellow: '\x1b[33m',
15
15
  blue: '\x1b[34m',
16
16
  red: '\x1b[31m',
17
- cyan: '\x1b[36m'
17
+ cyan: '\x1b[36m',
18
18
  };
19
19
 
20
20
  function log(message, color = colors.reset) {
@@ -23,14 +23,9 @@ function log(message, color = colors.reset) {
23
23
 
24
24
  function copyRecursive(src, dest) {
25
25
  const stats = fs.statSync(src);
26
-
27
26
  if (stats.isDirectory()) {
28
- if (!fs.existsSync(dest)) {
29
- fs.mkdirSync(dest, { recursive: true });
30
- }
31
-
32
- const entries = fs.readdirSync(src);
33
- for (const entry of entries) {
27
+ if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
28
+ for (const entry of fs.readdirSync(src)) {
34
29
  copyRecursive(path.join(src, entry), path.join(dest, entry));
35
30
  }
36
31
  } else {
@@ -39,18 +34,20 @@ function copyRecursive(src, dest) {
39
34
  }
40
35
 
41
36
  function findPackageRoot() {
42
- let packageRoot = __dirname;
43
- while (!fs.existsSync(path.join(packageRoot, 'SKILL.md'))) {
44
- const parent = path.dirname(packageRoot);
45
- if (parent === packageRoot) {
37
+ let dir = __dirname;
38
+ while (!fs.existsSync(path.join(dir, 'SKILL.md'))) {
39
+ const parent = path.dirname(dir);
40
+ if (parent === dir) {
46
41
  log(' Error: Could not find SKILL.md in package', colors.red);
47
42
  process.exit(1);
48
43
  }
49
- packageRoot = parent;
44
+ dir = parent;
50
45
  }
51
- return packageRoot;
46
+ return dir;
52
47
  }
53
48
 
49
+ // ---------- global install (default command) ----------
50
+
54
51
  function install() {
55
52
  log('\n Swift Code Reviewer Skill Installer', colors.cyan + colors.bright);
56
53
  log(' ====================================\n', colors.cyan);
@@ -76,113 +73,70 @@ function install() {
76
73
 
77
74
  fs.mkdirSync(targetDir, { recursive: true });
78
75
 
79
- const filesToCopy = [
80
- 'SKILL.md',
81
- 'README.md',
82
- 'LICENSE',
83
- 'CONTRIBUTING.md',
84
- 'CHANGELOG.md'
85
- ];
86
-
87
- const dirsToCopy = ['references', 'skills', 'templates'];
76
+ const filesToCopy = ['SKILL.md', 'README.md', 'LICENSE', 'CONTRIBUTING.md', 'CHANGELOG.md'];
77
+ const dirsToCopy = ['core', 'references', 'skills', 'templates'];
88
78
 
89
79
  for (const file of filesToCopy) {
90
80
  const src = path.join(packageRoot, file);
91
- const dest = path.join(targetDir, file);
92
-
93
81
  if (fs.existsSync(src)) {
94
- fs.copyFileSync(src, dest);
82
+ fs.copyFileSync(src, path.join(targetDir, file));
95
83
  log(` Copied: ${file}`, colors.green);
96
84
  }
97
85
  }
98
86
 
99
87
  for (const dir of dirsToCopy) {
100
88
  const src = path.join(packageRoot, dir);
101
- const dest = path.join(targetDir, dir);
102
-
103
89
  if (fs.existsSync(src)) {
104
- copyRecursive(src, dest);
90
+ copyRecursive(src, path.join(targetDir, dir));
105
91
  log(` Copied: ${dir}/`, colors.green);
106
92
  }
107
93
  }
108
94
 
109
95
  log('\n Installation complete!', colors.green + colors.bright);
110
96
  log('\n The skill is now available in Claude Code.', colors.reset);
111
- log(' To add the review agent and /review command to a project, run:', colors.reset);
97
+ log(' To scaffold the review agent into a project, run:', colors.reset);
112
98
  log('\n npx swift-code-reviewer-skill init\n', colors.cyan);
113
- log(' Or use it directly by asking Claude to:', colors.reset);
114
- log('\n - "Review this PR"', colors.cyan);
115
- log(' - "Review LoginView.swift"', colors.cyan);
116
- log(' - "Review my uncommitted changes"', colors.cyan);
117
- log(' - "Check if this follows our coding standards"\n', colors.cyan);
118
-
119
99
  log(` Skill location: ${targetDir}`, colors.blue);
120
100
  log(' Documentation: https://github.com/Viniciuscarvalho/swift-code-reviewer-skill\n', colors.blue);
121
101
  }
122
102
 
123
- function init() {
103
+ // ---------- init (project scaffolding) ----------
104
+
105
+ async function init(flags) {
124
106
  log('\n Swift Code Reviewer — Project Setup', colors.cyan + colors.bright);
125
107
  log(' =====================================\n', colors.cyan);
126
108
 
127
109
  const packageRoot = findPackageRoot();
128
110
  const cwd = process.cwd();
129
111
 
130
- // Verify we're in a git repo (likely a real project)
131
112
  if (!fs.existsSync(path.join(cwd, '.git'))) {
132
113
  log(' Warning: Not a git repository. Running anyway.\n', colors.yellow);
133
114
  }
134
115
 
135
- const claudeDir = path.join(cwd, '.claude');
136
- const agentsDir = path.join(claudeDir, 'agents');
137
- const commandsDir = path.join(claudeDir, 'commands');
116
+ const { selectAgents } = require('./lib/prompt');
117
+ const { AGENT_INSTALLERS } = require('./lib/agents');
138
118
 
139
- const templateAgentSrc = path.join(packageRoot, 'templates', 'agents', 'swift-code-reviewer.md');
140
- const templateCommandSrc = path.join(packageRoot, 'templates', 'commands', 'review.md');
119
+ const agents = await selectAgents({
120
+ allFlag: flags.all,
121
+ agentFlag: flags.agent,
122
+ isTTY: Boolean(process.stdout.isTTY),
123
+ });
141
124
 
142
- // Check templates exist
143
- if (!fs.existsSync(templateAgentSrc)) {
144
- log(' Error: Agent template not found. Reinstall the skill with:', colors.red);
145
- log(' npx swift-code-reviewer-skill\n', colors.cyan);
146
- process.exit(1);
125
+ if (agents.length === 0) {
126
+ log(' No agents selected. Nothing to do.\n', colors.yellow);
127
+ return;
147
128
  }
148
129
 
149
- fs.mkdirSync(agentsDir, { recursive: true });
150
- fs.mkdirSync(commandsDir, { recursive: true });
151
-
152
- let created = 0;
153
- let skipped = 0;
154
-
155
- // Copy agent
156
- const agentDest = path.join(agentsDir, 'swift-code-reviewer.md');
157
- if (fs.existsSync(agentDest)) {
158
- log(' Skipped: .claude/agents/swift-code-reviewer.md (already exists)', colors.yellow);
159
- skipped++;
160
- } else {
161
- fs.copyFileSync(templateAgentSrc, agentDest);
162
- log(' Created: .claude/agents/swift-code-reviewer.md', colors.green);
163
- created++;
130
+ const opts = { dryRun: flags.dryRun, force: flags.force };
131
+ for (const agent of agents) {
132
+ AGENT_INSTALLERS[agent](packageRoot, cwd, opts);
164
133
  }
165
134
 
166
- // Copy command
167
- const commandDest = path.join(commandsDir, 'review.md');
168
- if (fs.existsSync(commandDest)) {
169
- log(' Skipped: .claude/commands/review.md (already exists)', colors.yellow);
170
- skipped++;
171
- } else {
172
- fs.copyFileSync(templateCommandSrc, commandDest);
173
- log(' Created: .claude/commands/review.md', colors.green);
174
- created++;
175
- }
176
-
177
- log(`\n Done! ${created} file(s) created, ${skipped} skipped.`, colors.green + colors.bright);
178
-
179
- if (created > 0) {
180
- log('\n Usage:', colors.reset);
181
- log(' /review — run a full code review before pushing', colors.cyan);
182
- log(' @swift-code-reviewer — invoke the agent directly\n', colors.cyan);
183
- }
135
+ log('\n Done!\n', colors.green + colors.bright);
184
136
  }
185
137
 
138
+ // ---------- uninstall ----------
139
+
186
140
  function uninstall() {
187
141
  log('\n Uninstalling Swift Code Reviewer Skill', colors.yellow + colors.bright);
188
142
  log(' ======================================\n', colors.yellow);
@@ -199,29 +153,56 @@ function uninstall() {
199
153
  }
200
154
  }
201
155
 
156
+ // ---------- help ----------
157
+
202
158
  function showHelp() {
203
159
  log('\n Swift Code Reviewer Skill', colors.cyan + colors.bright);
204
160
  log(' =========================\n', colors.cyan);
205
- log(' Usage: npx swift-code-reviewer-skill [command]\n', colors.reset);
161
+ log(' Usage: npx swift-code-reviewer-skill [command] [options]\n', colors.reset);
206
162
  log(' Commands:', colors.bright);
207
163
  log(' (none) Install the skill to ~/.claude/skills/', colors.reset);
208
- log(' init Scaffold agent + /review command into current project', colors.reset);
164
+ log(' init Scaffold review agent into the current project', colors.reset);
209
165
  log(' uninstall Remove the skill from ~/.claude/skills/', colors.reset);
210
166
  log(' help Show this help message\n', colors.reset);
167
+ log(' Options for init:', colors.bright);
168
+ log(' --agent <name[,name]> Target specific agent(s): claude, codex, gemini, kiro', colors.reset);
169
+ log(' --all Install for all supported agents', colors.reset);
170
+ log(' --force Overwrite existing files', colors.reset);
171
+ log(' --dry-run Preview writes without touching the filesystem\n', colors.reset);
211
172
  log(' Examples:', colors.bright);
212
- log(' npx swift-code-reviewer-skill # install skill globally', colors.cyan);
213
- log(' npx swift-code-reviewer-skill init # add agent + command to project', colors.cyan);
214
- log(' npx swift-code-reviewer-skill uninstall # remove skill\n', colors.cyan);
173
+ log(' npx swift-code-reviewer-skill # install skill globally (Claude)', colors.cyan);
174
+ log(' npx swift-code-reviewer-skill init # interactive agent picker', colors.cyan);
175
+ log(' npx swift-code-reviewer-skill init --all # all agents at once', colors.cyan);
176
+ log(' npx swift-code-reviewer-skill init --agent codex,gemini', colors.cyan);
177
+ log(' npx swift-code-reviewer-skill init --dry-run', colors.cyan);
178
+ log(' npx swift-code-reviewer-skill uninstall\n', colors.cyan);
215
179
  }
216
180
 
217
- // Parse command line arguments
181
+ // ---------- arg parsing ----------
182
+
183
+ function parseFlags(argv) {
184
+ const flags = { all: false, agent: null, dryRun: false, force: false };
185
+ for (let i = 0; i < argv.length; i++) {
186
+ const arg = argv[i];
187
+ if (arg === '--all') flags.all = true;
188
+ else if (arg === '--dry-run') flags.dryRun = true;
189
+ else if (arg === '--force') flags.force = true;
190
+ else if (arg === '--agent' && argv[i + 1]) { flags.agent = argv[++i]; }
191
+ else if (arg.startsWith('--agent=')) { flags.agent = arg.slice('--agent='.length); }
192
+ }
193
+ return flags;
194
+ }
195
+
196
+ // ---------- entry point ----------
197
+
218
198
  const args = process.argv.slice(2);
219
- const command = args[0];
199
+ const command = args.find((a) => !a.startsWith('-')) || '';
200
+ const flags = parseFlags(args.filter((a) => a.startsWith('-') || args.indexOf(a) > 0));
220
201
 
221
202
  switch (command) {
222
203
  case 'init':
223
204
  case 'setup':
224
- init();
205
+ init(flags).catch((err) => { console.error(err); process.exit(1); });
225
206
  break;
226
207
  case 'uninstall':
227
208
  case 'remove':
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const colors = {
7
+ reset: '\x1b[0m',
8
+ bright: '\x1b[1m',
9
+ green: '\x1b[32m',
10
+ yellow: '\x1b[33m',
11
+ blue: '\x1b[34m',
12
+ cyan: '\x1b[36m',
13
+ };
14
+
15
+ function log(msg, color = colors.reset) {
16
+ console.log(`${color}${msg}${colors.reset}`);
17
+ }
18
+
19
+ function writeFile(dest, src, label, dryRun) {
20
+ const exists = fs.existsSync(dest);
21
+ if (dryRun) {
22
+ log(` [dry-run] ${exists ? 'Update' : 'Create'}: ${label}`, colors.blue);
23
+ return;
24
+ }
25
+ if (exists) {
26
+ log(` Updated: ${label}`, colors.yellow);
27
+ } else {
28
+ log(` Created: ${label}`, colors.green);
29
+ }
30
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
31
+ fs.copyFileSync(src, dest);
32
+ }
33
+
34
+ function skipFile(label, dryRun) {
35
+ if (dryRun) {
36
+ log(` [dry-run] Skip (exists): ${label}`, colors.yellow);
37
+ } else {
38
+ log(` Skipped: ${label} (already exists)`, colors.yellow);
39
+ }
40
+ }
41
+
42
+ function installClaude(packageRoot, cwd, { dryRun = false, force = false } = {}) {
43
+ log('\n Installing for Claude Code…', colors.cyan);
44
+
45
+ const agentSrc = path.join(packageRoot, 'templates', 'agents', 'claude', 'swift-code-reviewer.md');
46
+ const cmdSrc = path.join(packageRoot, 'templates', 'commands', 'claude', 'review.md');
47
+
48
+ const agentDest = path.join(cwd, '.claude', 'agents', 'swift-code-reviewer.md');
49
+ const cmdDest = path.join(cwd, '.claude', 'commands', 'review.md');
50
+
51
+ if (!force && fs.existsSync(agentDest)) {
52
+ skipFile('.claude/agents/swift-code-reviewer.md', dryRun);
53
+ } else {
54
+ writeFile(agentDest, agentSrc, '.claude/agents/swift-code-reviewer.md', dryRun);
55
+ }
56
+
57
+ if (!force && fs.existsSync(cmdDest)) {
58
+ skipFile('.claude/commands/review.md', dryRun);
59
+ } else {
60
+ writeFile(cmdDest, cmdSrc, '.claude/commands/review.md', dryRun);
61
+ }
62
+
63
+ log('\n Claude Code hints:', colors.reset);
64
+ log(' /review — run a full Swift code review', colors.cyan);
65
+ log(' @swift-code-reviewer — invoke the agent directly\n', colors.cyan);
66
+ }
67
+
68
+ function installCodex(packageRoot, cwd, { dryRun = false, force = false } = {}) {
69
+ log('\n Installing for OpenAI Codex CLI…', colors.cyan);
70
+
71
+ const agentSrc = path.join(packageRoot, 'templates', 'agents', 'codex', 'swift-code-reviewer.md');
72
+ const agentDest = path.join(cwd, 'swift-code-reviewer.md');
73
+
74
+ if (!force && fs.existsSync(agentDest)) {
75
+ skipFile('swift-code-reviewer.md', dryRun);
76
+ } else {
77
+ writeFile(agentDest, agentSrc, 'swift-code-reviewer.md', dryRun);
78
+ }
79
+
80
+ log('\n Codex post-install — add this to your AGENTS.md:\n', colors.reset);
81
+ log(' ## Swift code review', colors.cyan);
82
+ log(' See swift-code-reviewer.md for the full review guide.\n', colors.cyan);
83
+ log(' Note: Codex concatenates AGENTS.md into the system prompt and does not', colors.yellow);
84
+ log(' auto-resolve @-path mentions. Paste the snippet above manually.\n', colors.yellow);
85
+ }
86
+
87
+ function installGemini(packageRoot, cwd, { dryRun = false, force = false } = {}) {
88
+ log('\n Installing for Google Gemini CLI…', colors.cyan);
89
+
90
+ const agentSrc = path.join(packageRoot, 'templates', 'agents', 'gemini', 'swift-code-reviewer.md');
91
+ const cmdSrc = path.join(packageRoot, 'templates', 'commands', 'gemini', 'review.toml');
92
+
93
+ const agentDest = path.join(cwd, 'swift-code-reviewer.md');
94
+ const cmdDest = path.join(cwd, '.gemini', 'commands', 'review.toml');
95
+
96
+ if (!force && fs.existsSync(agentDest)) {
97
+ skipFile('swift-code-reviewer.md', dryRun);
98
+ } else {
99
+ writeFile(agentDest, agentSrc, 'swift-code-reviewer.md', dryRun);
100
+ }
101
+
102
+ if (!force && fs.existsSync(cmdDest)) {
103
+ skipFile('.gemini/commands/review.toml', dryRun);
104
+ } else {
105
+ writeFile(cmdDest, cmdSrc, '.gemini/commands/review.toml', dryRun);
106
+ }
107
+
108
+ log('\n Gemini CLI hints:', colors.reset);
109
+ log(' /review — run a full Swift code review (via review.toml)', colors.cyan);
110
+ log(' The command uses @./swift-code-reviewer.md to load the guide.\n', colors.cyan);
111
+ }
112
+
113
+ function installKiro(packageRoot, cwd, { dryRun = false, force = false } = {}) {
114
+ log('\n Installing for Kiro…', colors.cyan);
115
+
116
+ const agentSrc = path.join(packageRoot, 'templates', 'agents', 'kiro', 'swift-code-reviewer.md');
117
+ const agentDest = path.join(cwd, '.kiro', 'steering', 'swift-code-reviewer.md');
118
+
119
+ if (!force && fs.existsSync(agentDest)) {
120
+ skipFile('.kiro/steering/swift-code-reviewer.md', dryRun);
121
+ } else {
122
+ writeFile(agentDest, agentSrc, '.kiro/steering/swift-code-reviewer.md', dryRun);
123
+ }
124
+
125
+ log('\n Kiro hints:', colors.reset);
126
+ log(' Steering auto-activates for any *.swift file (fileMatch inclusion).', colors.cyan);
127
+ log(' Note: workspace-scoped steering only — global steering has a known', colors.yellow);
128
+ log(' bug (https://github.com/kirodotdev/Kiro/issues/6171).\n', colors.yellow);
129
+ }
130
+
131
+ const AGENT_INSTALLERS = { claude: installClaude, codex: installCodex, gemini: installGemini, kiro: installKiro };
132
+ const VALID_AGENTS = Object.keys(AGENT_INSTALLERS);
133
+
134
+ module.exports = { installClaude, installCodex, installGemini, installKiro, AGENT_INSTALLERS, VALID_AGENTS };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const { VALID_AGENTS } = require('./agents');
4
+
5
+ /**
6
+ * Resolve which agents to install based on CLI flags and TTY state.
7
+ *
8
+ * Priority:
9
+ * 1. --all flag → all agents
10
+ * 2. --agent <name[,name]> flag → named agents
11
+ * 3. TTY available → interactive @inquirer/prompts checkbox
12
+ * 4. Non-TTY without flags → ['claude'] (CI-safe fallback)
13
+ */
14
+ async function selectAgents({ allFlag, agentFlag, isTTY } = {}) {
15
+ if (allFlag) return [...VALID_AGENTS];
16
+
17
+ if (agentFlag) {
18
+ const requested = agentFlag.split(',').map((s) => s.trim().toLowerCase());
19
+ const invalid = requested.filter((a) => !VALID_AGENTS.includes(a));
20
+ if (invalid.length > 0) {
21
+ console.error(` Unknown agent(s): ${invalid.join(', ')}`);
22
+ console.error(` Valid options: ${VALID_AGENTS.join(', ')}`);
23
+ process.exit(1);
24
+ }
25
+ return requested;
26
+ }
27
+
28
+ if (!isTTY) {
29
+ return ['claude'];
30
+ }
31
+
32
+ const { checkbox } = require('@inquirer/prompts');
33
+ const choices = VALID_AGENTS.map((name) => ({
34
+ name: agentLabel(name),
35
+ value: name,
36
+ checked: name === 'claude',
37
+ }));
38
+
39
+ const selected = await checkbox({
40
+ message: 'Which agent(s) should the review guide be installed for?',
41
+ choices,
42
+ validate: (val) => val.length > 0 || 'Select at least one agent.',
43
+ });
44
+
45
+ return selected;
46
+ }
47
+
48
+ function agentLabel(name) {
49
+ const labels = {
50
+ claude: 'Claude Code (.claude/agents/ + .claude/commands/)',
51
+ codex: 'OpenAI Codex CLI (swift-code-reviewer.md at repo root)',
52
+ gemini: 'Google Gemini CLI (swift-code-reviewer.md + .gemini/commands/review.toml)',
53
+ kiro: 'Kiro (.kiro/steering/swift-code-reviewer.md, fileMatch: **/*.swift)',
54
+ };
55
+ return labels[name] || name;
56
+ }
57
+
58
+ module.exports = { selectAgents };