swift-code-reviewer-skill 1.2.1 → 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/CHANGELOG.md +53 -0
- package/CONTRIBUTING.md +86 -3
- package/README.md +315 -118
- package/SKILL.md +122 -4
- package/bin/install.js +71 -90
- package/bin/lib/agents.js +134 -0
- package/bin/lib/prompt.js +58 -0
- package/core/swift-code-reviewer.core.md +281 -0
- package/examples/README.md +35 -0
- package/examples/claude-tca-review.md +140 -0
- package/examples/codex-async-algorithms-review.md +208 -0
- package/examples/gemini-isowords-review.md +207 -0
- package/package.json +6 -1
- package/references/agent-loop-feedback.md +148 -0
- package/references/spec-adherence.md +157 -0
- package/templates/agents/claude/swift-code-reviewer.md +78 -0
- package/templates/agents/codex/swift-code-reviewer.md +211 -0
- package/templates/agents/gemini/swift-code-reviewer.md +211 -0
- package/templates/agents/kiro/swift-code-reviewer.md +218 -0
- package/templates/commands/claude/review.md +56 -0
- package/templates/commands/gemini/review.toml +15 -0
package/SKILL.md
CHANGED
|
@@ -1,26 +1,68 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: swift-code-reviewer
|
|
3
|
-
description: "
|
|
3
|
+
description: "Perform thorough code reviews for Swift/SwiftUI code, including spec adherence (PR description + linked issues), code quality, architecture, performance, security, Swift 6+ best practices, project standards from .claude/CLAUDE.md, and meta-feedback on recurring patterns that suggest gaps in the agent's instructions. Use when reviewing PRs/MRs (especially AI-generated ones), performing quality audits, validating against original spec, or providing structured feedback with severity levels and improvement suggestions for both the code and the agent loop that produced it."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Swift/SwiftUI Code Review Skill
|
|
7
7
|
|
|
8
8
|
Multi-layer review covering Swift 6+ concurrency, SwiftUI patterns, performance, security, architecture, and project-specific standards. Reads `.claude/CLAUDE.md` and outputs Critical/High/Medium/Low severity findings with `file:line` references and before/after code examples.
|
|
9
9
|
|
|
10
|
+
## When to Use This Skill
|
|
11
|
+
|
|
12
|
+
- "Review this PR"
|
|
13
|
+
- "Review my code" / "Review my changes" / "Review uncommitted changes"
|
|
14
|
+
- "Code review for [component]"
|
|
15
|
+
- "Audit this codebase" / "Check code quality"
|
|
16
|
+
- "Review against .claude/CLAUDE.md" / "Check if this follows our coding standards"
|
|
17
|
+
- "Architecture review" / "Performance audit" / "Security review"
|
|
18
|
+
- "Review this PR against the spec"
|
|
19
|
+
- "Did the agent miss anything from issue #123?"
|
|
20
|
+
- "What rules am I missing in CLAUDE.md based on this PR?"
|
|
21
|
+
- "Review this AI-generated PR"
|
|
22
|
+
|
|
10
23
|
## Workflow
|
|
11
24
|
|
|
12
25
|
### Phase 1 — Context Gathering
|
|
13
26
|
|
|
14
|
-
1.
|
|
27
|
+
1. **Read the Spec**
|
|
28
|
+
- For PRs: `gh pr view <num> --json title,body,closingIssuesReferences,labels`
|
|
29
|
+
- For linked issues: `gh issue view <num> --json title,body,labels`
|
|
30
|
+
- For MRs: `glab mr view <num>` and `glab issue view <num>`
|
|
31
|
+
- Extract:
|
|
32
|
+
- Stated goal / problem being solved
|
|
33
|
+
- Explicit acceptance criteria (look for checkboxes, "should", "must", "Given/When/Then")
|
|
34
|
+
- Edge cases or non-goals mentioned
|
|
35
|
+
- Out-of-scope items
|
|
36
|
+
- If no PR/issue context is available, note this and fall back to inferring intent from the diff.
|
|
37
|
+
2. Try to load `.claude/CLAUDE.md`.
|
|
15
38
|
- **If missing**: add a note to the report — _"No project standards file found — review uses default Apple guidelines"_ — then continue.
|
|
16
|
-
|
|
39
|
+
3. Obtain the changeset: `git diff`, `git diff --cached`, or `gh pr diff <n>`.
|
|
17
40
|
- **If diff is empty**: stop and ask the user to specify files, a PR number, or a directory.
|
|
18
|
-
|
|
41
|
+
4. Read each changed file plus key related files (imports, protocols it conforms to, corresponding test file if present).
|
|
19
42
|
|
|
20
43
|
### Phase 2 — Analysis
|
|
21
44
|
|
|
22
45
|
For each category, load the reference file before writing findings:
|
|
23
46
|
|
|
47
|
+
#### 0. Spec Adherence
|
|
48
|
+
|
|
49
|
+
Reference: `references/spec-adherence.md`
|
|
50
|
+
|
|
51
|
+
- **Requirement Coverage**
|
|
52
|
+
- Does each acceptance criterion map to a concrete code change?
|
|
53
|
+
- Are edge cases mentioned in the spec handled?
|
|
54
|
+
- Are tests covering the scenarios described?
|
|
55
|
+
- **Scope Discipline**
|
|
56
|
+
- Flag changes outside the stated scope (scope creep)
|
|
57
|
+
- Flag unrelated refactors bundled into the PR
|
|
58
|
+
- **Missing Work**
|
|
59
|
+
- TODOs, `fatalError("not implemented")`, empty function bodies
|
|
60
|
+
- Stubbed mocks that should be real implementations
|
|
61
|
+
- Acceptance criteria with no corresponding diff
|
|
62
|
+
- **Intent Drift**
|
|
63
|
+
- Code solves a *similar* but different problem than stated
|
|
64
|
+
- Naming/structure suggests a different mental model than the spec
|
|
65
|
+
|
|
24
66
|
1. **Swift Quality** — concurrency, error handling, optionals, naming → `references/swift-quality-checklist.md`; for concurrency findings also read `skills/swift-concurrency/references/sendable.md` and `actors.md`
|
|
25
67
|
2. **SwiftUI Patterns** — property wrappers, state management, deprecated APIs → `references/swiftui-review-checklist.md`; for wrapper selection read `skills/swiftui-expert-skill/references/state-management.md`
|
|
26
68
|
3. **Performance** — view body cost, ForEach identity, lazy loading, retain cycles → `references/performance-review.md`
|
|
@@ -31,6 +73,28 @@ For each category, load the reference file before writing findings:
|
|
|
31
73
|
For test file findings, consult `skills/swift-testing/references/test-organization.md`.
|
|
32
74
|
For navigation/routing findings, consult `skills/swiftui-ui-patterns/references/navigationstack.md`.
|
|
33
75
|
|
|
76
|
+
### Phase 2.5 — Pattern Detection (for Agent Loop Feedback)
|
|
77
|
+
|
|
78
|
+
**Objective**: Identify recurring issues that point to gaps in the agent's
|
|
79
|
+
instructions, not just the code.
|
|
80
|
+
|
|
81
|
+
After collecting per-file findings, aggregate them:
|
|
82
|
+
|
|
83
|
+
1. Group findings by rule (e.g., "force-unwrap", "deprecated NavigationView",
|
|
84
|
+
"missing @MainActor on UI mutation").
|
|
85
|
+
2. Mark any rule that fires **≥2 times across the diff** as a recurring pattern.
|
|
86
|
+
3. For each recurring pattern, draft a one-line rule suitable for
|
|
87
|
+
`.claude/CLAUDE.md` or an agent system prompt — written as a directive,
|
|
88
|
+
not a description.
|
|
89
|
+
4. If the same recurring pattern appeared in past reviews (check git log of
|
|
90
|
+
`.claude/CLAUDE.md`), escalate priority — the existing rule isn't strong
|
|
91
|
+
enough or isn't being read.
|
|
92
|
+
|
|
93
|
+
Threshold rationale: one occurrence is a slip; two is a pattern; three+ means
|
|
94
|
+
the agent's instructions are silent on this and need an explicit rule.
|
|
95
|
+
|
|
96
|
+
Reference: `references/agent-loop-feedback.md`.
|
|
97
|
+
|
|
34
98
|
### Phase 3 — Report
|
|
35
99
|
|
|
36
100
|
Group findings by file → sort by severity within each file → write prioritized action items.
|
|
@@ -102,6 +166,22 @@ Also migrate from `ObservableObject`/`@Published` to `@Observable` (iOS 17+) —
|
|
|
102
166
|
## Summary
|
|
103
167
|
Files: N | Critical: N | High: N | Medium: N | Low: N
|
|
104
168
|
|
|
169
|
+
## Spec Adherence
|
|
170
|
+
|
|
171
|
+
**Source**: PR #123 / Issue #456
|
|
172
|
+
|
|
173
|
+
| Requirement | Status | Location |
|
|
174
|
+
|-------------|--------|----------|
|
|
175
|
+
| User can log in with email | ✅ Implemented | LoginView.swift:23 |
|
|
176
|
+
| Show error on invalid credentials | ⚠️ Partial — missing 401 case | LoginViewModel.swift:67 |
|
|
177
|
+
| Persist session in Keychain | ❌ Not implemented | — |
|
|
178
|
+
| Rate limit retries | ❌ Not implemented | — |
|
|
179
|
+
|
|
180
|
+
**Scope creep**: 1 unrelated change (UserSettings.swift refactor) — recommend
|
|
181
|
+
splitting into a separate PR.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
105
185
|
## <Filename.swift>
|
|
106
186
|
|
|
107
187
|
[Severity] **<Category>** (line N)
|
|
@@ -115,6 +195,33 @@ Fix: <explanation + corrected snippet>
|
|
|
115
195
|
- [Must fix] ...
|
|
116
196
|
- [Should fix] ...
|
|
117
197
|
- [Consider] ...
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Agent Loop Feedback
|
|
202
|
+
|
|
203
|
+
Recurring patterns suggest the following rules are missing or under-emphasized
|
|
204
|
+
in `.claude/CLAUDE.md`:
|
|
205
|
+
|
|
206
|
+
### Pattern: Force-unwraps (4 occurrences)
|
|
207
|
+
**Files**: LoginView.swift:89, NetworkService.swift:34, UserRepo.swift:12,78
|
|
208
|
+
|
|
209
|
+
**Suggested rule**:
|
|
210
|
+
> Never use `!`, `try!`, or `as!`. Use `guard let` with explicit early return,
|
|
211
|
+
> typed throws, or `as?` with handling. Force-unwraps are crashes waiting to happen.
|
|
212
|
+
|
|
213
|
+
### Pattern: Deprecated NavigationView (2 occurrences)
|
|
214
|
+
**Files**: ProfileView.swift:15, SettingsView.swift:22
|
|
215
|
+
|
|
216
|
+
**Suggested rule**:
|
|
217
|
+
> Use `NavigationStack` exclusively. `NavigationView` is deprecated as of iOS 16.
|
|
218
|
+
|
|
219
|
+
### Pattern: Business logic in View body (3 occurrences)
|
|
220
|
+
**Files**: LoginView.swift:45, ProfileView.swift:78, FeedView.swift:34
|
|
221
|
+
|
|
222
|
+
**Suggested rule**:
|
|
223
|
+
> Views must not contain business logic, network calls, or data transformations.
|
|
224
|
+
> Move all such work into the @Observable view model.
|
|
118
225
|
```
|
|
119
226
|
|
|
120
227
|
Full templates and severity classification: `references/feedback-templates.md`.
|
|
@@ -149,10 +256,21 @@ git diff HEAD~1 # last commit
|
|
|
149
256
|
git diff -- path/to/file.swift
|
|
150
257
|
```
|
|
151
258
|
|
|
259
|
+
## Limitations
|
|
260
|
+
|
|
261
|
+
- Spec adherence checks require an accessible PR description or linked issue.
|
|
262
|
+
When reviewing local changes with no PR context, mark spec adherence as
|
|
263
|
+
"not assessed" rather than guessing intent.
|
|
264
|
+
- Agent loop feedback assumes the code was AI-generated or AI-assisted. For
|
|
265
|
+
fully human-written code, recurring patterns are still useful but should be
|
|
266
|
+
framed as team coding standards rather than agent instructions.
|
|
267
|
+
|
|
152
268
|
## Reference Files
|
|
153
269
|
|
|
154
270
|
- `references/review-workflow.md` — detailed process, diff parsing, git commands
|
|
155
271
|
- `references/feedback-templates.md` — output templates, severity classification
|
|
272
|
+
- `references/spec-adherence.md` — parsing PR/issue specs, requirement coverage tables, scope creep classification
|
|
273
|
+
- `references/agent-loop-feedback.md` — recurring-pattern threshold, directive phrasing, suggested-rule template
|
|
156
274
|
- `references/swift-quality-checklist.md` — Swift 6+, concurrency, optionals, naming
|
|
157
275
|
- `references/swiftui-review-checklist.md` — property wrappers, state, modern APIs
|
|
158
276
|
- `references/performance-review.md` — view optimization, ForEach, resource management
|
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
|
-
|
|
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
|
|
43
|
-
while (!fs.existsSync(path.join(
|
|
44
|
-
const parent = path.dirname(
|
|
45
|
-
if (parent ===
|
|
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
|
-
|
|
44
|
+
dir = parent;
|
|
50
45
|
}
|
|
51
|
-
return
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
136
|
-
const
|
|
137
|
-
const commandsDir = path.join(claudeDir, 'commands');
|
|
116
|
+
const { selectAgents } = require('./lib/prompt');
|
|
117
|
+
const { AGENT_INSTALLERS } = require('./lib/agents');
|
|
138
118
|
|
|
139
|
-
const
|
|
140
|
-
|
|
119
|
+
const agents = await selectAgents({
|
|
120
|
+
allFlag: flags.all,
|
|
121
|
+
agentFlag: flags.agent,
|
|
122
|
+
isTTY: Boolean(process.stdout.isTTY),
|
|
123
|
+
});
|
|
141
124
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
|
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 #
|
|
214
|
-
log(' npx swift-code-reviewer-skill
|
|
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
|
-
//
|
|
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
|
|
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 };
|