specvector 0.3.1 → 0.3.3
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/package.json +1 -1
- package/src/config/index.ts +5 -5
- package/src/index.ts +2 -2
- package/src/mcp/mcp-client.ts +3 -2
- package/src/review/engine.ts +26 -4
- package/src/utils/redact.ts +125 -0
package/package.json
CHANGED
package/src/config/index.ts
CHANGED
|
@@ -280,19 +280,19 @@ export function getStrictnessModifier(strictness: SpecVectorConfig["strictness"]
|
|
|
280
280
|
switch (strictness) {
|
|
281
281
|
case "strict":
|
|
282
282
|
return `Be VERY strict. Flag any potential issue, no matter how minor.
|
|
283
|
-
Focus on: security vulnerabilities,
|
|
283
|
+
Focus on: security vulnerabilities, logic errors, boundary conditions, data flow issues, performance problems, error handling, edge cases.
|
|
284
284
|
Do not approve code that could be improved.`;
|
|
285
285
|
|
|
286
286
|
case "lenient":
|
|
287
287
|
return `Be lenient and focus only on critical issues.
|
|
288
|
-
Only flag: bugs that would cause runtime errors, security vulnerabilities, obvious mistakes.
|
|
289
|
-
Skip: style issues, minor improvements, suggestions.
|
|
288
|
+
Only flag: bugs that would cause runtime errors, logic errors causing crashes or data corruption, security vulnerabilities, obvious mistakes.
|
|
289
|
+
Skip: style issues, minor improvements, suggestions, theoretical problems.
|
|
290
290
|
Approve unless there are blocking issues.`;
|
|
291
291
|
|
|
292
292
|
case "normal":
|
|
293
293
|
default:
|
|
294
294
|
return `Use balanced judgement. Flag important issues but don't be nitpicky.
|
|
295
|
-
Focus on: bugs, security, performance, maintainability.
|
|
296
|
-
Skip: purely stylistic preferences.`;
|
|
295
|
+
Focus on: logic errors that would cause real bugs in production, security, performance, maintainability.
|
|
296
|
+
Skip: purely stylistic preferences, theoretical issues you haven't verified.`;
|
|
297
297
|
}
|
|
298
298
|
}
|
package/src/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { postPRComment } from "./github/comment";
|
|
|
9
9
|
import { parseDiff, getDiffSummary } from "./review/diff-parser";
|
|
10
10
|
import { formatReviewComment, formatReviewSummary } from "./review/formatter";
|
|
11
11
|
import { runReview } from "./review/engine";
|
|
12
|
+
import { redactError } from "./utils/redact";
|
|
12
13
|
|
|
13
14
|
const VERSION = "0.1.0";
|
|
14
15
|
|
|
@@ -132,7 +133,6 @@ async function handleReview(args: string[]): Promise<void> {
|
|
|
132
133
|
displayAndPostReview(mockReview, prNumber, dryRun);
|
|
133
134
|
} else {
|
|
134
135
|
console.log("🤖 Running AI review...");
|
|
135
|
-
console.log(" (this may take 15-30 seconds)");
|
|
136
136
|
console.log("");
|
|
137
137
|
|
|
138
138
|
// Get branch name for Linear context
|
|
@@ -242,6 +242,6 @@ function generateMockReview(filesReviewed: number): import("./types/review").Rev
|
|
|
242
242
|
|
|
243
243
|
// Run the CLI
|
|
244
244
|
main().catch((error) => {
|
|
245
|
-
console.error("Fatal error:", error);
|
|
245
|
+
console.error("Fatal error:", redactError(error));
|
|
246
246
|
process.exit(1);
|
|
247
247
|
});
|
package/src/mcp/mcp-client.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
MCPToolsListResult,
|
|
18
18
|
MCPToolCallResult,
|
|
19
19
|
} from "./types";
|
|
20
|
+
import { redactSecrets, buildSafeEnv } from "../utils/redact";
|
|
20
21
|
|
|
21
22
|
// ============================================================================
|
|
22
23
|
// Constants
|
|
@@ -73,7 +74,7 @@ export async function createMCPClient(
|
|
|
73
74
|
let proc: Subprocess;
|
|
74
75
|
try {
|
|
75
76
|
proc = spawn([config.command, ...config.args], {
|
|
76
|
-
env:
|
|
77
|
+
env: buildSafeEnv(config.env as Record<string, string> | undefined),
|
|
77
78
|
stdin: "pipe",
|
|
78
79
|
stdout: "pipe",
|
|
79
80
|
stderr: "pipe",
|
|
@@ -351,7 +352,7 @@ function startReadingStderr(state: MCPClientState): void {
|
|
|
351
352
|
const text = decoder.decode(value, { stream: true });
|
|
352
353
|
// Log stderr for debugging (could be made configurable)
|
|
353
354
|
if (text.trim()) {
|
|
354
|
-
console.error(`[MCP:${state.config.name}] ${text.trim()}`);
|
|
355
|
+
console.error(`[MCP:${state.config.name}] ${redactSecrets(text.trim())}`);
|
|
355
356
|
}
|
|
356
357
|
}
|
|
357
358
|
} catch {
|
package/src/review/engine.ts
CHANGED
|
@@ -172,7 +172,7 @@ export async function runReview(
|
|
|
172
172
|
/**
|
|
173
173
|
* System prompt for the code review agent.
|
|
174
174
|
*/
|
|
175
|
-
const REVIEW_SYSTEM_PROMPT = `You are a pragmatic code reviewer. Your job is to catch REAL problems, not nitpick.
|
|
175
|
+
export const REVIEW_SYSTEM_PROMPT = `You are a pragmatic code reviewer. Your job is to catch REAL problems, not nitpick.
|
|
176
176
|
|
|
177
177
|
## Tools Available
|
|
178
178
|
- read_file: Read source files to understand context
|
|
@@ -181,21 +181,43 @@ const REVIEW_SYSTEM_PROMPT = `You are a pragmatic code reviewer. Your job is to
|
|
|
181
181
|
- get_outline: Get functions/classes in a file (fast overview)
|
|
182
182
|
- find_symbol: Find where a function or class is defined
|
|
183
183
|
|
|
184
|
+
## Tool Use Strategy
|
|
185
|
+
Before flagging any issue, you MUST verify your understanding:
|
|
186
|
+
1. **Read the full file** for any function being changed — don't judge from diff alone
|
|
187
|
+
2. **Use find_symbol** to trace how changed functions are called by other code
|
|
188
|
+
3. **Use grep** to find other usages of modified functions or variables
|
|
189
|
+
4. **Only flag an issue if you have verified it** by reading the surrounding context
|
|
190
|
+
|
|
184
191
|
## What to Look For (in priority order)
|
|
185
192
|
1. **CRITICAL**: Security vulnerabilities, data loss, crashes
|
|
186
193
|
2. **HIGH**: Bugs that WILL break functionality in production
|
|
187
194
|
3. **MEDIUM**: Significant code quality issues (not style nits)
|
|
188
195
|
|
|
196
|
+
## Business Logic Patterns to Detect
|
|
197
|
+
Focus on real logic errors that cause incorrect behavior:
|
|
198
|
+
- **Off-by-one errors**: Wrong boundary conditions, < vs <=, array index issues
|
|
199
|
+
- **Null/undefined handling**: Missing null checks on values that can be null
|
|
200
|
+
- **Race conditions**: Shared state without synchronization, async ordering bugs
|
|
201
|
+
- **Incorrect boolean logic**: Inverted conditions, wrong operator (AND vs OR)
|
|
202
|
+
- **Missing error paths**: Happy-path-only code that ignores failure cases in data flows
|
|
203
|
+
- **Wrong operator**: Using = instead of ==, + instead of -, incorrect comparisons
|
|
204
|
+
- **State management bugs**: Mutating shared state, stale closures, incorrect resets
|
|
205
|
+
- **Type coercion issues**: Implicit conversions causing unexpected behavior
|
|
206
|
+
|
|
189
207
|
## What NOT to Flag
|
|
190
208
|
- Style preferences or "I would do it differently"
|
|
191
209
|
- Theoretical performance issues without evidence
|
|
192
210
|
- Missing edge case tests for working code
|
|
193
211
|
- "Could be refactored" suggestions
|
|
194
212
|
- Code that works but isn't perfect
|
|
213
|
+
- Naming convention preferences
|
|
214
|
+
- Comment formatting or missing comments
|
|
215
|
+
- Import ordering or grouping
|
|
195
216
|
|
|
196
217
|
## Key Principle
|
|
197
218
|
Most PRs should have 0-2 findings. If you're finding 5+ issues, you're being too picky.
|
|
198
219
|
Only flag issues you'd actually block a PR for in a real code review.
|
|
220
|
+
Verify every finding with tool use before reporting it.
|
|
199
221
|
|
|
200
222
|
## Response Format
|
|
201
223
|
SUMMARY: [1-2 sentences - is this code ready to merge?]
|
|
@@ -228,15 +250,15 @@ ${diff.length > 15000 ? "\n(diff truncated, use tools to read full files if need
|
|
|
228
250
|
|
|
229
251
|
## Instructions
|
|
230
252
|
1. First, understand what the changes are doing
|
|
231
|
-
2. Use tools to explore related code
|
|
232
|
-
3.
|
|
253
|
+
2. Use tools to explore related code — read full files, trace call chains, check usages
|
|
254
|
+
3. For each potential issue, verify it by reading surrounding context before flagging
|
|
233
255
|
4. Provide your review in the specified format`;
|
|
234
256
|
}
|
|
235
257
|
|
|
236
258
|
/**
|
|
237
259
|
* Parse the agent's response into structured findings.
|
|
238
260
|
*/
|
|
239
|
-
function parseReviewResponse(response: string, diffSummary: string, contextSources: ContextSource[] = []): ReviewResult {
|
|
261
|
+
export function parseReviewResponse(response: string, diffSummary: string, contextSources: ContextSource[] = []): ReviewResult {
|
|
240
262
|
const findings: ReviewFinding[] = [];
|
|
241
263
|
|
|
242
264
|
// Extract summary
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret redaction utilities.
|
|
3
|
+
*
|
|
4
|
+
* Prevents API keys, tokens, and other sensitive values from appearing
|
|
5
|
+
* in logs, error messages, or PR comments.
|
|
6
|
+
*
|
|
7
|
+
* Story 7.4: Security Enforcement (NFR4, NFR5)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Environment variable names that contain secrets.
|
|
12
|
+
* Values from these variables will be redacted from any output.
|
|
13
|
+
*/
|
|
14
|
+
const SECRET_ENV_VARS = [
|
|
15
|
+
"OPENROUTER_API_KEY",
|
|
16
|
+
"LINEAR_API_TOKEN",
|
|
17
|
+
"LINEAR_API_KEY",
|
|
18
|
+
"GITHUB_TOKEN",
|
|
19
|
+
"GH_TOKEN",
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Environment variable names that are safe to pass to MCP subprocesses.
|
|
24
|
+
* Only these variables (plus explicitly provided config.env) are forwarded.
|
|
25
|
+
*/
|
|
26
|
+
export const MCP_SAFE_ENV_VARS = [
|
|
27
|
+
"PATH",
|
|
28
|
+
"HOME",
|
|
29
|
+
"USER",
|
|
30
|
+
"SHELL",
|
|
31
|
+
"TERM",
|
|
32
|
+
"LANG",
|
|
33
|
+
"LC_ALL",
|
|
34
|
+
"NODE_ENV",
|
|
35
|
+
"TMPDIR",
|
|
36
|
+
"XDG_RUNTIME_DIR",
|
|
37
|
+
// Node/Bun runtime
|
|
38
|
+
"NODE_PATH",
|
|
39
|
+
"BUN_INSTALL",
|
|
40
|
+
// npm/npx needs these to find packages
|
|
41
|
+
"npm_config_prefix",
|
|
42
|
+
"npm_config_cache",
|
|
43
|
+
"NVM_DIR",
|
|
44
|
+
"NVM_BIN",
|
|
45
|
+
"VOLTA_HOME",
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Redact known secret values from a string.
|
|
50
|
+
*
|
|
51
|
+
* Scans the string for any values that match current environment variable
|
|
52
|
+
* secrets and replaces them with "[REDACTED]".
|
|
53
|
+
*
|
|
54
|
+
* @param text - The string to redact secrets from
|
|
55
|
+
* @returns The redacted string
|
|
56
|
+
*/
|
|
57
|
+
export function redactSecrets(text: string): string {
|
|
58
|
+
let redacted = text;
|
|
59
|
+
|
|
60
|
+
for (const envVar of SECRET_ENV_VARS) {
|
|
61
|
+
const value = process.env[envVar];
|
|
62
|
+
if (value && value.length >= 4) {
|
|
63
|
+
// Replace all occurrences of the secret value
|
|
64
|
+
redacted = redacted.replaceAll(value, "[REDACTED]");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return redacted;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Safely stringify an error, redacting any secrets.
|
|
73
|
+
*
|
|
74
|
+
* Handles unknown error types (Error, string, object, etc.)
|
|
75
|
+
* and ensures no secret values leak through error messages.
|
|
76
|
+
*
|
|
77
|
+
* @param error - The error to stringify
|
|
78
|
+
* @returns A safe, redacted error string
|
|
79
|
+
*/
|
|
80
|
+
export function redactError(error: unknown): string {
|
|
81
|
+
let message: string;
|
|
82
|
+
|
|
83
|
+
if (error instanceof Error) {
|
|
84
|
+
// Only use message, not stack (stack can contain env var values)
|
|
85
|
+
message = error.message;
|
|
86
|
+
} else if (typeof error === "string") {
|
|
87
|
+
message = error;
|
|
88
|
+
} else {
|
|
89
|
+
try {
|
|
90
|
+
message = JSON.stringify(error) ?? String(error);
|
|
91
|
+
} catch {
|
|
92
|
+
message = String(error);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return redactSecrets(message);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build a filtered environment for MCP subprocesses.
|
|
101
|
+
*
|
|
102
|
+
* Only includes safe, non-secret environment variables from process.env,
|
|
103
|
+
* merged with any explicitly provided config env vars.
|
|
104
|
+
*
|
|
105
|
+
* @param configEnv - Additional env vars from MCP config (may include tokens intentionally)
|
|
106
|
+
* @returns A filtered environment object
|
|
107
|
+
*/
|
|
108
|
+
export function buildSafeEnv(configEnv?: Record<string, string>): Record<string, string> {
|
|
109
|
+
const safeEnv: Record<string, string> = {};
|
|
110
|
+
|
|
111
|
+
// Only copy allowed env vars from process.env
|
|
112
|
+
for (const key of MCP_SAFE_ENV_VARS) {
|
|
113
|
+
const value = process.env[key];
|
|
114
|
+
if (value !== undefined) {
|
|
115
|
+
safeEnv[key] = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Merge explicitly provided config env (these are intentional, e.g., LINEAR_API_KEY)
|
|
120
|
+
if (configEnv) {
|
|
121
|
+
Object.assign(safeEnv, configEnv);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return safeEnv;
|
|
125
|
+
}
|