skannr 0.2.0 → 0.2.2
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/dist/cli.js +86 -0
- package/dist/cli.js.map +1 -1
- package/dist/guard/call-context.d.ts +14 -0
- package/dist/guard/call-context.d.ts.map +1 -0
- package/dist/guard/call-context.js +120 -0
- package/dist/guard/call-context.js.map +1 -0
- package/dist/guard/fix-applier.d.ts +38 -0
- package/dist/guard/fix-applier.d.ts.map +1 -0
- package/dist/guard/fix-applier.js +181 -0
- package/dist/guard/fix-applier.js.map +1 -0
- package/dist/guard/hook-installer.d.ts +19 -0
- package/dist/guard/hook-installer.d.ts.map +1 -0
- package/dist/guard/hook-installer.js +119 -0
- package/dist/guard/hook-installer.js.map +1 -0
- package/dist/guard/index.d.ts +34 -0
- package/dist/guard/index.d.ts.map +1 -0
- package/dist/guard/index.js +152 -0
- package/dist/guard/index.js.map +1 -0
- package/dist/guard/provider.d.ts +12 -0
- package/dist/guard/provider.d.ts.map +1 -0
- package/dist/guard/provider.js +211 -0
- package/dist/guard/provider.js.map +1 -0
- package/dist/guard/review-runner.d.ts +21 -0
- package/dist/guard/review-runner.d.ts.map +1 -0
- package/dist/guard/review-runner.js +85 -0
- package/dist/guard/review-runner.js.map +1 -0
- package/dist/guard/rules-loader.d.ts +10 -0
- package/dist/guard/rules-loader.d.ts.map +1 -0
- package/dist/guard/rules-loader.js +156 -0
- package/dist/guard/rules-loader.js.map +1 -0
- package/dist/guard/schema.d.ts +182 -0
- package/dist/guard/schema.d.ts.map +1 -0
- package/dist/guard/schema.js +53 -0
- package/dist/guard/schema.js.map +1 -0
- package/dist/guard/symbol-diff.d.ts +18 -0
- package/dist/guard/symbol-diff.d.ts.map +1 -0
- package/dist/guard/symbol-diff.js +243 -0
- package/dist/guard/symbol-diff.js.map +1 -0
- package/dist/guard/types.d.ts +62 -0
- package/dist/guard/types.d.ts.map +1 -0
- package/dist/guard/types.js +6 -0
- package/dist/guard/types.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +44 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/scanner.d.ts +0 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +0 -7
- package/dist/scanner.js.map +1 -1
- package/package.json +3 -2
- package/src/cli.ts +101 -0
- package/src/guard/call-context.ts +101 -0
- package/src/guard/fix-applier.ts +172 -0
- package/src/guard/hook-installer.ts +96 -0
- package/src/guard/index.ts +134 -0
- package/src/guard/provider.ts +209 -0
- package/src/guard/review-runner.ts +110 -0
- package/src/guard/rules-loader.ts +131 -0
- package/src/guard/schema.ts +59 -0
- package/src/guard/symbol-diff.ts +227 -0
- package/src/guard/types.ts +68 -0
- package/src/index.ts +2 -2
- package/src/mcp-server.ts +49 -0
- package/src/scanner.ts +0 -7
- package/gemini-extension/GEMINI.md +0 -26
- package/gemini-extension/gemini-extension.json +0 -12
- package/gemini-extension/package.json +0 -14
- package/gemini-extension/src/server.ts +0 -69
- package/gemini-extension/tsconfig.json +0 -14
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Git hook installer for skannr guard.
|
|
4
|
+
* Appends to existing hooks using markers — never overwrites.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.installHook = installHook;
|
|
41
|
+
exports.uninstallHook = uninstallHook;
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
const HOOK_START_MARKER = '# >>> skannr-guard start >>>';
|
|
45
|
+
const HOOK_END_MARKER = '# <<< skannr-guard end <<<';
|
|
46
|
+
const HOOK_CONTENT = `
|
|
47
|
+
${HOOK_START_MARKER}
|
|
48
|
+
# Run skannr guard on staged files before committing.
|
|
49
|
+
# Exit code 1 blocks the commit; exit code 2 is a tool error (non-blocking).
|
|
50
|
+
npx skannr guard
|
|
51
|
+
GUARD_EXIT=$?
|
|
52
|
+
if [ $GUARD_EXIT -eq 1 ]; then
|
|
53
|
+
echo ""
|
|
54
|
+
echo " Commit blocked by skannr guard. Fix violations or use --no-verify to skip."
|
|
55
|
+
echo ""
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
${HOOK_END_MARKER}
|
|
59
|
+
`;
|
|
60
|
+
/**
|
|
61
|
+
* Install the pre-commit hook (append-safe).
|
|
62
|
+
*/
|
|
63
|
+
function installHook(root) {
|
|
64
|
+
const hooksDir = path.join(root, '.git', 'hooks');
|
|
65
|
+
if (!fs.existsSync(path.join(root, '.git'))) {
|
|
66
|
+
return { installed: false, message: 'Not a git repository (no .git directory found).' };
|
|
67
|
+
}
|
|
68
|
+
if (!fs.existsSync(hooksDir)) {
|
|
69
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
const hookPath = path.join(hooksDir, 'pre-commit');
|
|
72
|
+
if (fs.existsSync(hookPath)) {
|
|
73
|
+
const existing = fs.readFileSync(hookPath, 'utf-8');
|
|
74
|
+
// Already installed?
|
|
75
|
+
if (existing.includes(HOOK_START_MARKER)) {
|
|
76
|
+
return { installed: true, message: 'Hook already installed.' };
|
|
77
|
+
}
|
|
78
|
+
// Append to existing hook
|
|
79
|
+
const updated = existing.trimEnd() + '\n\n' + HOOK_CONTENT.trim() + '\n';
|
|
80
|
+
fs.writeFileSync(hookPath, updated, { mode: 0o755 });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Create new hook
|
|
84
|
+
const content = '#!/bin/sh\n' + HOOK_CONTENT;
|
|
85
|
+
fs.writeFileSync(hookPath, content, { mode: 0o755 });
|
|
86
|
+
}
|
|
87
|
+
return { installed: true, message: `Hook installed at ${hookPath}` };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Uninstall the pre-commit hook (remove only our markers).
|
|
91
|
+
*/
|
|
92
|
+
function uninstallHook(root) {
|
|
93
|
+
const hookPath = path.join(root, '.git', 'hooks', 'pre-commit');
|
|
94
|
+
if (!fs.existsSync(hookPath)) {
|
|
95
|
+
return { removed: false, message: 'No pre-commit hook found.' };
|
|
96
|
+
}
|
|
97
|
+
const content = fs.readFileSync(hookPath, 'utf-8');
|
|
98
|
+
if (!content.includes(HOOK_START_MARKER)) {
|
|
99
|
+
return { removed: false, message: 'Skannr guard hook not found in pre-commit.' };
|
|
100
|
+
}
|
|
101
|
+
// Remove everything between (and including) the markers
|
|
102
|
+
const startIdx = content.indexOf(HOOK_START_MARKER);
|
|
103
|
+
const endIdx = content.indexOf(HOOK_END_MARKER);
|
|
104
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
105
|
+
return { removed: false, message: 'Malformed hook markers.' };
|
|
106
|
+
}
|
|
107
|
+
const before = content.slice(0, startIdx).trimEnd();
|
|
108
|
+
const after = content.slice(endIdx + HOOK_END_MARKER.length).trimStart();
|
|
109
|
+
const updated = (before + '\n' + after).trim();
|
|
110
|
+
if (updated === '#!/bin/sh' || updated === '') {
|
|
111
|
+
// Hook is now empty — remove the file
|
|
112
|
+
fs.unlinkSync(hookPath);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
fs.writeFileSync(hookPath, updated + '\n', { mode: 0o755 });
|
|
116
|
+
}
|
|
117
|
+
return { removed: true, message: 'Skannr guard hook removed.' };
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=hook-installer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-installer.js","sourceRoot":"","sources":["../../src/guard/hook-installer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skannr Guard — entry point.
|
|
3
|
+
* Reviews staged changes against team-defined rules using symbol-level analysis.
|
|
4
|
+
*/
|
|
5
|
+
import type { GuardResult } from './types';
|
|
6
|
+
export interface GuardCliOptions {
|
|
7
|
+
root: string;
|
|
8
|
+
fix?: boolean;
|
|
9
|
+
dryRun?: boolean;
|
|
10
|
+
prMode?: boolean;
|
|
11
|
+
diffOnly?: boolean;
|
|
12
|
+
noCache?: boolean;
|
|
13
|
+
json?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run the guard review and return the result + exit code.
|
|
17
|
+
* Exit codes: 0 = pass, 1 = fail (violations), 2 = tool error.
|
|
18
|
+
*/
|
|
19
|
+
export declare function runGuard(options: GuardCliOptions): Promise<{
|
|
20
|
+
result: GuardResult;
|
|
21
|
+
exitCode: number;
|
|
22
|
+
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Format the guard result for terminal output.
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatGuardText(result: GuardResult): string;
|
|
27
|
+
/**
|
|
28
|
+
* Format the guard result as JSON.
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatGuardJson(result: GuardResult): string;
|
|
31
|
+
export { installHook, uninstallHook } from './hook-installer';
|
|
32
|
+
export { loadRules, loadGuardConfig } from './rules-loader';
|
|
33
|
+
export type { GuardResult, Violation } from './types';
|
|
34
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,WAAW,EAAa,MAAM,SAAS,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6C3G;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAsC3D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAS3D;AAGD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC5D,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Skannr Guard — entry point.
|
|
4
|
+
* Reviews staged changes against team-defined rules using symbol-level analysis.
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.loadGuardConfig = exports.loadRules = exports.uninstallHook = exports.installHook = void 0;
|
|
41
|
+
exports.runGuard = runGuard;
|
|
42
|
+
exports.formatGuardText = formatGuardText;
|
|
43
|
+
exports.formatGuardJson = formatGuardJson;
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const review_runner_1 = require("./review-runner");
|
|
46
|
+
const fix_applier_1 = require("./fix-applier");
|
|
47
|
+
/**
|
|
48
|
+
* Run the guard review and return the result + exit code.
|
|
49
|
+
* Exit codes: 0 = pass, 1 = fail (violations), 2 = tool error.
|
|
50
|
+
*/
|
|
51
|
+
async function runGuard(options) {
|
|
52
|
+
const { root, fix = false, dryRun = false, prMode = false, diffOnly = false, noCache = false } = options;
|
|
53
|
+
const absoluteRoot = path.resolve(root);
|
|
54
|
+
// Get PR diff if in --pr-mode
|
|
55
|
+
let diffContent;
|
|
56
|
+
if (prMode) {
|
|
57
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
58
|
+
try {
|
|
59
|
+
diffContent = execSync('git diff origin/main...HEAD', {
|
|
60
|
+
cwd: absoluteRoot,
|
|
61
|
+
encoding: 'utf-8',
|
|
62
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
diffContent = execSync('git diff main...HEAD', {
|
|
67
|
+
cwd: absoluteRoot,
|
|
68
|
+
encoding: 'utf-8',
|
|
69
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const result = await (0, review_runner_1.runGuardReview)({
|
|
74
|
+
root: absoluteRoot,
|
|
75
|
+
diffContent,
|
|
76
|
+
diffOnly,
|
|
77
|
+
noCache,
|
|
78
|
+
});
|
|
79
|
+
// Handle --fix
|
|
80
|
+
if (fix && result.response.violations.length > 0) {
|
|
81
|
+
if (dryRun) {
|
|
82
|
+
const previews = (0, fix_applier_1.previewFixes)(result.response.violations, absoluteRoot);
|
|
83
|
+
const output = (0, fix_applier_1.formatFixResults)(previews, true);
|
|
84
|
+
process.stdout.write(output + '\n');
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const applied = (0, fix_applier_1.applyFixes)(result.response.violations, absoluteRoot);
|
|
88
|
+
const output = (0, fix_applier_1.formatFixResults)(applied, false);
|
|
89
|
+
process.stdout.write(output + '\n');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const exitCode = result.response.status === 'fail' ? 1 : 0;
|
|
93
|
+
return { result, exitCode };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Format the guard result for terminal output.
|
|
97
|
+
*/
|
|
98
|
+
function formatGuardText(result) {
|
|
99
|
+
const lines = [];
|
|
100
|
+
const { response } = result;
|
|
101
|
+
lines.push('');
|
|
102
|
+
lines.push(' Skannr Guard');
|
|
103
|
+
lines.push(' ' + '─'.repeat(50));
|
|
104
|
+
lines.push('');
|
|
105
|
+
lines.push(` Status: ${response.status.toUpperCase()}`);
|
|
106
|
+
lines.push(` ${response.summary}`);
|
|
107
|
+
lines.push(` Reviewed ${result.symbolsReviewed} symbol(s) against ${result.rulesUsed.length} rule(s) in ${result.durationMs}ms`);
|
|
108
|
+
lines.push('');
|
|
109
|
+
if (response.violations.length > 0) {
|
|
110
|
+
lines.push(' Violations:');
|
|
111
|
+
lines.push('');
|
|
112
|
+
for (const v of response.violations) {
|
|
113
|
+
const fixTag = v.fixable ? ' [FIXABLE]' : '';
|
|
114
|
+
const confTag = `(${Math.round(v.confidence * 100)}% confidence)`;
|
|
115
|
+
lines.push(` ${v.severity.toUpperCase()} ${v.file}:${v.line_start} ${confTag}${fixTag}`);
|
|
116
|
+
lines.push(` Rule: ${v.rule_id}`);
|
|
117
|
+
lines.push(` Symbol: ${v.symbol}`);
|
|
118
|
+
lines.push(` ${v.message}`);
|
|
119
|
+
if (v.suggested_fix) {
|
|
120
|
+
lines.push(` Fix: ${v.suggested_fix}`);
|
|
121
|
+
}
|
|
122
|
+
lines.push('');
|
|
123
|
+
}
|
|
124
|
+
const fixableCount = response.violations.filter((v) => v.fixable).length;
|
|
125
|
+
if (fixableCount > 0) {
|
|
126
|
+
lines.push(` ${fixableCount} violation(s) are auto-fixable. Run: skannr guard --fix`);
|
|
127
|
+
lines.push('');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lines.join('\n');
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Format the guard result as JSON.
|
|
134
|
+
*/
|
|
135
|
+
function formatGuardJson(result) {
|
|
136
|
+
return JSON.stringify({
|
|
137
|
+
status: result.response.status,
|
|
138
|
+
summary: result.response.summary,
|
|
139
|
+
violations: result.response.violations,
|
|
140
|
+
symbolsReviewed: result.symbolsReviewed,
|
|
141
|
+
rulesUsed: result.rulesUsed.length,
|
|
142
|
+
durationMs: result.durationMs,
|
|
143
|
+
}, null, 2);
|
|
144
|
+
}
|
|
145
|
+
// Re-export for CLI/MCP wiring
|
|
146
|
+
var hook_installer_1 = require("./hook-installer");
|
|
147
|
+
Object.defineProperty(exports, "installHook", { enumerable: true, get: function () { return hook_installer_1.installHook; } });
|
|
148
|
+
Object.defineProperty(exports, "uninstallHook", { enumerable: true, get: function () { return hook_installer_1.uninstallHook; } });
|
|
149
|
+
var rules_loader_1 = require("./rules-loader");
|
|
150
|
+
Object.defineProperty(exports, "loadRules", { enumerable: true, get: function () { return rules_loader_1.loadRules; } });
|
|
151
|
+
Object.defineProperty(exports, "loadGuardConfig", { enumerable: true, get: function () { return rules_loader_1.loadGuardConfig; } });
|
|
152
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/guard/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLM provider abstraction for guard reviews.
|
|
3
|
+
* Supports Gemini (existing dep) and OpenAI-compatible APIs.
|
|
4
|
+
* Enforces structured JSON output — never parses free text.
|
|
5
|
+
*/
|
|
6
|
+
import type { GuardConfig, GuardRule, ReviewResponse, SymbolDiffUnit } from './types';
|
|
7
|
+
/**
|
|
8
|
+
* Run the LLM review and return a validated response.
|
|
9
|
+
* Retries once on validation failure with a correction prompt.
|
|
10
|
+
*/
|
|
11
|
+
export declare function runLlmReview(config: GuardConfig, rules: GuardRule[], units: SymbolDiffUnit[]): Promise<ReviewResponse>;
|
|
12
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/guard/provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAgJtF;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,SAAS,EAAE,EAClB,KAAK,EAAE,cAAc,EAAE,GACtB,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LLM provider abstraction for guard reviews.
|
|
4
|
+
* Supports Gemini (existing dep) and OpenAI-compatible APIs.
|
|
5
|
+
* Enforces structured JSON output — never parses free text.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.runLlmReview = runLlmReview;
|
|
42
|
+
const schema_1 = require("./schema");
|
|
43
|
+
/** Maximum number of retry attempts when LLM returns invalid JSON. */
|
|
44
|
+
const MAX_RETRIES = 1;
|
|
45
|
+
/**
|
|
46
|
+
* Build the system prompt for the guard review.
|
|
47
|
+
*/
|
|
48
|
+
function buildSystemPrompt(rules) {
|
|
49
|
+
const rulesText = rules
|
|
50
|
+
.map((r) => `- [${r.id}] (${r.severity}, fixable=${r.fixable}): ${r.description}`)
|
|
51
|
+
.join('\n');
|
|
52
|
+
return [
|
|
53
|
+
'You are a code review tool. Review the provided symbol-level diffs against the rules below.',
|
|
54
|
+
'For each violation found, output a structured JSON object matching the exact schema.',
|
|
55
|
+
'',
|
|
56
|
+
'Rules:',
|
|
57
|
+
rulesText,
|
|
58
|
+
'',
|
|
59
|
+
'Response format (strict JSON, no text before or after):',
|
|
60
|
+
'{',
|
|
61
|
+
' "violations": [{ "rule_id": "...", "file": "...", "symbol": "...", "line_start": N, "line_end": N, "severity": "...", "confidence": 0.0-1.0, "fixable": bool, "message": "...", "suggested_fix": "..." }],',
|
|
62
|
+
' "status": "pass" | "fail",',
|
|
63
|
+
' "summary": "..."',
|
|
64
|
+
'}',
|
|
65
|
+
'',
|
|
66
|
+
'IMPORTANT:',
|
|
67
|
+
'- Only flag violations for rules listed above.',
|
|
68
|
+
'- The "fixable" field MUST match the rule definition — do not override it.',
|
|
69
|
+
'- "confidence" reflects how certain you are this is a real violation (0.0-1.0).',
|
|
70
|
+
'- "suggested_fix" is required only when fixable=true.',
|
|
71
|
+
'- If no violations found, return status "pass" with an empty violations array.',
|
|
72
|
+
'- Return ONLY the JSON object. No markdown, no explanation, no preamble.',
|
|
73
|
+
].join('\n');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build the user prompt with symbol diff units.
|
|
77
|
+
*/
|
|
78
|
+
function buildUserPrompt(units) {
|
|
79
|
+
const parts = ['Review these symbol-level changes:\n'];
|
|
80
|
+
for (const unit of units) {
|
|
81
|
+
parts.push(`--- ${unit.file} :: ${unit.symbol} (${unit.changeType}) ---`);
|
|
82
|
+
if (unit.oldSignature)
|
|
83
|
+
parts.push(`Old: ${unit.oldSignature}`);
|
|
84
|
+
if (unit.newSignature)
|
|
85
|
+
parts.push(`New: ${unit.newSignature}`);
|
|
86
|
+
if (unit.callers.length > 0)
|
|
87
|
+
parts.push(`Callers: ${unit.callers.join(', ')}`);
|
|
88
|
+
if (unit.callees.length > 0)
|
|
89
|
+
parts.push(`Callees: ${unit.callees.join(', ')}`);
|
|
90
|
+
parts.push('');
|
|
91
|
+
// Include a trimmed diff (first 100 lines to avoid token explosion)
|
|
92
|
+
const diffLines = unit.diffText.split('\n').slice(0, 100);
|
|
93
|
+
parts.push(diffLines.join('\n'));
|
|
94
|
+
parts.push('');
|
|
95
|
+
}
|
|
96
|
+
return parts.join('\n');
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extract JSON from a response that might have text before/after.
|
|
100
|
+
* This handles the exact failure mode from gga: providers prepending
|
|
101
|
+
* acknowledgment text before the actual JSON response.
|
|
102
|
+
*/
|
|
103
|
+
function extractJson(text) {
|
|
104
|
+
// Try to find a JSON object in the response
|
|
105
|
+
const firstBrace = text.indexOf('{');
|
|
106
|
+
const lastBrace = text.lastIndexOf('}');
|
|
107
|
+
if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
|
|
108
|
+
throw new Error('No JSON object found in response');
|
|
109
|
+
}
|
|
110
|
+
return text.slice(firstBrace, lastBrace + 1);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Call Gemini API with structured output enforcement.
|
|
114
|
+
*/
|
|
115
|
+
async function callGemini(config, systemPrompt, userPrompt) {
|
|
116
|
+
const { GoogleGenerativeAI } = await Promise.resolve().then(() => __importStar(require('@google/generative-ai')));
|
|
117
|
+
if (!config.apiKey) {
|
|
118
|
+
throw new Error('No API key. Set GEMINI_API_KEY or SKANNR_GUARD_API_KEY.');
|
|
119
|
+
}
|
|
120
|
+
const genAI = new GoogleGenerativeAI(config.apiKey);
|
|
121
|
+
const model = genAI.getGenerativeModel({
|
|
122
|
+
model: config.model,
|
|
123
|
+
generationConfig: {
|
|
124
|
+
responseMimeType: 'application/json',
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
const result = await model.generateContent({
|
|
128
|
+
contents: [{ role: 'user', parts: [{ text: userPrompt }] }],
|
|
129
|
+
systemInstruction: { role: 'system', parts: [{ text: systemPrompt }] },
|
|
130
|
+
});
|
|
131
|
+
return result.response.text();
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Call OpenAI-compatible API with structured output enforcement.
|
|
135
|
+
*/
|
|
136
|
+
async function callOpenAI(config, systemPrompt, userPrompt) {
|
|
137
|
+
const baseUrl = config.baseUrl || 'https://api.openai.com/v1';
|
|
138
|
+
const url = `${baseUrl}/chat/completions`;
|
|
139
|
+
if (!config.apiKey) {
|
|
140
|
+
throw new Error('No API key. Set OPENAI_API_KEY or SKANNR_GUARD_API_KEY.');
|
|
141
|
+
}
|
|
142
|
+
const response = await fetch(url, {
|
|
143
|
+
method: 'POST',
|
|
144
|
+
headers: {
|
|
145
|
+
'Content-Type': 'application/json',
|
|
146
|
+
'Authorization': `Bearer ${config.apiKey}`,
|
|
147
|
+
},
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
model: config.model,
|
|
150
|
+
messages: [
|
|
151
|
+
{ role: 'system', content: systemPrompt },
|
|
152
|
+
{ role: 'user', content: userPrompt },
|
|
153
|
+
],
|
|
154
|
+
response_format: { type: 'json_object' },
|
|
155
|
+
temperature: 0.1,
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`);
|
|
160
|
+
}
|
|
161
|
+
const data = await response.json();
|
|
162
|
+
return data.choices?.[0]?.message?.content ?? '';
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Run the LLM review and return a validated response.
|
|
166
|
+
* Retries once on validation failure with a correction prompt.
|
|
167
|
+
*/
|
|
168
|
+
async function runLlmReview(config, rules, units) {
|
|
169
|
+
const systemPrompt = buildSystemPrompt(rules);
|
|
170
|
+
const userPrompt = buildUserPrompt(units);
|
|
171
|
+
let lastError = null;
|
|
172
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
173
|
+
const prompt = attempt === 0
|
|
174
|
+
? userPrompt
|
|
175
|
+
: `${userPrompt}\n\n[RETRY: Your last response didn't match the required schema. Error: ${lastError}. Return ONLY valid JSON matching the schema.]`;
|
|
176
|
+
let rawResponse;
|
|
177
|
+
try {
|
|
178
|
+
rawResponse = config.provider === 'openai'
|
|
179
|
+
? await callOpenAI(config, systemPrompt, prompt)
|
|
180
|
+
: await callGemini(config, systemPrompt, prompt);
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
throw new Error(`Provider call failed (${config.provider}): ${err instanceof Error ? err.message : String(err)}`);
|
|
184
|
+
}
|
|
185
|
+
// Extract and validate JSON
|
|
186
|
+
try {
|
|
187
|
+
const jsonStr = extractJson(rawResponse);
|
|
188
|
+
const parsed = JSON.parse(jsonStr);
|
|
189
|
+
const result = schema_1.ReviewResponseSchema.safeParse(parsed);
|
|
190
|
+
if (result.success) {
|
|
191
|
+
// Enforce: fixable field must match the rule definition
|
|
192
|
+
for (const violation of result.data.violations) {
|
|
193
|
+
const rule = rules.find((r) => r.id === violation.rule_id);
|
|
194
|
+
if (rule) {
|
|
195
|
+
violation.fixable = rule.fixable;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return result.data;
|
|
199
|
+
}
|
|
200
|
+
lastError = result.error.issues
|
|
201
|
+
.map((i) => `${i.path.join('.')}: ${i.message}`)
|
|
202
|
+
.join('; ');
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// All retries exhausted
|
|
209
|
+
throw new Error(`LLM response failed schema validation after ${MAX_RETRIES + 1} attempts: ${lastError}`);
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/guard/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review runner — orchestrates the full guard pipeline:
|
|
3
|
+
* build symbol diffs → enrich with call context → batch → call LLM → validate.
|
|
4
|
+
*/
|
|
5
|
+
import type { GuardResult } from './types';
|
|
6
|
+
export interface RunOptions {
|
|
7
|
+
/** Project root directory. */
|
|
8
|
+
root: string;
|
|
9
|
+
/** If provided, review this diff instead of staged files. */
|
|
10
|
+
diffContent?: string;
|
|
11
|
+
/** Skip cross-file caller/callee context (faster, cheaper). */
|
|
12
|
+
diffOnly?: boolean;
|
|
13
|
+
/** Skip symbol cache. */
|
|
14
|
+
noCache?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run the full guard review pipeline.
|
|
18
|
+
* Returns a GuardResult with violations, or throws with exit code 2 errors.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runGuardReview(options: RunOptions): Promise<GuardResult>;
|
|
21
|
+
//# sourceMappingURL=review-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review-runner.d.ts","sourceRoot":"","sources":["../../src/guard/review-runner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,WAAW,EAAkC,MAAM,SAAS,CAAC;AAK3E,MAAM,WAAW,UAAU;IACzB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,yBAAyB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,CAAC,CAuE9E"}
|