@kb-labs/review-entry 2.14.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/README.md +293 -0
- package/dist/commands/flags.d.ts +49 -0
- package/dist/commands/flags.js +46 -0
- package/dist/commands/flags.js.map +1 -0
- package/dist/commands/run.d.ts +46 -0
- package/dist/commands/run.js +334 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +217 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +43 -0
- package/dist/manifest.js +105 -0
- package/dist/manifest.js.map +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# @kb-labs/review-cli
|
|
2
|
+
|
|
3
|
+
CLI commands for AI Review plugin.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
### `review:run`
|
|
8
|
+
|
|
9
|
+
Run code review analysis.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Heuristic mode (CI, fast, no LLM)
|
|
13
|
+
kb review run
|
|
14
|
+
|
|
15
|
+
# Full mode (heuristic + LLM)
|
|
16
|
+
kb review run --mode=full
|
|
17
|
+
|
|
18
|
+
# LLM-only mode (deep analysis)
|
|
19
|
+
kb review run --mode=llm
|
|
20
|
+
|
|
21
|
+
# Scope options
|
|
22
|
+
kb review run --scope=all # Entire codebase
|
|
23
|
+
kb review run --scope=changed # Changed files vs main (default)
|
|
24
|
+
kb review run --scope=staged # Staged files only
|
|
25
|
+
|
|
26
|
+
# Use preset
|
|
27
|
+
kb review run --preset=typescript-strict
|
|
28
|
+
|
|
29
|
+
# Custom file patterns
|
|
30
|
+
kb review run --files="src/**/*.ts" --files="src/**/*.tsx"
|
|
31
|
+
|
|
32
|
+
# JSON output
|
|
33
|
+
kb review run --json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Review Modes
|
|
37
|
+
|
|
38
|
+
### heuristic (CI Mode)
|
|
39
|
+
|
|
40
|
+
Fast, deterministic analysis using only heuristic engines:
|
|
41
|
+
- ESLint (TypeScript/JavaScript)
|
|
42
|
+
- Ruff (Python) - coming soon
|
|
43
|
+
- golangci-lint (Go) - coming soon
|
|
44
|
+
- Clippy (Rust) - coming soon
|
|
45
|
+
|
|
46
|
+
**Use cases:**
|
|
47
|
+
- CI/CD pipelines
|
|
48
|
+
- Pre-commit hooks
|
|
49
|
+
- Quick local checks
|
|
50
|
+
|
|
51
|
+
**Exit code:**
|
|
52
|
+
- 0: No blocker or high severity issues
|
|
53
|
+
- 1: Has blocker or high severity issues
|
|
54
|
+
|
|
55
|
+
### full (Local Mode)
|
|
56
|
+
|
|
57
|
+
Comprehensive analysis with heuristic + LLM:
|
|
58
|
+
- All heuristic engines
|
|
59
|
+
- LLM analyzers for complex issues
|
|
60
|
+
- Cached LLM results
|
|
61
|
+
|
|
62
|
+
**Use cases:**
|
|
63
|
+
- Local development
|
|
64
|
+
- Pre-PR review
|
|
65
|
+
- Weekly codebase audits
|
|
66
|
+
|
|
67
|
+
**Exit code:**
|
|
68
|
+
- 0: No blocker issues
|
|
69
|
+
- 1: Has blocker issues
|
|
70
|
+
|
|
71
|
+
### llm (Deep Analysis Mode)
|
|
72
|
+
|
|
73
|
+
LLM-only analysis for semantic issues:
|
|
74
|
+
- Naming conventions
|
|
75
|
+
- Architecture patterns
|
|
76
|
+
- Logic bugs
|
|
77
|
+
- Code smells
|
|
78
|
+
|
|
79
|
+
**Use cases:**
|
|
80
|
+
- Architecture reviews
|
|
81
|
+
- Complex refactoring
|
|
82
|
+
- Semantic bug detection
|
|
83
|
+
|
|
84
|
+
**Exit code:**
|
|
85
|
+
- 0: No blocker issues
|
|
86
|
+
- 1: Has blocker issues
|
|
87
|
+
|
|
88
|
+
## Scope Options
|
|
89
|
+
|
|
90
|
+
### all
|
|
91
|
+
|
|
92
|
+
Analyze entire codebase:
|
|
93
|
+
```bash
|
|
94
|
+
kb review run --scope=all
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### changed
|
|
98
|
+
|
|
99
|
+
Analyze files changed vs main branch (git diff):
|
|
100
|
+
```bash
|
|
101
|
+
kb review run --scope=changed # default
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### staged
|
|
105
|
+
|
|
106
|
+
Analyze only staged files (git diff --staged):
|
|
107
|
+
```bash
|
|
108
|
+
kb review run --scope=staged
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Presets
|
|
112
|
+
|
|
113
|
+
Use predefined rule sets:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# TypeScript strict preset
|
|
117
|
+
kb review run --preset=typescript-strict
|
|
118
|
+
|
|
119
|
+
# Python production preset
|
|
120
|
+
kb review run --preset=python-production
|
|
121
|
+
|
|
122
|
+
# Security-focused preset
|
|
123
|
+
kb review run --preset=security-all
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Output Formats
|
|
127
|
+
|
|
128
|
+
### Text (default)
|
|
129
|
+
|
|
130
|
+
Human-readable output with colors:
|
|
131
|
+
```
|
|
132
|
+
Running heuristic analysis...
|
|
133
|
+
✔ Found 3 issue(s)
|
|
134
|
+
|
|
135
|
+
┌─ Code Review ─────────────────┐
|
|
136
|
+
│ Summary │
|
|
137
|
+
│ Files: 5 │
|
|
138
|
+
│ Findings: 3 │
|
|
139
|
+
│ High: 1 │
|
|
140
|
+
│ Medium: 2 │
|
|
141
|
+
│ Engines: eslint │
|
|
142
|
+
│ │
|
|
143
|
+
│ Findings │
|
|
144
|
+
│ [HIGH] src/index.ts:10 - ... │
|
|
145
|
+
│ [MEDIUM] src/utils.ts:5 - ... │
|
|
146
|
+
│ [MEDIUM] src/api.ts:20 - ... │
|
|
147
|
+
└───────────────────────────────┘
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### JSON
|
|
151
|
+
|
|
152
|
+
Machine-readable output:
|
|
153
|
+
```bash
|
|
154
|
+
kb review run --json
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"findings": [
|
|
160
|
+
{
|
|
161
|
+
"id": "eslint:src/index.ts:no-unused-vars:10:5",
|
|
162
|
+
"ruleId": "no-unused-vars",
|
|
163
|
+
"type": "code-quality",
|
|
164
|
+
"severity": "medium",
|
|
165
|
+
"confidence": "certain",
|
|
166
|
+
"file": "src/index.ts",
|
|
167
|
+
"line": 10,
|
|
168
|
+
"message": "'foo' is defined but never used.",
|
|
169
|
+
"engine": "eslint",
|
|
170
|
+
"source": "heuristic"
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
"metadata": {
|
|
174
|
+
"mode": "heuristic",
|
|
175
|
+
"scope": "changed",
|
|
176
|
+
"analyzedFiles": 5,
|
|
177
|
+
"duration": 1234,
|
|
178
|
+
"engines": ["eslint"],
|
|
179
|
+
"timestamp": "2025-01-21T12:00:00Z"
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Examples
|
|
185
|
+
|
|
186
|
+
### CI Pipeline
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
# .github/workflows/review.yml
|
|
190
|
+
- name: Code Review
|
|
191
|
+
run: |
|
|
192
|
+
kb review run --mode=heuristic --scope=changed --json > review.json
|
|
193
|
+
|
|
194
|
+
# Fail if issues found
|
|
195
|
+
if [ $? -ne 0 ]; then
|
|
196
|
+
echo "Code review failed"
|
|
197
|
+
exit 1
|
|
198
|
+
fi
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Pre-commit Hook
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
#!/bin/bash
|
|
205
|
+
# .git/hooks/pre-commit
|
|
206
|
+
|
|
207
|
+
kb review run --mode=heuristic --scope=staged
|
|
208
|
+
|
|
209
|
+
if [ $? -ne 0 ]; then
|
|
210
|
+
echo "Fix issues before committing"
|
|
211
|
+
exit 1
|
|
212
|
+
fi
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Local Development
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Quick check
|
|
219
|
+
kb review run
|
|
220
|
+
|
|
221
|
+
# Comprehensive review before PR
|
|
222
|
+
kb review run --mode=full --scope=all
|
|
223
|
+
|
|
224
|
+
# Deep analysis for refactoring
|
|
225
|
+
kb review run --mode=llm --files="src/core/**/*.ts"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Configuration
|
|
229
|
+
|
|
230
|
+
Configure via `.kb/kb.config.json`:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"plugins": {
|
|
235
|
+
"@kb-labs/review": {
|
|
236
|
+
"eslintConfig": ".eslintrc.json",
|
|
237
|
+
"defaultMode": "heuristic",
|
|
238
|
+
"defaultScope": "changed"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Platform Integration
|
|
245
|
+
|
|
246
|
+
The CLI uses KB Labs platform composables:
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
import { useLLM, useCache, useAnalytics, useLoader, useConfig } from '@kb-labs/sdk';
|
|
250
|
+
|
|
251
|
+
// Access LLM (if configured)
|
|
252
|
+
const llm = useLLM({ tier: 'medium' });
|
|
253
|
+
|
|
254
|
+
// Access cache
|
|
255
|
+
const cache = useCache();
|
|
256
|
+
|
|
257
|
+
// Show progress
|
|
258
|
+
const loader = useLoader('Analyzing...');
|
|
259
|
+
loader.start();
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Exit Codes
|
|
263
|
+
|
|
264
|
+
- **0**: Success (no critical issues)
|
|
265
|
+
- **1**: Issues found (blockers or high severity in heuristic mode)
|
|
266
|
+
- **1**: Error during execution
|
|
267
|
+
|
|
268
|
+
## Architecture
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
review:run
|
|
272
|
+
│
|
|
273
|
+
├─ ReviewOrchestrator
|
|
274
|
+
│ ├─ runHeuristicAnalysis()
|
|
275
|
+
│ │ ├─ ESLint adapter
|
|
276
|
+
│ │ ├─ Ruff adapter (TODO)
|
|
277
|
+
│ │ └─ deduplicateFindings()
|
|
278
|
+
│ │
|
|
279
|
+
│ ├─ runFullAnalysis()
|
|
280
|
+
│ │ ├─ heuristic + LLM
|
|
281
|
+
│ │ └─ deduplicateFindings()
|
|
282
|
+
│ │
|
|
283
|
+
│ └─ runLLMAnalysis()
|
|
284
|
+
│ └─ LLM-only (TODO)
|
|
285
|
+
│
|
|
286
|
+
└─ Output (text/json)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Related Packages
|
|
290
|
+
|
|
291
|
+
- **@kb-labs/review-contracts** - Type definitions
|
|
292
|
+
- **@kb-labs/review-heuristic** - Heuristic engines (ESLint, etc.)
|
|
293
|
+
- **@kb-labs/review-core** - Orchestration logic
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Command flags definitions for AI Review plugin
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Flags for review:run command
|
|
8
|
+
*/
|
|
9
|
+
declare const runFlags: _kb_labs_shared_command_kit.FlagSchemaWithInfer<{
|
|
10
|
+
mode: {
|
|
11
|
+
type: "string";
|
|
12
|
+
description: string;
|
|
13
|
+
choices: string[];
|
|
14
|
+
default: string;
|
|
15
|
+
};
|
|
16
|
+
scope: {
|
|
17
|
+
type: "string";
|
|
18
|
+
description: string;
|
|
19
|
+
choices: string[];
|
|
20
|
+
default: string;
|
|
21
|
+
};
|
|
22
|
+
repos: {
|
|
23
|
+
type: "array";
|
|
24
|
+
description: string;
|
|
25
|
+
};
|
|
26
|
+
task: {
|
|
27
|
+
type: "string";
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
preset: {
|
|
31
|
+
type: "string";
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
files: {
|
|
35
|
+
type: "array";
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
38
|
+
eslintConfig: {
|
|
39
|
+
type: "string";
|
|
40
|
+
description: string;
|
|
41
|
+
};
|
|
42
|
+
json: {
|
|
43
|
+
type: "boolean";
|
|
44
|
+
description: string;
|
|
45
|
+
default: false;
|
|
46
|
+
};
|
|
47
|
+
}>;
|
|
48
|
+
|
|
49
|
+
export { runFlags };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineFlags } from '@kb-labs/sdk';
|
|
2
|
+
|
|
3
|
+
// src/commands/flags.ts
|
|
4
|
+
var runFlags = defineFlags({
|
|
5
|
+
mode: {
|
|
6
|
+
type: "string",
|
|
7
|
+
description: "Review mode: heuristic (CI), full (local), llm (deep)",
|
|
8
|
+
choices: ["heuristic", "full", "llm"],
|
|
9
|
+
default: "heuristic"
|
|
10
|
+
},
|
|
11
|
+
scope: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "File scope: all, changed (vs main), staged",
|
|
14
|
+
choices: ["all", "changed", "staged"],
|
|
15
|
+
default: "changed"
|
|
16
|
+
},
|
|
17
|
+
repos: {
|
|
18
|
+
type: "array",
|
|
19
|
+
description: "Repository scope (submodule names to include, e.g., --repos kb-labs-core kb-labs-cli)"
|
|
20
|
+
},
|
|
21
|
+
task: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Task context - describe what the changes are trying to achieve"
|
|
24
|
+
},
|
|
25
|
+
preset: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Preset ID (e.g., typescript-strict)"
|
|
28
|
+
},
|
|
29
|
+
files: {
|
|
30
|
+
type: "array",
|
|
31
|
+
description: "File patterns to analyze"
|
|
32
|
+
},
|
|
33
|
+
eslintConfig: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "ESLint config file path"
|
|
36
|
+
},
|
|
37
|
+
json: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
description: "Output JSON",
|
|
40
|
+
default: false
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export { runFlags };
|
|
45
|
+
//# sourceMappingURL=flags.js.map
|
|
46
|
+
//# sourceMappingURL=flags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/flags.ts"],"names":[],"mappings":";;;AASO,IAAM,WAAW,WAAA,CAAY;AAAA,EAClC,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,WAAA,EAAa,uDAAA;AAAA,IACb,OAAA,EAAS,CAAC,WAAA,EAAa,MAAA,EAAQ,KAAK,CAAA;AAAA,IACpC,OAAA,EAAS;AAAA,GACX;AAAA,EACA,KAAA,EAAO;AAAA,IACL,IAAA,EAAM,QAAA;AAAA,IACN,WAAA,EAAa,4CAAA;AAAA,IACb,OAAA,EAAS,CAAC,KAAA,EAAO,SAAA,EAAW,QAAQ,CAAA;AAAA,IACpC,OAAA,EAAS;AAAA,GACX;AAAA,EACA,KAAA,EAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,WAAA,EAAa;AAAA,GACf;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,QAAA;AAAA,IACN,WAAA,EAAa;AAAA,GACf;AAAA,EACA,MAAA,EAAQ;AAAA,IACN,IAAA,EAAM,QAAA;AAAA,IACN,WAAA,EAAa;AAAA,GACf;AAAA,EACA,KAAA,EAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,WAAA,EAAa;AAAA,GACf;AAAA,EACA,YAAA,EAAc;AAAA,IACZ,IAAA,EAAM,QAAA;AAAA,IACN,WAAA,EAAa;AAAA,GACf;AAAA,EACA,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,SAAA;AAAA,IACN,WAAA,EAAa,aAAA;AAAA,IACb,OAAA,EAAS;AAAA;AAEb,CAAC","file":"flags.js","sourcesContent":["/**\n * Command flags definitions for AI Review plugin\n */\n\nimport { defineFlags } from '@kb-labs/sdk';\n\n/**\n * Flags for review:run command\n */\nexport const runFlags = defineFlags({\n mode: {\n type: 'string',\n description: 'Review mode: heuristic (CI), full (local), llm (deep)',\n choices: ['heuristic', 'full', 'llm'],\n default: 'heuristic',\n },\n scope: {\n type: 'string',\n description: 'File scope: all, changed (vs main), staged',\n choices: ['all', 'changed', 'staged'],\n default: 'changed',\n },\n repos: {\n type: 'array',\n description: 'Repository scope (submodule names to include, e.g., --repos kb-labs-core kb-labs-cli)',\n },\n task: {\n type: 'string',\n description: 'Task context - describe what the changes are trying to achieve',\n },\n preset: {\n type: 'string',\n description: 'Preset ID (e.g., typescript-strict)',\n },\n files: {\n type: 'array',\n description: 'File patterns to analyze',\n },\n eslintConfig: {\n type: 'string',\n description: 'ESLint config file path',\n },\n json: {\n type: 'boolean',\n description: 'Output JSON',\n default: false,\n },\n});\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
|
|
2
|
+
import { CLIInput } from '@kb-labs/sdk';
|
|
3
|
+
import { ReviewMode } from '@kb-labs/review-contracts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Flags for review:run command
|
|
7
|
+
*/
|
|
8
|
+
type RunFlags = {
|
|
9
|
+
mode?: ReviewMode;
|
|
10
|
+
scope?: 'all' | 'changed' | 'staged';
|
|
11
|
+
repos?: string[];
|
|
12
|
+
task?: string;
|
|
13
|
+
preset?: string;
|
|
14
|
+
files?: string[];
|
|
15
|
+
eslintConfig?: string;
|
|
16
|
+
cwd?: string;
|
|
17
|
+
json?: boolean;
|
|
18
|
+
agent?: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Agent-friendly review report.
|
|
22
|
+
* Simplified format for automated workflows.
|
|
23
|
+
*/
|
|
24
|
+
interface AgentReviewReport {
|
|
25
|
+
/** Can the agent proceed (commit/merge)? */
|
|
26
|
+
passed: boolean;
|
|
27
|
+
/** Issues that must be fixed before proceeding */
|
|
28
|
+
issues: Array<{
|
|
29
|
+
file: string;
|
|
30
|
+
line: number;
|
|
31
|
+
problem: string;
|
|
32
|
+
fix: string;
|
|
33
|
+
severity: 'blocker' | 'high' | 'medium' | 'low' | 'info';
|
|
34
|
+
/** Rule ID (e.g., "rule:security/no-eval" or "llm-lite:security") */
|
|
35
|
+
ruleId: string;
|
|
36
|
+
/** Source: "rule" (matched project rule) or "llm" (ad-hoc finding) */
|
|
37
|
+
source: 'rule' | 'llm';
|
|
38
|
+
/** Confidence score (0.0-1.0) from verification engine */
|
|
39
|
+
confidence: number;
|
|
40
|
+
}>;
|
|
41
|
+
/** One-line summary for logs */
|
|
42
|
+
summary: string;
|
|
43
|
+
}
|
|
44
|
+
declare const _default: _kb_labs_shared_command_kit.CommandHandlerV3<unknown, CLIInput<RunFlags>, AgentReviewReport>;
|
|
45
|
+
|
|
46
|
+
export { _default as default };
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { defineCommand, useConfig, useLoader } from '@kb-labs/sdk';
|
|
2
|
+
import { resolveGitScope, getReposWithChanges, runReview } from '@kb-labs/review-core';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { realpath } from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
// src/commands/run.ts
|
|
7
|
+
var SEVERITY_ORDER = {
|
|
8
|
+
blocker: 0,
|
|
9
|
+
high: 1,
|
|
10
|
+
medium: 2,
|
|
11
|
+
low: 3,
|
|
12
|
+
info: 4
|
|
13
|
+
};
|
|
14
|
+
var SECURITY_IGNORE_PATTERNS = [
|
|
15
|
+
"**/node_modules/**",
|
|
16
|
+
"**/dist/**",
|
|
17
|
+
"**/build/**",
|
|
18
|
+
"**/.git/**",
|
|
19
|
+
"**/.env",
|
|
20
|
+
"**/.env.*",
|
|
21
|
+
"**/.ssh/**",
|
|
22
|
+
"**/credentials/**",
|
|
23
|
+
"**/password/**",
|
|
24
|
+
"**/*.pem",
|
|
25
|
+
"**/*.key",
|
|
26
|
+
"**/*.secret"
|
|
27
|
+
];
|
|
28
|
+
var SECURITY_REGEX_PATTERNS = [
|
|
29
|
+
/node_modules/,
|
|
30
|
+
/\.git\//,
|
|
31
|
+
/\.env$/,
|
|
32
|
+
/\.env\./,
|
|
33
|
+
/\.ssh/,
|
|
34
|
+
/\/etc\//,
|
|
35
|
+
/\/usr\//,
|
|
36
|
+
/\/var\//,
|
|
37
|
+
/credentials/i,
|
|
38
|
+
/password/i,
|
|
39
|
+
/\.pem$/,
|
|
40
|
+
/\.key$/,
|
|
41
|
+
/\.secret$/
|
|
42
|
+
];
|
|
43
|
+
async function isPathWithinCwd(filePath, cwd) {
|
|
44
|
+
try {
|
|
45
|
+
const resolvedPath = path.resolve(cwd, filePath);
|
|
46
|
+
const resolvedCwd = path.resolve(cwd);
|
|
47
|
+
const realPath = await realpath(resolvedPath).catch(() => resolvedPath);
|
|
48
|
+
const realCwd = await realpath(resolvedCwd).catch(() => resolvedCwd);
|
|
49
|
+
const relative2 = path.relative(realCwd, realPath);
|
|
50
|
+
return relative2 !== "" && !relative2.startsWith("..") && !path.isAbsolute(relative2);
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function filterSecurePaths(filePaths, cwd) {
|
|
56
|
+
const results = await Promise.all(
|
|
57
|
+
filePaths.map(async (filePath) => {
|
|
58
|
+
if (!await isPathWithinCwd(filePath, cwd)) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (SECURITY_REGEX_PATTERNS.some((pattern) => pattern.test(filePath))) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return filePath;
|
|
65
|
+
})
|
|
66
|
+
);
|
|
67
|
+
return results.filter((p) => p !== null);
|
|
68
|
+
}
|
|
69
|
+
function toAgentReport(result) {
|
|
70
|
+
const blockingIssues = result.findings.filter(
|
|
71
|
+
(f) => f.severity === "blocker" || f.severity === "high"
|
|
72
|
+
);
|
|
73
|
+
const passed = blockingIssues.length === 0;
|
|
74
|
+
const sortedFindings = [...result.findings].sort((a, b) => {
|
|
75
|
+
const aOrder = SEVERITY_ORDER[a.severity] ?? 5;
|
|
76
|
+
const bOrder = SEVERITY_ORDER[b.severity] ?? 5;
|
|
77
|
+
return aOrder - bOrder;
|
|
78
|
+
});
|
|
79
|
+
const issues = sortedFindings.map((f) => ({
|
|
80
|
+
file: f.file,
|
|
81
|
+
line: f.line,
|
|
82
|
+
problem: f.message,
|
|
83
|
+
fix: f.suggestion ?? "Review and fix manually",
|
|
84
|
+
severity: f.severity,
|
|
85
|
+
ruleId: f.ruleId ?? "unknown",
|
|
86
|
+
source: f.ruleId?.startsWith("rule:") ? "rule" : "llm",
|
|
87
|
+
// Map categorical confidence to numeric score (default 0.5 for undefined/unknown)
|
|
88
|
+
confidence: f.confidence === "certain" ? 1 : f.confidence === "likely" ? 0.8 : f.confidence === "heuristic" ? 0.6 : 0.5
|
|
89
|
+
}));
|
|
90
|
+
const mediumCount = result.findings.filter((f) => f.severity === "medium").length;
|
|
91
|
+
const lowCount = result.findings.filter((f) => f.severity === "low").length;
|
|
92
|
+
const infoCount = result.findings.filter((f) => f.severity === "info").length;
|
|
93
|
+
const nonBlockingCount = mediumCount + lowCount + infoCount;
|
|
94
|
+
const summary = passed ? nonBlockingCount > 0 ? `Review passed. ${nonBlockingCount} non-blocking issue(s) found (${mediumCount} medium, ${lowCount} low, ${infoCount} info).` : "Review passed. No issues found." : `Review failed. ${blockingIssues.length} blocking issue(s) must be fixed.`;
|
|
95
|
+
return { passed, issues, summary };
|
|
96
|
+
}
|
|
97
|
+
var run_default = defineCommand({
|
|
98
|
+
id: "review:run",
|
|
99
|
+
description: "Run code review analysis",
|
|
100
|
+
handler: {
|
|
101
|
+
// eslint-disable-next-line sonarjs/cognitive-complexity -- Main orchestration handler: coordinates input modes (repos/files/scope), git resolution, security filtering, output formats (agent/json/human), and severity-based exit codes
|
|
102
|
+
async execute(ctx, input) {
|
|
103
|
+
const startTime = Date.now();
|
|
104
|
+
const flags = input.flags;
|
|
105
|
+
const mode = flags.mode ?? "heuristic";
|
|
106
|
+
const scope = flags.scope ?? "changed";
|
|
107
|
+
const cwd = flags.cwd ?? ctx.cwd ?? process.cwd();
|
|
108
|
+
const fileConfig = await useConfig();
|
|
109
|
+
const loader = useLoader(`Running ${mode} analysis...`);
|
|
110
|
+
loader.start();
|
|
111
|
+
try {
|
|
112
|
+
let files = [];
|
|
113
|
+
let repoScope;
|
|
114
|
+
const reposInput = flags.repos ? Array.isArray(flags.repos) ? flags.repos : [flags.repos] : void 0;
|
|
115
|
+
if (reposInput && reposInput.length > 0) {
|
|
116
|
+
repoScope = reposInput;
|
|
117
|
+
const scopedFiles = await resolveGitScope({
|
|
118
|
+
cwd,
|
|
119
|
+
repos: repoScope,
|
|
120
|
+
includeStaged: scope === "staged" || scope === "changed",
|
|
121
|
+
includeUnstaged: scope === "changed",
|
|
122
|
+
// Include untracked (new) files when explicitly scoping to repos
|
|
123
|
+
includeUntracked: true
|
|
124
|
+
});
|
|
125
|
+
files = scopedFiles.files;
|
|
126
|
+
} else if (flags.files) {
|
|
127
|
+
const filePaths = Array.isArray(flags.files) ? flags.files : [flags.files];
|
|
128
|
+
const safeFilePaths = await filterSecurePaths(filePaths, cwd);
|
|
129
|
+
const fileReadResults = await Promise.allSettled(
|
|
130
|
+
safeFilePaths.map(async (filePath) => ({
|
|
131
|
+
path: filePath,
|
|
132
|
+
content: await ctx.runtime.fs.readFile(filePath, "utf-8")
|
|
133
|
+
}))
|
|
134
|
+
);
|
|
135
|
+
files = fileReadResults.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
|
136
|
+
} else {
|
|
137
|
+
let filePaths = [];
|
|
138
|
+
switch (scope) {
|
|
139
|
+
case "all":
|
|
140
|
+
const [tsFiles, tsxFiles, jsFiles, jsxFiles] = await Promise.all([
|
|
141
|
+
ctx.runtime.fs.glob("**/*.ts", { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] }),
|
|
142
|
+
ctx.runtime.fs.glob("**/*.tsx", { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] }),
|
|
143
|
+
ctx.runtime.fs.glob("**/*.js", { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] }),
|
|
144
|
+
ctx.runtime.fs.glob("**/*.jsx", { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] })
|
|
145
|
+
]);
|
|
146
|
+
filePaths = [...tsFiles, ...tsxFiles, ...jsFiles, ...jsxFiles];
|
|
147
|
+
break;
|
|
148
|
+
case "staged":
|
|
149
|
+
const reposWithChanges = await getReposWithChanges(cwd);
|
|
150
|
+
if (reposWithChanges.length > 0) {
|
|
151
|
+
const scopedFiles = await resolveGitScope({
|
|
152
|
+
cwd,
|
|
153
|
+
repos: reposWithChanges,
|
|
154
|
+
includeStaged: true,
|
|
155
|
+
includeUnstaged: false,
|
|
156
|
+
includeUntracked: false
|
|
157
|
+
});
|
|
158
|
+
files = scopedFiles.files;
|
|
159
|
+
repoScope = reposWithChanges;
|
|
160
|
+
} else {
|
|
161
|
+
filePaths = await ctx.runtime.fs.glob("**/*.{ts,tsx,js,jsx}", {
|
|
162
|
+
cwd,
|
|
163
|
+
ignore: [...SECURITY_IGNORE_PATTERNS]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
break;
|
|
167
|
+
case "changed":
|
|
168
|
+
default:
|
|
169
|
+
const changedRepos = await getReposWithChanges(cwd);
|
|
170
|
+
if (changedRepos.length > 0) {
|
|
171
|
+
const scopedFiles = await resolveGitScope({
|
|
172
|
+
cwd,
|
|
173
|
+
repos: changedRepos,
|
|
174
|
+
includeStaged: true,
|
|
175
|
+
includeUnstaged: true,
|
|
176
|
+
includeUntracked: false
|
|
177
|
+
});
|
|
178
|
+
files = scopedFiles.files;
|
|
179
|
+
repoScope = changedRepos;
|
|
180
|
+
} else {
|
|
181
|
+
filePaths = await ctx.runtime.fs.glob("**/*.{ts,tsx,js,jsx}", {
|
|
182
|
+
cwd,
|
|
183
|
+
ignore: [...SECURITY_IGNORE_PATTERNS]
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
if (files.length === 0 && filePaths.length > 0) {
|
|
189
|
+
const safeFilePaths = await filterSecurePaths(filePaths, cwd);
|
|
190
|
+
const fileReadResults = await Promise.allSettled(
|
|
191
|
+
safeFilePaths.map(async (filePath) => ({
|
|
192
|
+
path: filePath,
|
|
193
|
+
content: await ctx.runtime.fs.readFile(filePath, "utf-8")
|
|
194
|
+
}))
|
|
195
|
+
);
|
|
196
|
+
files = fileReadResults.filter((r) => r.status === "fulfilled").map((r) => r.value);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const result = await runReview({
|
|
200
|
+
files,
|
|
201
|
+
mode,
|
|
202
|
+
presetId: flags.preset ?? "default",
|
|
203
|
+
cwd,
|
|
204
|
+
taskContext: flags.task,
|
|
205
|
+
repoScope,
|
|
206
|
+
config: {
|
|
207
|
+
eslintConfig: flags.eslintConfig ?? fileConfig?.eslintConfig
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
loader.succeed(`Found ${result.findings.length} issue(s)`);
|
|
211
|
+
if (flags.json) {
|
|
212
|
+
ctx.ui?.json?.(result);
|
|
213
|
+
} else {
|
|
214
|
+
const sections = [];
|
|
215
|
+
const counts = {
|
|
216
|
+
blocker: result.findings.filter((f) => f.severity === "blocker").length,
|
|
217
|
+
high: result.findings.filter((f) => f.severity === "high").length,
|
|
218
|
+
medium: result.findings.filter((f) => f.severity === "medium").length,
|
|
219
|
+
low: result.findings.filter((f) => f.severity === "low").length,
|
|
220
|
+
info: result.findings.filter((f) => f.severity === "info").length
|
|
221
|
+
};
|
|
222
|
+
const summaryItems = [
|
|
223
|
+
`Files: ${result.metadata.analyzedFiles}`,
|
|
224
|
+
`Findings: ${result.findings.length}`
|
|
225
|
+
];
|
|
226
|
+
if (flags.task) {
|
|
227
|
+
summaryItems.push(`Task: ${flags.task}`);
|
|
228
|
+
}
|
|
229
|
+
if (repoScope && repoScope.length > 0) {
|
|
230
|
+
summaryItems.push(`Repos: ${repoScope.join(", ")}`);
|
|
231
|
+
}
|
|
232
|
+
if (counts.blocker > 0) {
|
|
233
|
+
summaryItems.push(`Blocker: ${counts.blocker}`);
|
|
234
|
+
}
|
|
235
|
+
if (counts.high > 0) {
|
|
236
|
+
summaryItems.push(`High: ${counts.high}`);
|
|
237
|
+
}
|
|
238
|
+
if (counts.medium > 0) {
|
|
239
|
+
summaryItems.push(`Medium: ${counts.medium}`);
|
|
240
|
+
}
|
|
241
|
+
if (counts.low > 0) {
|
|
242
|
+
summaryItems.push(`Low: ${counts.low}`);
|
|
243
|
+
}
|
|
244
|
+
if (counts.info > 0) {
|
|
245
|
+
summaryItems.push(`Info: ${counts.info}`);
|
|
246
|
+
}
|
|
247
|
+
summaryItems.push(`Engines: ${result.metadata.engines.join(", ")}`);
|
|
248
|
+
const incr = result.metadata.incremental;
|
|
249
|
+
if (incr) {
|
|
250
|
+
if (incr.cachedFiles > 0) {
|
|
251
|
+
summaryItems.push(`Cached: ${incr.cachedFiles} file(s) skipped`);
|
|
252
|
+
}
|
|
253
|
+
if (incr.newFindings > 0 || incr.knownFindings > 0 || incr.cachedFindings > 0) {
|
|
254
|
+
const parts = [];
|
|
255
|
+
if (incr.newFindings > 0) {
|
|
256
|
+
parts.push(`${incr.newFindings} new`);
|
|
257
|
+
}
|
|
258
|
+
if (incr.knownFindings > 0) {
|
|
259
|
+
parts.push(`${incr.knownFindings} known`);
|
|
260
|
+
}
|
|
261
|
+
if (incr.cachedFindings > 0) {
|
|
262
|
+
parts.push(`${incr.cachedFindings} cached`);
|
|
263
|
+
}
|
|
264
|
+
summaryItems.push(`Breakdown: ${parts.join(", ")}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
sections.push({
|
|
268
|
+
header: "Summary",
|
|
269
|
+
items: summaryItems
|
|
270
|
+
});
|
|
271
|
+
if (result.findings.length > 0) {
|
|
272
|
+
const sortedFindings = [...result.findings].sort((a, b) => {
|
|
273
|
+
const aOrder = SEVERITY_ORDER[a.severity] ?? 5;
|
|
274
|
+
const bOrder = SEVERITY_ORDER[b.severity] ?? 5;
|
|
275
|
+
return aOrder - bOrder;
|
|
276
|
+
});
|
|
277
|
+
const topFindings = sortedFindings.slice(0, 10);
|
|
278
|
+
const findingsItems = topFindings.map((f) => {
|
|
279
|
+
const loc = `${f.file}:${f.line}`;
|
|
280
|
+
const severity = f.severity.toUpperCase();
|
|
281
|
+
return `[${severity}] ${loc} - ${f.message}`;
|
|
282
|
+
});
|
|
283
|
+
if (result.findings.length > 10) {
|
|
284
|
+
findingsItems.push(`... and ${result.findings.length - 10} more`);
|
|
285
|
+
}
|
|
286
|
+
sections.push({
|
|
287
|
+
header: "Findings",
|
|
288
|
+
items: findingsItems
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
const timing = Date.now() - startTime;
|
|
292
|
+
if (result.findings.length === 0) {
|
|
293
|
+
ctx.ui?.success?.("No issues found", {
|
|
294
|
+
title: "Code Review",
|
|
295
|
+
sections,
|
|
296
|
+
timing
|
|
297
|
+
});
|
|
298
|
+
} else {
|
|
299
|
+
ctx.ui?.warn?.(`Found ${result.findings.length} issue(s)`, {
|
|
300
|
+
title: "Code Review",
|
|
301
|
+
sections,
|
|
302
|
+
timing
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const blockerCount = result.findings.filter((f) => f.severity === "blocker").length;
|
|
307
|
+
const highCount = result.findings.filter((f) => f.severity === "high").length;
|
|
308
|
+
const agentReport = toAgentReport(result);
|
|
309
|
+
if (blockerCount > 0) {
|
|
310
|
+
return { exitCode: 1, result: agentReport };
|
|
311
|
+
}
|
|
312
|
+
if (mode === "heuristic" && highCount > 0) {
|
|
313
|
+
return { exitCode: 1, result: agentReport };
|
|
314
|
+
}
|
|
315
|
+
return { exitCode: 0, result: agentReport };
|
|
316
|
+
} catch (error) {
|
|
317
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
318
|
+
loader.fail(`Review failed: ${message}`);
|
|
319
|
+
ctx.ui?.error?.(message);
|
|
320
|
+
ctx.platform.logger?.error?.("review:run failed", error instanceof Error ? error : new Error(message));
|
|
321
|
+
const errorReport = {
|
|
322
|
+
passed: false,
|
|
323
|
+
issues: [],
|
|
324
|
+
summary: `Review error: ${message}`
|
|
325
|
+
};
|
|
326
|
+
return { exitCode: 1, result: errorReport };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
export { run_default as default };
|
|
333
|
+
//# sourceMappingURL=run.js.map
|
|
334
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/run.ts"],"names":["relative"],"mappings":"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","file":"run.js","sourcesContent":["/**\n * review:run command\n * Run code review analysis\n */\n\nimport {\n defineCommand,\n useLoader,\n useConfig,\n type PluginContextV3,\n type CLIInput,\n type CommandResult,\n} from '@kb-labs/sdk';\nimport type { ReviewMode, ReviewResult, InputFile } from '@kb-labs/review-contracts';\nimport { runReview, resolveGitScope, getReposWithChanges } from '@kb-labs/review-core';\nimport * as path from 'node:path';\nimport { realpath } from 'node:fs/promises';\n\n/**\n * Flags for review:run command\n */\ntype RunFlags = {\n mode?: ReviewMode;\n scope?: 'all' | 'changed' | 'staged';\n repos?: string[];\n task?: string;\n preset?: string;\n files?: string[];\n eslintConfig?: string;\n cwd?: string;\n json?: boolean;\n agent?: boolean;\n};\n\n/**\n * Agent-friendly review report.\n * Simplified format for automated workflows.\n */\ninterface AgentReviewReport {\n /** Can the agent proceed (commit/merge)? */\n passed: boolean;\n /** Issues that must be fixed before proceeding */\n issues: Array<{\n file: string;\n line: number;\n problem: string;\n fix: string;\n severity: 'blocker' | 'high' | 'medium' | 'low' | 'info';\n /** Rule ID (e.g., \"rule:security/no-eval\" or \"llm-lite:security\") */\n ruleId: string;\n /** Source: \"rule\" (matched project rule) or \"llm\" (ad-hoc finding) */\n source: 'rule' | 'llm';\n /** Confidence score (0.0-1.0) from verification engine */\n confidence: number;\n }>;\n /** One-line summary for logs */\n summary: string;\n}\n\n/**\n * Severity order for sorting (lower = more critical)\n */\nconst SEVERITY_ORDER: Record<string, number> = {\n blocker: 0,\n high: 1,\n medium: 2,\n low: 3,\n info: 4,\n};\n\n/**\n * Security-sensitive patterns for file filtering.\n * Shared across all file filtering operations for consistency.\n */\nconst SECURITY_IGNORE_PATTERNS = [\n '**/node_modules/**',\n '**/dist/**',\n '**/build/**',\n '**/.git/**',\n '**/.env',\n '**/.env.*',\n '**/.ssh/**',\n '**/credentials/**',\n '**/password/**',\n '**/*.pem',\n '**/*.key',\n '**/*.secret',\n] as const;\n\n/**\n * Regex patterns for additional security filtering.\n * Used as a second layer of defense after glob ignore.\n */\nconst SECURITY_REGEX_PATTERNS = [\n /node_modules/,\n /\\.git\\//,\n /\\.env$/,\n /\\.env\\./,\n /\\.ssh/,\n /\\/etc\\//,\n /\\/usr\\//,\n /\\/var\\//,\n /credentials/i,\n /password/i,\n /\\.pem$/,\n /\\.key$/,\n /\\.secret$/,\n] as const;\n\n/**\n * Validate that a file path is within the allowed directory.\n * Prevents path traversal attacks including symlink bypass.\n * Uses fs.realpath() to resolve symlinks and path.relative() for cross-platform safety.\n */\nasync function isPathWithinCwd(filePath: string, cwd: string): Promise<boolean> {\n try {\n const resolvedPath = path.resolve(cwd, filePath);\n const resolvedCwd = path.resolve(cwd);\n\n // Resolve symlinks to prevent symlink bypass attacks\n const realPath = await realpath(resolvedPath).catch(() => resolvedPath);\n const realCwd = await realpath(resolvedCwd).catch(() => resolvedCwd);\n\n const relative = path.relative(realCwd, realPath);\n // Path is within cwd if relative path exists, doesn't start with '..', and isn't absolute\n return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);\n } catch {\n // If path doesn't exist or can't be resolved, reject it\n return false;\n }\n}\n\n/**\n * Filter file paths for security concerns.\n */\nasync function filterSecurePaths(filePaths: string[], cwd: string): Promise<string[]> {\n const results = await Promise.all(\n filePaths.map(async filePath => {\n // Path traversal check (including symlink resolution)\n if (!await isPathWithinCwd(filePath, cwd)) {\n return null;\n }\n // Security pattern check\n if (SECURITY_REGEX_PATTERNS.some(pattern => pattern.test(filePath))) {\n return null;\n }\n return filePath;\n })\n );\n return results.filter((p): p is string => p !== null);\n}\n\n/**\n * Transform ReviewResult to agent-friendly format\n */\nfunction toAgentReport(result: ReviewResult): AgentReviewReport {\n // Blocking issues = blocker and high severity only\n // Medium/low/info are acceptable and don't block the review\n const blockingIssues = result.findings.filter(\n f => f.severity === 'blocker' || f.severity === 'high'\n );\n\n // Pass when no blocking issues (medium and below are acceptable)\n const passed = blockingIssues.length === 0;\n\n // Sort by severity: blocker > high > medium > low > info\n const sortedFindings = [...result.findings].sort((a, b) => {\n const aOrder = SEVERITY_ORDER[a.severity] ?? 5;\n const bOrder = SEVERITY_ORDER[b.severity] ?? 5;\n return aOrder - bOrder;\n });\n\n const issues = sortedFindings.map(f => ({\n file: f.file,\n line: f.line,\n problem: f.message,\n fix: f.suggestion ?? 'Review and fix manually',\n severity: f.severity as 'blocker' | 'high' | 'medium' | 'low' | 'info',\n ruleId: f.ruleId ?? 'unknown',\n source: (f.ruleId?.startsWith('rule:') ? 'rule' : 'llm') as 'rule' | 'llm',\n // Map categorical confidence to numeric score (default 0.5 for undefined/unknown)\n confidence: f.confidence === 'certain' ? 1.0\n : f.confidence === 'likely' ? 0.8\n : f.confidence === 'heuristic' ? 0.6\n : 0.5,\n }));\n\n // Count non-blocking issues for summary\n const mediumCount = result.findings.filter(f => f.severity === 'medium').length;\n const lowCount = result.findings.filter(f => f.severity === 'low').length;\n const infoCount = result.findings.filter(f => f.severity === 'info').length;\n const nonBlockingCount = mediumCount + lowCount + infoCount;\n\n const summary = passed\n ? nonBlockingCount > 0\n ? `Review passed. ${nonBlockingCount} non-blocking issue(s) found (${mediumCount} medium, ${lowCount} low, ${infoCount} info).`\n : 'Review passed. No issues found.'\n : `Review failed. ${blockingIssues.length} blocking issue(s) must be fixed.`;\n\n return { passed, issues, summary };\n}\n\nexport default defineCommand<unknown, CLIInput<RunFlags>, AgentReviewReport>({\n id: 'review:run',\n description: 'Run code review analysis',\n\n handler: {\n // eslint-disable-next-line sonarjs/cognitive-complexity -- Main orchestration handler: coordinates input modes (repos/files/scope), git resolution, security filtering, output formats (agent/json/human), and severity-based exit codes\n async execute(ctx: PluginContextV3, input: CLIInput<RunFlags>): Promise<CommandResult<AgentReviewReport>> {\n const startTime = Date.now();\n\n // Extract flags from input\n const flags = input.flags;\n\n // Parse input\n const mode = flags.mode ?? 'heuristic';\n const scope = flags.scope ?? 'changed';\n\n // Use --cwd flag if provided, otherwise use current directory where command was run\n const cwd = flags.cwd ?? ctx.cwd ?? process.cwd();\n\n // Load config\n const fileConfig = await useConfig<{ eslintConfig?: string }>();\n\n // Show progress\n const loader = useLoader(`Running ${mode} analysis...`);\n loader.start();\n\n try {\n // Collect files based on scope\n let files: InputFile[] = [];\n let repoScope: string[] | undefined;\n\n // If --repos flag is provided, use git-scope resolver\n // Normalize repos to array (may come as string from CLI)\n const reposInput = flags.repos\n ? (Array.isArray(flags.repos) ? flags.repos : [flags.repos])\n : undefined;\n\n if (reposInput && reposInput.length > 0) {\n repoScope = reposInput;\n\n const scopedFiles = await resolveGitScope({\n cwd,\n repos: repoScope,\n includeStaged: scope === 'staged' || scope === 'changed',\n includeUnstaged: scope === 'changed',\n // Include untracked (new) files when explicitly scoping to repos\n includeUntracked: true,\n });\n\n files = scopedFiles.files;\n } else if (flags.files) {\n // Use explicitly provided files (with security validation)\n const filePaths = Array.isArray(flags.files) ? flags.files : [flags.files];\n const safeFilePaths = await filterSecurePaths(filePaths, cwd);\n\n const fileReadResults = await Promise.allSettled(\n safeFilePaths.map(async (filePath) => ({\n path: filePath,\n content: await ctx.runtime.fs.readFile(filePath, 'utf-8'),\n }))\n );\n\n files = fileReadResults\n .filter((r): r is PromiseFulfilledResult<InputFile> => r.status === 'fulfilled')\n .map(r => r.value);\n } else {\n // Resolve files based on file scope (all/changed/staged)\n let filePaths: string[] = [];\n\n switch (scope) {\n case 'all':\n // All TypeScript/JavaScript files\n // Run multiple globs in parallel since ctx.runtime.fs.glob might not support brace expansion\n const [tsFiles, tsxFiles, jsFiles, jsxFiles] = await Promise.all([\n ctx.runtime.fs.glob('**/*.ts', { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] }),\n ctx.runtime.fs.glob('**/*.tsx', { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] }),\n ctx.runtime.fs.glob('**/*.js', { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] }),\n ctx.runtime.fs.glob('**/*.jsx', { cwd, ignore: [...SECURITY_IGNORE_PATTERNS] }),\n ]);\n filePaths = [...tsFiles, ...tsxFiles, ...jsFiles, ...jsxFiles];\n break;\n\n case 'staged':\n // Git staged files - try to detect repos with changes\n const reposWithChanges = await getReposWithChanges(cwd);\n if (reposWithChanges.length > 0) {\n const scopedFiles = await resolveGitScope({\n cwd,\n repos: reposWithChanges,\n includeStaged: true,\n includeUnstaged: false,\n includeUntracked: false,\n });\n files = scopedFiles.files;\n repoScope = reposWithChanges;\n } else {\n // Fallback to glob\n filePaths = await ctx.runtime.fs.glob('**/*.{ts,tsx,js,jsx}', {\n cwd,\n ignore: [...SECURITY_IGNORE_PATTERNS],\n });\n }\n break;\n\n case 'changed':\n default:\n // Changed files - try to detect repos with changes\n const changedRepos = await getReposWithChanges(cwd);\n if (changedRepos.length > 0) {\n const scopedFiles = await resolveGitScope({\n cwd,\n repos: changedRepos,\n includeStaged: true,\n includeUnstaged: true,\n includeUntracked: false,\n });\n files = scopedFiles.files;\n repoScope = changedRepos;\n } else {\n // Fallback to glob\n filePaths = await ctx.runtime.fs.glob('**/*.{ts,tsx,js,jsx}', {\n cwd,\n ignore: [...SECURITY_IGNORE_PATTERNS],\n });\n }\n break;\n }\n\n // If we used glob fallback, read file contents\n if (files.length === 0 && filePaths.length > 0) {\n // Apply security filtering (path traversal + pattern matching)\n const safeFilePaths = await filterSecurePaths(filePaths, cwd);\n\n // Read files with individual error handling\n const fileReadResults = await Promise.allSettled(\n safeFilePaths.map(async (filePath) => ({\n path: filePath,\n content: await ctx.runtime.fs.readFile(filePath, 'utf-8'),\n }))\n );\n\n // Collect successful reads, skip failed ones\n files = fileReadResults\n .filter((r): r is PromiseFulfilledResult<InputFile> => r.status === 'fulfilled')\n .map(r => r.value);\n }\n }\n\n // Run review\n const result = await runReview({\n files,\n mode,\n presetId: flags.preset ?? 'default',\n cwd,\n taskContext: flags.task,\n repoScope,\n config: {\n eslintConfig: flags.eslintConfig ?? fileConfig?.eslintConfig,\n },\n });\n\n loader.succeed(`Found ${result.findings.length} issue(s)`);\n\n // Output results\n if (flags.json) {\n ctx.ui?.json?.(result);\n } else {\n // Build sections\n const sections: Array<{ header?: string; items: string[] }> = [];\n\n // Summary section\n const counts = {\n blocker: result.findings.filter((f) => f.severity === 'blocker').length,\n high: result.findings.filter((f) => f.severity === 'high').length,\n medium: result.findings.filter((f) => f.severity === 'medium').length,\n low: result.findings.filter((f) => f.severity === 'low').length,\n info: result.findings.filter((f) => f.severity === 'info').length,\n };\n\n const summaryItems: string[] = [\n `Files: ${result.metadata.analyzedFiles}`,\n `Findings: ${result.findings.length}`,\n ];\n\n // Show task context if provided\n if (flags.task) {\n summaryItems.push(`Task: ${flags.task}`);\n }\n\n // Show repo scope if used\n if (repoScope && repoScope.length > 0) {\n summaryItems.push(`Repos: ${repoScope.join(', ')}`);\n }\n\n if (counts.blocker > 0) {\n summaryItems.push(`Blocker: ${counts.blocker}`);\n }\n if (counts.high > 0) {\n summaryItems.push(`High: ${counts.high}`);\n }\n if (counts.medium > 0) {\n summaryItems.push(`Medium: ${counts.medium}`);\n }\n if (counts.low > 0) {\n summaryItems.push(`Low: ${counts.low}`);\n }\n if (counts.info > 0) {\n summaryItems.push(`Info: ${counts.info}`);\n }\n\n summaryItems.push(`Engines: ${result.metadata.engines.join(', ')}`);\n\n // Show incremental stats if available (LLM modes with caching)\n const incr = result.metadata.incremental;\n if (incr) {\n if (incr.cachedFiles > 0) {\n summaryItems.push(`Cached: ${incr.cachedFiles} file(s) skipped`);\n }\n if (incr.newFindings > 0 || incr.knownFindings > 0 || incr.cachedFindings > 0) {\n const parts: string[] = [];\n if (incr.newFindings > 0) {\n parts.push(`${incr.newFindings} new`);\n }\n if (incr.knownFindings > 0) {\n parts.push(`${incr.knownFindings} known`);\n }\n if (incr.cachedFindings > 0) {\n parts.push(`${incr.cachedFindings} cached`);\n }\n summaryItems.push(`Breakdown: ${parts.join(', ')}`);\n }\n }\n\n sections.push({\n header: 'Summary',\n items: summaryItems,\n });\n\n // Findings section (top 10, sorted by severity)\n if (result.findings.length > 0) {\n // Sort by severity: blocker > high > medium > low > info\n const sortedFindings = [...result.findings].sort((a, b) => {\n const aOrder = SEVERITY_ORDER[a.severity] ?? 5;\n const bOrder = SEVERITY_ORDER[b.severity] ?? 5;\n return aOrder - bOrder;\n });\n\n const topFindings = sortedFindings.slice(0, 10);\n const findingsItems = topFindings.map((f) => {\n const loc = `${f.file}:${f.line}`;\n const severity = f.severity.toUpperCase();\n return `[${severity}] ${loc} - ${f.message}`;\n });\n\n if (result.findings.length > 10) {\n findingsItems.push(`... and ${result.findings.length - 10} more`);\n }\n\n sections.push({\n header: 'Findings',\n items: findingsItems,\n });\n }\n\n const timing = Date.now() - startTime;\n\n if (result.findings.length === 0) {\n ctx.ui?.success?.('No issues found', {\n title: 'Code Review',\n sections,\n timing,\n });\n } else {\n ctx.ui?.warn?.(`Found ${result.findings.length} issue(s)`, {\n title: 'Code Review',\n sections,\n timing,\n });\n }\n }\n\n // Exit code based on severity\n const blockerCount = result.findings.filter((f) => f.severity === 'blocker').length;\n const highCount = result.findings.filter((f) => f.severity === 'high').length;\n const agentReport = toAgentReport(result);\n\n if (blockerCount > 0) {\n return { exitCode: 1, result: agentReport };\n }\n\n if (mode === 'heuristic' && highCount > 0) {\n // In CI mode (heuristic), fail on high severity\n return { exitCode: 1, result: agentReport };\n }\n\n return { exitCode: 0, result: agentReport };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n loader.fail(`Review failed: ${message}`);\n ctx.ui?.error?.(message);\n ctx.platform.logger?.error?.('review:run failed', error instanceof Error ? error : new Error(message));\n const errorReport: AgentReviewReport = {\n passed: false,\n issues: [],\n summary: `Review error: ${message}`,\n };\n return { exitCode: 1, result: errorReport };\n }\n },\n },\n});\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { default as manifest } from './manifest.js';
|
|
2
|
+
import { ReviewFinding, ReviewResult } from '@kb-labs/review-contracts';
|
|
3
|
+
import '@kb-labs/perm-presets';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @module @kb-labs/review-cli/formatters
|
|
7
|
+
* Output formatters for code review results.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format findings as text.
|
|
12
|
+
*/
|
|
13
|
+
declare function formatFindings(findings: ReviewFinding[]): string;
|
|
14
|
+
/**
|
|
15
|
+
* Format summary statistics.
|
|
16
|
+
*/
|
|
17
|
+
declare function formatSummary(result: ReviewResult): string;
|
|
18
|
+
|
|
19
|
+
export { formatFindings, formatSummary };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { defineFlags, combinePermissions, gitWorkflowPreset, kbPlatformPreset, defineCommandFlags } from '@kb-labs/sdk';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
|
|
4
|
+
// src/manifest.ts
|
|
5
|
+
var runFlags = defineFlags({
|
|
6
|
+
mode: {
|
|
7
|
+
type: "string",
|
|
8
|
+
description: "Review mode: heuristic (CI), full (local), llm (deep)",
|
|
9
|
+
choices: ["heuristic", "full", "llm"],
|
|
10
|
+
default: "heuristic"
|
|
11
|
+
},
|
|
12
|
+
scope: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "File scope: all, changed (vs main), staged",
|
|
15
|
+
choices: ["all", "changed", "staged"],
|
|
16
|
+
default: "changed"
|
|
17
|
+
},
|
|
18
|
+
repos: {
|
|
19
|
+
type: "array",
|
|
20
|
+
description: "Repository scope (submodule names to include, e.g., --repos kb-labs-core kb-labs-cli)"
|
|
21
|
+
},
|
|
22
|
+
task: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Task context - describe what the changes are trying to achieve"
|
|
25
|
+
},
|
|
26
|
+
preset: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Preset ID (e.g., typescript-strict)"
|
|
29
|
+
},
|
|
30
|
+
files: {
|
|
31
|
+
type: "array",
|
|
32
|
+
description: "File patterns to analyze"
|
|
33
|
+
},
|
|
34
|
+
eslintConfig: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "ESLint config file path"
|
|
37
|
+
},
|
|
38
|
+
json: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
description: "Output JSON",
|
|
41
|
+
default: false
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// src/manifest.ts
|
|
46
|
+
var pluginPermissions = combinePermissions().with(gitWorkflowPreset).with(kbPlatformPreset).withFs({
|
|
47
|
+
mode: "read",
|
|
48
|
+
allow: ["**/*"]
|
|
49
|
+
}).withPlatform({
|
|
50
|
+
llm: true,
|
|
51
|
+
// LLM for full/llm modes
|
|
52
|
+
cache: ["review:"],
|
|
53
|
+
// Cache namespace prefix
|
|
54
|
+
analytics: true
|
|
55
|
+
// Track review events
|
|
56
|
+
}).withQuotas({
|
|
57
|
+
timeoutMs: 6e5,
|
|
58
|
+
// 10 min for deep analysis
|
|
59
|
+
memoryMb: 512
|
|
60
|
+
}).build();
|
|
61
|
+
var manifest = {
|
|
62
|
+
schema: "kb.plugin/3",
|
|
63
|
+
id: "@kb-labs/review",
|
|
64
|
+
version: "0.1.0",
|
|
65
|
+
display: {
|
|
66
|
+
name: "AI Review",
|
|
67
|
+
description: "AI-powered code review with heuristic engines (ESLint, Ruff, etc.) and LLM analyzers.",
|
|
68
|
+
tags: ["code-review", "linting", "ai", "quality"]
|
|
69
|
+
},
|
|
70
|
+
// Configuration section in kb.config.json
|
|
71
|
+
configSection: "review",
|
|
72
|
+
// Platform requirements
|
|
73
|
+
platform: {
|
|
74
|
+
requires: ["storage", "cache"],
|
|
75
|
+
optional: ["llm", "analytics", "logger"]
|
|
76
|
+
},
|
|
77
|
+
// CLI commands
|
|
78
|
+
cli: {
|
|
79
|
+
commands: [
|
|
80
|
+
{
|
|
81
|
+
id: "review:run",
|
|
82
|
+
group: "review",
|
|
83
|
+
describe: "Run code review analysis",
|
|
84
|
+
longDescription: "Analyzes code using heuristic engines (ESLint, Ruff, etc.) and optionally LLM analyzers. Supports three modes: heuristic (CI, fast), full (local, comprehensive), llm (deep analysis).",
|
|
85
|
+
handler: "./commands/run.js#default",
|
|
86
|
+
handlerPath: "./commands/run.js",
|
|
87
|
+
flags: defineCommandFlags(runFlags.schema),
|
|
88
|
+
examples: [
|
|
89
|
+
"kb review run",
|
|
90
|
+
"kb review run --mode=full",
|
|
91
|
+
"kb review run --scope=staged",
|
|
92
|
+
"kb review run --preset=typescript-strict",
|
|
93
|
+
'kb review run --files="src/**/*.ts"',
|
|
94
|
+
"kb review run --json"
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
]
|
|
98
|
+
},
|
|
99
|
+
// Permissions
|
|
100
|
+
permissions: pluginPermissions
|
|
101
|
+
};
|
|
102
|
+
var manifest_default = manifest;
|
|
103
|
+
function formatFindings(findings) {
|
|
104
|
+
if (findings.length === 0) {
|
|
105
|
+
return chalk.green("\u2713 No issues found!");
|
|
106
|
+
}
|
|
107
|
+
const lines = [];
|
|
108
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
109
|
+
for (const finding of findings) {
|
|
110
|
+
if (!byFile.has(finding.file)) {
|
|
111
|
+
byFile.set(finding.file, []);
|
|
112
|
+
}
|
|
113
|
+
byFile.get(finding.file).push(finding);
|
|
114
|
+
}
|
|
115
|
+
for (const [file, fileFindings] of byFile) {
|
|
116
|
+
lines.push("");
|
|
117
|
+
lines.push(chalk.bold.white(file));
|
|
118
|
+
for (const finding of fileFindings) {
|
|
119
|
+
const severity = formatSeverity(finding.severity);
|
|
120
|
+
const location = chalk.dim(`${finding.line}:${finding.column ?? 0}`);
|
|
121
|
+
const message = finding.message;
|
|
122
|
+
const sourceTag = formatSource(finding.source, finding.ruleId);
|
|
123
|
+
lines.push(` ${location} ${severity} ${message} ${sourceTag}`);
|
|
124
|
+
if (finding.fix && finding.automated) {
|
|
125
|
+
lines.push(chalk.dim(" \u26A1 Auto-fixable"));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return lines.join("\n");
|
|
130
|
+
}
|
|
131
|
+
function formatSource(source, ruleId) {
|
|
132
|
+
if (ruleId.startsWith("rule:")) {
|
|
133
|
+
const projectRuleId = ruleId.slice(5);
|
|
134
|
+
return chalk.green(`[rule:${projectRuleId}]`);
|
|
135
|
+
} else if (ruleId.startsWith("llm-lite:")) {
|
|
136
|
+
const category = ruleId.slice(9);
|
|
137
|
+
return chalk.dim(`[llm:${category}]`);
|
|
138
|
+
} else {
|
|
139
|
+
return chalk.dim(`[${ruleId}]`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function formatSeverity(severity) {
|
|
143
|
+
switch (severity) {
|
|
144
|
+
case "blocker":
|
|
145
|
+
return chalk.bgRed.white(" BLOCKER ");
|
|
146
|
+
case "high":
|
|
147
|
+
return chalk.red("error");
|
|
148
|
+
case "medium":
|
|
149
|
+
return chalk.yellow("warning");
|
|
150
|
+
case "low":
|
|
151
|
+
return chalk.blue("info");
|
|
152
|
+
case "info":
|
|
153
|
+
return chalk.gray("hint");
|
|
154
|
+
default:
|
|
155
|
+
return severity;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
function formatSummary(result) {
|
|
159
|
+
const lines = [];
|
|
160
|
+
lines.push("");
|
|
161
|
+
lines.push(chalk.bold("Summary"));
|
|
162
|
+
lines.push("\u2500".repeat(50));
|
|
163
|
+
const counts = {
|
|
164
|
+
blocker: 0,
|
|
165
|
+
high: 0,
|
|
166
|
+
medium: 0,
|
|
167
|
+
low: 0,
|
|
168
|
+
info: 0
|
|
169
|
+
};
|
|
170
|
+
const sourceCounts = {
|
|
171
|
+
rule: 0,
|
|
172
|
+
llm: 0
|
|
173
|
+
};
|
|
174
|
+
for (const finding of result.findings) {
|
|
175
|
+
counts[finding.severity]++;
|
|
176
|
+
if (finding.ruleId.startsWith("rule:")) {
|
|
177
|
+
sourceCounts.rule++;
|
|
178
|
+
} else {
|
|
179
|
+
sourceCounts.llm++;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (counts.blocker > 0) {
|
|
183
|
+
lines.push(chalk.red(` Blocker: ${counts.blocker}`));
|
|
184
|
+
}
|
|
185
|
+
if (counts.high > 0) {
|
|
186
|
+
lines.push(chalk.red(` High: ${counts.high}`));
|
|
187
|
+
}
|
|
188
|
+
if (counts.medium > 0) {
|
|
189
|
+
lines.push(chalk.yellow(` Medium: ${counts.medium}`));
|
|
190
|
+
}
|
|
191
|
+
if (counts.low > 0) {
|
|
192
|
+
lines.push(chalk.blue(` Low: ${counts.low}`));
|
|
193
|
+
}
|
|
194
|
+
if (counts.info > 0) {
|
|
195
|
+
lines.push(chalk.gray(` Info: ${counts.info}`));
|
|
196
|
+
}
|
|
197
|
+
lines.push("");
|
|
198
|
+
lines.push(` Total: ${result.findings.length}`);
|
|
199
|
+
lines.push(` Files: ${result.metadata.analyzedFiles}`);
|
|
200
|
+
lines.push(` Time: ${result.metadata.durationMs}ms`);
|
|
201
|
+
lines.push(` Engines: ${result.metadata.engines.join(", ")}`);
|
|
202
|
+
if (result.findings.length > 0) {
|
|
203
|
+
lines.push("");
|
|
204
|
+
lines.push(chalk.bold(" Finding Sources:"));
|
|
205
|
+
if (sourceCounts.rule > 0) {
|
|
206
|
+
lines.push(chalk.green(` From rules: ${sourceCounts.rule}`));
|
|
207
|
+
}
|
|
208
|
+
if (sourceCounts.llm > 0) {
|
|
209
|
+
lines.push(chalk.dim(` LLM ad-hoc: ${sourceCounts.llm}`));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return lines.join("\n");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export { formatFindings, formatSummary, manifest_default as manifest };
|
|
216
|
+
//# sourceMappingURL=index.js.map
|
|
217
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/flags.ts","../src/manifest.ts","../src/formatters/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Command flags definitions for AI Review plugin\n */\n\nimport { defineFlags } from '@kb-labs/sdk';\n\n/**\n * Flags for review:run command\n */\nexport const runFlags = defineFlags({\n mode: {\n type: 'string',\n description: 'Review mode: heuristic (CI), full (local), llm (deep)',\n choices: ['heuristic', 'full', 'llm'],\n default: 'heuristic',\n },\n scope: {\n type: 'string',\n description: 'File scope: all, changed (vs main), staged',\n choices: ['all', 'changed', 'staged'],\n default: 'changed',\n },\n repos: {\n type: 'array',\n description: 'Repository scope (submodule names to include, e.g., --repos kb-labs-core kb-labs-cli)',\n },\n task: {\n type: 'string',\n description: 'Task context - describe what the changes are trying to achieve',\n },\n preset: {\n type: 'string',\n description: 'Preset ID (e.g., typescript-strict)',\n },\n files: {\n type: 'array',\n description: 'File patterns to analyze',\n },\n eslintConfig: {\n type: 'string',\n description: 'ESLint config file path',\n },\n json: {\n type: 'boolean',\n description: 'Output JSON',\n default: false,\n },\n});\n","/**\n * KB Labs AI Review Plugin - Manifest V3\n */\n\nimport {\n defineCommandFlags,\n combinePermissions,\n gitWorkflowPreset,\n kbPlatformPreset,\n} from '@kb-labs/sdk';\nimport { runFlags } from './commands/flags.js';\n\n/**\n * Build permissions:\n * - gitWorkflow: HOME, USER, GIT_* for git operations\n * - kbPlatform: KB_* env vars and .kb/ directory\n * - Custom: file read access, LLM, cache, analytics\n */\nconst pluginPermissions = combinePermissions()\n .with(gitWorkflowPreset)\n .with(kbPlatformPreset)\n .withFs({\n mode: 'read',\n allow: ['**/*'],\n })\n .withPlatform({\n llm: true, // LLM for full/llm modes\n cache: ['review:'], // Cache namespace prefix\n analytics: true, // Track review events\n })\n .withQuotas({\n timeoutMs: 600000, // 10 min for deep analysis\n memoryMb: 512,\n })\n .build();\n\nexport const manifest = {\n schema: 'kb.plugin/3',\n id: '@kb-labs/review',\n version: '0.1.0',\n\n display: {\n name: 'AI Review',\n description: 'AI-powered code review with heuristic engines (ESLint, Ruff, etc.) and LLM analyzers.',\n tags: ['code-review', 'linting', 'ai', 'quality'],\n },\n\n // Configuration section in kb.config.json\n configSection: 'review',\n\n // Platform requirements\n platform: {\n requires: ['storage', 'cache'],\n optional: ['llm', 'analytics', 'logger'],\n },\n\n // CLI commands\n cli: {\n commands: [\n {\n id: 'review:run',\n group: 'review',\n describe: 'Run code review analysis',\n longDescription:\n 'Analyzes code using heuristic engines (ESLint, Ruff, etc.) and optionally LLM analyzers. ' +\n 'Supports three modes: heuristic (CI, fast), full (local, comprehensive), llm (deep analysis).',\n\n handler: './commands/run.js#default',\n handlerPath: './commands/run.js',\n\n flags: defineCommandFlags(runFlags.schema),\n\n examples: [\n 'kb review run',\n 'kb review run --mode=full',\n 'kb review run --scope=staged',\n 'kb review run --preset=typescript-strict',\n 'kb review run --files=\"src/**/*.ts\"',\n 'kb review run --json',\n ],\n },\n ],\n },\n\n // Permissions\n permissions: pluginPermissions,\n};\n\nexport default manifest;\n","/**\n * @module @kb-labs/review-cli/formatters\n * Output formatters for code review results.\n */\n\nimport type { ReviewFinding, ReviewResult } from '@kb-labs/review-contracts';\nimport chalk from 'chalk';\n\n/**\n * Format findings as text.\n */\nexport function formatFindings(findings: ReviewFinding[]): string {\n if (findings.length === 0) {\n return chalk.green('✓ No issues found!');\n }\n\n const lines: string[] = [];\n\n // Group by severity\n const byFile = new Map<string, ReviewFinding[]>();\n\n for (const finding of findings) {\n if (!byFile.has(finding.file)) {\n byFile.set(finding.file, []);\n }\n byFile.get(finding.file)!.push(finding);\n }\n\n // Format by file\n for (const [file, fileFindings] of byFile) {\n lines.push('');\n lines.push(chalk.bold.white(file));\n\n for (const finding of fileFindings) {\n const severity = formatSeverity(finding.severity);\n const location = chalk.dim(`${finding.line}:${finding.column ?? 0}`);\n const message = finding.message;\n const sourceTag = formatSource(finding.source, finding.ruleId);\n\n lines.push(` ${location} ${severity} ${message} ${sourceTag}`);\n\n // Show fix hint if available\n if (finding.fix && finding.automated) {\n lines.push(chalk.dim(' ⚡ Auto-fixable'));\n }\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format source tag (rule vs llm).\n * Shows whether finding came from a project rule or LLM ad-hoc analysis.\n */\nfunction formatSource(source: string, ruleId: string): string {\n // Parse ruleId format: \"rule:category/name\" or \"llm-lite:category\"\n if (ruleId.startsWith('rule:')) {\n // Project rule - show rule ID with green highlight\n const projectRuleId = ruleId.slice(5); // Remove \"rule:\" prefix\n return chalk.green(`[rule:${projectRuleId}]`);\n } else if (ruleId.startsWith('llm-lite:')) {\n // LLM ad-hoc finding - show category with dim styling\n const category = ruleId.slice(9); // Remove \"llm-lite:\" prefix\n return chalk.dim(`[llm:${category}]`);\n } else {\n // Legacy format - just show the ruleId\n return chalk.dim(`[${ruleId}]`);\n }\n}\n\n/**\n * Format severity with color.\n */\nfunction formatSeverity(severity: string): string {\n switch (severity) {\n case 'blocker':\n return chalk.bgRed.white(' BLOCKER ');\n case 'high':\n return chalk.red('error');\n case 'medium':\n return chalk.yellow('warning');\n case 'low':\n return chalk.blue('info');\n case 'info':\n return chalk.gray('hint');\n default:\n return severity;\n }\n}\n\n/**\n * Format summary statistics.\n */\nexport function formatSummary(result: ReviewResult): string {\n const lines: string[] = [];\n\n lines.push('');\n lines.push(chalk.bold('Summary'));\n lines.push('─'.repeat(50));\n\n // Count by severity\n const counts = {\n blocker: 0,\n high: 0,\n medium: 0,\n low: 0,\n info: 0,\n };\n\n // Count by source (rule vs llm)\n const sourceCounts = {\n rule: 0,\n llm: 0,\n };\n\n for (const finding of result.findings) {\n counts[finding.severity]++;\n\n // Count by source based on ruleId format\n if (finding.ruleId.startsWith('rule:')) {\n sourceCounts.rule++;\n } else {\n sourceCounts.llm++;\n }\n }\n\n // Format counts\n if (counts.blocker > 0) {\n lines.push(chalk.red(` Blocker: ${counts.blocker}`));\n }\n if (counts.high > 0) {\n lines.push(chalk.red(` High: ${counts.high}`));\n }\n if (counts.medium > 0) {\n lines.push(chalk.yellow(` Medium: ${counts.medium}`));\n }\n if (counts.low > 0) {\n lines.push(chalk.blue(` Low: ${counts.low}`));\n }\n if (counts.info > 0) {\n lines.push(chalk.gray(` Info: ${counts.info}`));\n }\n\n lines.push('');\n lines.push(` Total: ${result.findings.length}`);\n lines.push(` Files: ${result.metadata.analyzedFiles}`);\n lines.push(` Time: ${result.metadata.durationMs}ms`);\n lines.push(` Engines: ${result.metadata.engines.join(', ')}`);\n\n // Show source breakdown if there are findings\n if (result.findings.length > 0) {\n lines.push('');\n lines.push(chalk.bold(' Finding Sources:'));\n if (sourceCounts.rule > 0) {\n lines.push(chalk.green(` From rules: ${sourceCounts.rule}`));\n }\n if (sourceCounts.llm > 0) {\n lines.push(chalk.dim(` LLM ad-hoc: ${sourceCounts.llm}`));\n }\n }\n\n return lines.join('\\n');\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as _kb_labs_perm_presets from '@kb-labs/perm-presets';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* KB Labs AI Review Plugin - Manifest V3
|
|
5
|
+
*/
|
|
6
|
+
declare const manifest: {
|
|
7
|
+
schema: string;
|
|
8
|
+
id: string;
|
|
9
|
+
version: string;
|
|
10
|
+
display: {
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
tags: string[];
|
|
14
|
+
};
|
|
15
|
+
configSection: string;
|
|
16
|
+
platform: {
|
|
17
|
+
requires: string[];
|
|
18
|
+
optional: string[];
|
|
19
|
+
};
|
|
20
|
+
cli: {
|
|
21
|
+
commands: {
|
|
22
|
+
id: string;
|
|
23
|
+
group: string;
|
|
24
|
+
describe: string;
|
|
25
|
+
longDescription: string;
|
|
26
|
+
handler: string;
|
|
27
|
+
handlerPath: string;
|
|
28
|
+
flags: {
|
|
29
|
+
name: string;
|
|
30
|
+
type: "string" | "boolean" | "number" | "array";
|
|
31
|
+
alias?: string;
|
|
32
|
+
default?: unknown;
|
|
33
|
+
description?: string;
|
|
34
|
+
choices?: string[];
|
|
35
|
+
required?: boolean;
|
|
36
|
+
}[];
|
|
37
|
+
examples: string[];
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
40
|
+
permissions: _kb_labs_perm_presets.RuntimePermissionSpec;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export { manifest as default, manifest };
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineFlags, combinePermissions, gitWorkflowPreset, kbPlatformPreset, defineCommandFlags } from '@kb-labs/sdk';
|
|
2
|
+
|
|
3
|
+
// src/manifest.ts
|
|
4
|
+
var runFlags = defineFlags({
|
|
5
|
+
mode: {
|
|
6
|
+
type: "string",
|
|
7
|
+
description: "Review mode: heuristic (CI), full (local), llm (deep)",
|
|
8
|
+
choices: ["heuristic", "full", "llm"],
|
|
9
|
+
default: "heuristic"
|
|
10
|
+
},
|
|
11
|
+
scope: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: "File scope: all, changed (vs main), staged",
|
|
14
|
+
choices: ["all", "changed", "staged"],
|
|
15
|
+
default: "changed"
|
|
16
|
+
},
|
|
17
|
+
repos: {
|
|
18
|
+
type: "array",
|
|
19
|
+
description: "Repository scope (submodule names to include, e.g., --repos kb-labs-core kb-labs-cli)"
|
|
20
|
+
},
|
|
21
|
+
task: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Task context - describe what the changes are trying to achieve"
|
|
24
|
+
},
|
|
25
|
+
preset: {
|
|
26
|
+
type: "string",
|
|
27
|
+
description: "Preset ID (e.g., typescript-strict)"
|
|
28
|
+
},
|
|
29
|
+
files: {
|
|
30
|
+
type: "array",
|
|
31
|
+
description: "File patterns to analyze"
|
|
32
|
+
},
|
|
33
|
+
eslintConfig: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: "ESLint config file path"
|
|
36
|
+
},
|
|
37
|
+
json: {
|
|
38
|
+
type: "boolean",
|
|
39
|
+
description: "Output JSON",
|
|
40
|
+
default: false
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// src/manifest.ts
|
|
45
|
+
var pluginPermissions = combinePermissions().with(gitWorkflowPreset).with(kbPlatformPreset).withFs({
|
|
46
|
+
mode: "read",
|
|
47
|
+
allow: ["**/*"]
|
|
48
|
+
}).withPlatform({
|
|
49
|
+
llm: true,
|
|
50
|
+
// LLM for full/llm modes
|
|
51
|
+
cache: ["review:"],
|
|
52
|
+
// Cache namespace prefix
|
|
53
|
+
analytics: true
|
|
54
|
+
// Track review events
|
|
55
|
+
}).withQuotas({
|
|
56
|
+
timeoutMs: 6e5,
|
|
57
|
+
// 10 min for deep analysis
|
|
58
|
+
memoryMb: 512
|
|
59
|
+
}).build();
|
|
60
|
+
var manifest = {
|
|
61
|
+
schema: "kb.plugin/3",
|
|
62
|
+
id: "@kb-labs/review",
|
|
63
|
+
version: "0.1.0",
|
|
64
|
+
display: {
|
|
65
|
+
name: "AI Review",
|
|
66
|
+
description: "AI-powered code review with heuristic engines (ESLint, Ruff, etc.) and LLM analyzers.",
|
|
67
|
+
tags: ["code-review", "linting", "ai", "quality"]
|
|
68
|
+
},
|
|
69
|
+
// Configuration section in kb.config.json
|
|
70
|
+
configSection: "review",
|
|
71
|
+
// Platform requirements
|
|
72
|
+
platform: {
|
|
73
|
+
requires: ["storage", "cache"],
|
|
74
|
+
optional: ["llm", "analytics", "logger"]
|
|
75
|
+
},
|
|
76
|
+
// CLI commands
|
|
77
|
+
cli: {
|
|
78
|
+
commands: [
|
|
79
|
+
{
|
|
80
|
+
id: "review:run",
|
|
81
|
+
group: "review",
|
|
82
|
+
describe: "Run code review analysis",
|
|
83
|
+
longDescription: "Analyzes code using heuristic engines (ESLint, Ruff, etc.) and optionally LLM analyzers. Supports three modes: heuristic (CI, fast), full (local, comprehensive), llm (deep analysis).",
|
|
84
|
+
handler: "./commands/run.js#default",
|
|
85
|
+
handlerPath: "./commands/run.js",
|
|
86
|
+
flags: defineCommandFlags(runFlags.schema),
|
|
87
|
+
examples: [
|
|
88
|
+
"kb review run",
|
|
89
|
+
"kb review run --mode=full",
|
|
90
|
+
"kb review run --scope=staged",
|
|
91
|
+
"kb review run --preset=typescript-strict",
|
|
92
|
+
'kb review run --files="src/**/*.ts"',
|
|
93
|
+
"kb review run --json"
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
},
|
|
98
|
+
// Permissions
|
|
99
|
+
permissions: pluginPermissions
|
|
100
|
+
};
|
|
101
|
+
var manifest_default = manifest;
|
|
102
|
+
|
|
103
|
+
export { manifest_default as default, manifest };
|
|
104
|
+
//# sourceMappingURL=manifest.js.map
|
|
105
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/flags.ts","../src/manifest.ts"],"names":[],"mappings":"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","file":"manifest.js","sourcesContent":["/**\n * Command flags definitions for AI Review plugin\n */\n\nimport { defineFlags } from '@kb-labs/sdk';\n\n/**\n * Flags for review:run command\n */\nexport const runFlags = defineFlags({\n mode: {\n type: 'string',\n description: 'Review mode: heuristic (CI), full (local), llm (deep)',\n choices: ['heuristic', 'full', 'llm'],\n default: 'heuristic',\n },\n scope: {\n type: 'string',\n description: 'File scope: all, changed (vs main), staged',\n choices: ['all', 'changed', 'staged'],\n default: 'changed',\n },\n repos: {\n type: 'array',\n description: 'Repository scope (submodule names to include, e.g., --repos kb-labs-core kb-labs-cli)',\n },\n task: {\n type: 'string',\n description: 'Task context - describe what the changes are trying to achieve',\n },\n preset: {\n type: 'string',\n description: 'Preset ID (e.g., typescript-strict)',\n },\n files: {\n type: 'array',\n description: 'File patterns to analyze',\n },\n eslintConfig: {\n type: 'string',\n description: 'ESLint config file path',\n },\n json: {\n type: 'boolean',\n description: 'Output JSON',\n default: false,\n },\n});\n","/**\n * KB Labs AI Review Plugin - Manifest V3\n */\n\nimport {\n defineCommandFlags,\n combinePermissions,\n gitWorkflowPreset,\n kbPlatformPreset,\n} from '@kb-labs/sdk';\nimport { runFlags } from './commands/flags.js';\n\n/**\n * Build permissions:\n * - gitWorkflow: HOME, USER, GIT_* for git operations\n * - kbPlatform: KB_* env vars and .kb/ directory\n * - Custom: file read access, LLM, cache, analytics\n */\nconst pluginPermissions = combinePermissions()\n .with(gitWorkflowPreset)\n .with(kbPlatformPreset)\n .withFs({\n mode: 'read',\n allow: ['**/*'],\n })\n .withPlatform({\n llm: true, // LLM for full/llm modes\n cache: ['review:'], // Cache namespace prefix\n analytics: true, // Track review events\n })\n .withQuotas({\n timeoutMs: 600000, // 10 min for deep analysis\n memoryMb: 512,\n })\n .build();\n\nexport const manifest = {\n schema: 'kb.plugin/3',\n id: '@kb-labs/review',\n version: '0.1.0',\n\n display: {\n name: 'AI Review',\n description: 'AI-powered code review with heuristic engines (ESLint, Ruff, etc.) and LLM analyzers.',\n tags: ['code-review', 'linting', 'ai', 'quality'],\n },\n\n // Configuration section in kb.config.json\n configSection: 'review',\n\n // Platform requirements\n platform: {\n requires: ['storage', 'cache'],\n optional: ['llm', 'analytics', 'logger'],\n },\n\n // CLI commands\n cli: {\n commands: [\n {\n id: 'review:run',\n group: 'review',\n describe: 'Run code review analysis',\n longDescription:\n 'Analyzes code using heuristic engines (ESLint, Ruff, etc.) and optionally LLM analyzers. ' +\n 'Supports three modes: heuristic (CI, fast), full (local, comprehensive), llm (deep analysis).',\n\n handler: './commands/run.js#default',\n handlerPath: './commands/run.js',\n\n flags: defineCommandFlags(runFlags.schema),\n\n examples: [\n 'kb review run',\n 'kb review run --mode=full',\n 'kb review run --scope=staged',\n 'kb review run --preset=typescript-strict',\n 'kb review run --files=\"src/**/*.ts\"',\n 'kb review run --json',\n ],\n },\n ],\n },\n\n // Permissions\n permissions: pluginPermissions,\n};\n\nexport default manifest;\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/review-entry",
|
|
3
|
+
"version": "2.14.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"import": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"kb": {
|
|
12
|
+
"manifest": "./dist/manifest.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"chalk": "^5.3.0",
|
|
16
|
+
"@kb-labs/review-core": "2.14.0",
|
|
17
|
+
"@kb-labs/sdk": "1.6.6",
|
|
18
|
+
"@kb-labs/review-contracts": "2.14.0",
|
|
19
|
+
"@kb-labs/shared-cli-ui": "2.14.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^24.3.3",
|
|
23
|
+
"tsup": "^8.5.0",
|
|
24
|
+
"typescript": "^5.6.3",
|
|
25
|
+
"rimraf": "^6.0.1",
|
|
26
|
+
"vitest": "^3.2.4",
|
|
27
|
+
"@kb-labs/devkit": "2.14.0"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20.0.0",
|
|
31
|
+
"pnpm": ">=9.0.0"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"clean": "rimraf dist",
|
|
39
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
40
|
+
"lint": "eslint src --ext .ts",
|
|
41
|
+
"lint:fix": "eslint . --fix",
|
|
42
|
+
"type-check": "tsc --noEmit",
|
|
43
|
+
"test": "vitest run --passWithNoTests",
|
|
44
|
+
"test:watch": "vitest"
|
|
45
|
+
}
|
|
46
|
+
}
|