skannr 0.2.0 → 0.2.1

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.
Files changed (58) hide show
  1. package/dist/cli.js +86 -0
  2. package/dist/cli.js.map +1 -1
  3. package/dist/guard/call-context.d.ts +14 -0
  4. package/dist/guard/call-context.d.ts.map +1 -0
  5. package/dist/guard/call-context.js +120 -0
  6. package/dist/guard/call-context.js.map +1 -0
  7. package/dist/guard/fix-applier.d.ts +38 -0
  8. package/dist/guard/fix-applier.d.ts.map +1 -0
  9. package/dist/guard/fix-applier.js +181 -0
  10. package/dist/guard/fix-applier.js.map +1 -0
  11. package/dist/guard/hook-installer.d.ts +19 -0
  12. package/dist/guard/hook-installer.d.ts.map +1 -0
  13. package/dist/guard/hook-installer.js +119 -0
  14. package/dist/guard/hook-installer.js.map +1 -0
  15. package/dist/guard/index.d.ts +34 -0
  16. package/dist/guard/index.d.ts.map +1 -0
  17. package/dist/guard/index.js +152 -0
  18. package/dist/guard/index.js.map +1 -0
  19. package/dist/guard/provider.d.ts +12 -0
  20. package/dist/guard/provider.d.ts.map +1 -0
  21. package/dist/guard/provider.js +211 -0
  22. package/dist/guard/provider.js.map +1 -0
  23. package/dist/guard/review-runner.d.ts +21 -0
  24. package/dist/guard/review-runner.d.ts.map +1 -0
  25. package/dist/guard/review-runner.js +85 -0
  26. package/dist/guard/review-runner.js.map +1 -0
  27. package/dist/guard/rules-loader.d.ts +10 -0
  28. package/dist/guard/rules-loader.d.ts.map +1 -0
  29. package/dist/guard/rules-loader.js +121 -0
  30. package/dist/guard/rules-loader.js.map +1 -0
  31. package/dist/guard/schema.d.ts +182 -0
  32. package/dist/guard/schema.d.ts.map +1 -0
  33. package/dist/guard/schema.js +53 -0
  34. package/dist/guard/schema.js.map +1 -0
  35. package/dist/guard/symbol-diff.d.ts +18 -0
  36. package/dist/guard/symbol-diff.d.ts.map +1 -0
  37. package/dist/guard/symbol-diff.js +243 -0
  38. package/dist/guard/symbol-diff.js.map +1 -0
  39. package/dist/guard/types.d.ts +62 -0
  40. package/dist/guard/types.d.ts.map +1 -0
  41. package/dist/guard/types.js +6 -0
  42. package/dist/guard/types.js.map +1 -0
  43. package/dist/mcp-server.d.ts.map +1 -1
  44. package/dist/mcp-server.js +44 -0
  45. package/dist/mcp-server.js.map +1 -1
  46. package/package.json +3 -2
  47. package/src/cli.ts +101 -0
  48. package/src/guard/call-context.ts +101 -0
  49. package/src/guard/fix-applier.ts +172 -0
  50. package/src/guard/hook-installer.ts +96 -0
  51. package/src/guard/index.ts +134 -0
  52. package/src/guard/provider.ts +209 -0
  53. package/src/guard/review-runner.ts +110 -0
  54. package/src/guard/rules-loader.ts +97 -0
  55. package/src/guard/schema.ts +59 -0
  56. package/src/guard/symbol-diff.ts +227 -0
  57. package/src/guard/types.ts +68 -0
  58. package/src/mcp-server.ts +49 -0
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ /**
3
+ * Build symbol-level diff units from staged git changes.
4
+ * Uses Skannr's existing parser to identify which symbols changed,
5
+ * instead of working with raw file diffs.
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.getStagedFiles = getStagedFiles;
42
+ exports.buildSymbolDiffs = buildSymbolDiffs;
43
+ exports.buildSymbolDiffsFromDiff = buildSymbolDiffsFromDiff;
44
+ const path = __importStar(require("path"));
45
+ const fs = __importStar(require("fs"));
46
+ const child_process_1 = require("child_process");
47
+ const registry_1 = require("../languages/registry");
48
+ const scanner_1 = require("../scanner");
49
+ /** Get the list of staged files (paths relative to root). */
50
+ function getStagedFiles(root) {
51
+ try {
52
+ const output = (0, child_process_1.execSync)('git diff --cached --name-only --diff-filter=ACMR', {
53
+ cwd: root,
54
+ encoding: 'utf-8',
55
+ });
56
+ return output.trim().split('\n').filter(Boolean);
57
+ }
58
+ catch {
59
+ return [];
60
+ }
61
+ }
62
+ /** Get the diff text for a specific staged file. */
63
+ function getStagedDiff(root, file) {
64
+ try {
65
+ return (0, child_process_1.execSync)(`git diff --cached -- "${file}"`, {
66
+ cwd: root,
67
+ encoding: 'utf-8',
68
+ maxBuffer: 5 * 1024 * 1024,
69
+ });
70
+ }
71
+ catch {
72
+ return '';
73
+ }
74
+ }
75
+ /** Get the old (HEAD) version of a file, or null if newly added. */
76
+ function getHeadContent(root, file) {
77
+ try {
78
+ return (0, child_process_1.execSync)(`git show HEAD:"${file}"`, {
79
+ cwd: root,
80
+ encoding: 'utf-8',
81
+ maxBuffer: 5 * 1024 * 1024,
82
+ });
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ /** Extract changed line numbers from a unified diff. */
89
+ function extractChangedLines(diff) {
90
+ const lines = new Set();
91
+ let currentLine = 0;
92
+ for (const line of diff.split('\n')) {
93
+ const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)/);
94
+ if (hunkMatch) {
95
+ currentLine = parseInt(hunkMatch[1], 10);
96
+ continue;
97
+ }
98
+ if (line.startsWith('+') && !line.startsWith('+++')) {
99
+ lines.add(currentLine);
100
+ currentLine++;
101
+ }
102
+ else if (line.startsWith('-') && !line.startsWith('---')) {
103
+ // Deleted lines don't advance the new-file line counter
104
+ }
105
+ else {
106
+ currentLine++;
107
+ }
108
+ }
109
+ return lines;
110
+ }
111
+ /**
112
+ * Determine which symbols in a file were affected by the staged changes.
113
+ * Matches symbols whose line range overlaps any changed line.
114
+ */
115
+ function findAffectedSymbols(symbols, changedLines, allSymbols) {
116
+ if (changedLines.size === 0)
117
+ return [];
118
+ // Build approximate end lines: each symbol extends until the next symbol starts
119
+ const sorted = [...allSymbols].sort((a, b) => a.line - b.line);
120
+ const affected = [];
121
+ for (let i = 0; i < sorted.length; i++) {
122
+ const sym = sorted[i];
123
+ const startLine = sym.line;
124
+ const endLine = i + 1 < sorted.length ? sorted[i + 1].line - 1 : startLine + 50;
125
+ for (let line = startLine; line <= endLine; line++) {
126
+ if (changedLines.has(line)) {
127
+ affected.push(sym);
128
+ break;
129
+ }
130
+ }
131
+ }
132
+ return affected;
133
+ }
134
+ /**
135
+ * Build symbol-level diff units for all staged files in the project.
136
+ * This is the primary input to the review runner.
137
+ */
138
+ function buildSymbolDiffs(root) {
139
+ const stagedFiles = getStagedFiles(root);
140
+ const units = [];
141
+ for (const relFile of stagedFiles) {
142
+ const absPath = path.join(root, relFile);
143
+ if (!fs.existsSync(absPath))
144
+ continue;
145
+ const adapter = (0, registry_1.getAdapter)(absPath);
146
+ const newContent = (0, scanner_1.readFileContent)(absPath);
147
+ if (!newContent)
148
+ continue;
149
+ const newSymbols = adapter.extractSymbols(newContent);
150
+ const oldContent = getHeadContent(root, relFile);
151
+ const oldSymbols = oldContent ? adapter.extractSymbols(oldContent) : [];
152
+ const diff = getStagedDiff(root, relFile);
153
+ const changedLines = extractChangedLines(diff);
154
+ // Find symbols that overlap changed lines in the new version
155
+ const affected = findAffectedSymbols(newSymbols, changedLines, newSymbols);
156
+ // Determine change type for each affected symbol
157
+ const oldSymbolNames = new Set(oldSymbols.map((s) => s.name));
158
+ for (const sym of affected) {
159
+ if (sym.kind === 'export')
160
+ continue; // skip synthetic export markers
161
+ const changeType = oldSymbolNames.has(sym.name) ? 'modified' : 'added';
162
+ const oldSym = oldSymbols.find((s) => s.name === sym.name);
163
+ units.push({
164
+ file: relFile,
165
+ symbol: sym.name,
166
+ changeType,
167
+ oldSignature: oldSym ? `${oldSym.kind} ${oldSym.name}` : undefined,
168
+ newSignature: `${sym.kind} ${sym.name}`,
169
+ callers: [], // populated by call-context module
170
+ callees: [], // populated by call-context module
171
+ diffText: diff,
172
+ });
173
+ }
174
+ // Handle deleted symbols (in old but not in new)
175
+ if (oldContent) {
176
+ const newSymbolNames = new Set(newSymbols.map((s) => s.name));
177
+ for (const oldSym of oldSymbols) {
178
+ if (oldSym.kind === 'export')
179
+ continue;
180
+ if (!newSymbolNames.has(oldSym.name)) {
181
+ units.push({
182
+ file: relFile,
183
+ symbol: oldSym.name,
184
+ changeType: 'deleted',
185
+ oldSignature: `${oldSym.kind} ${oldSym.name}`,
186
+ callers: [],
187
+ callees: [],
188
+ diffText: diff,
189
+ });
190
+ }
191
+ }
192
+ }
193
+ }
194
+ return units;
195
+ }
196
+ /**
197
+ * Build symbol diffs from a PR diff string (for --pr-mode).
198
+ */
199
+ function buildSymbolDiffsFromDiff(root, diffContent) {
200
+ const parseDiff = require('parse-diff');
201
+ const parsedFiles = parseDiff(diffContent);
202
+ const units = [];
203
+ for (const file of parsedFiles) {
204
+ const raw = file.to ?? file.from;
205
+ if (!raw || raw === '/dev/null')
206
+ continue;
207
+ const relFile = raw.replace(/^[ab]\//, '');
208
+ const absPath = path.join(root, relFile);
209
+ if (!fs.existsSync(absPath))
210
+ continue;
211
+ const adapter = (0, registry_1.getAdapter)(absPath);
212
+ const content = (0, scanner_1.readFileContent)(absPath);
213
+ if (!content)
214
+ continue;
215
+ const symbols = adapter.extractSymbols(content);
216
+ const changedLines = new Set();
217
+ for (const chunk of file.chunks) {
218
+ for (const change of chunk.changes) {
219
+ if (change.type === 'add' || change.type === 'del') {
220
+ const ln = change.ln;
221
+ if (typeof ln === 'number')
222
+ changedLines.add(ln);
223
+ }
224
+ }
225
+ }
226
+ const affected = findAffectedSymbols(symbols, changedLines, symbols);
227
+ for (const sym of affected) {
228
+ if (sym.kind === 'export')
229
+ continue;
230
+ units.push({
231
+ file: relFile,
232
+ symbol: sym.name,
233
+ changeType: 'modified',
234
+ newSignature: `${sym.kind} ${sym.name}`,
235
+ callers: [],
236
+ callees: [],
237
+ diffText: file.chunks.map((c) => c.content).join('\n'),
238
+ });
239
+ }
240
+ }
241
+ return units;
242
+ }
243
+ //# sourceMappingURL=symbol-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-diff.js","sourceRoot":"","sources":["../../src/guard/symbol-diff.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Shared type definitions for the guard module.
3
+ */
4
+ /** Severity levels for rules and violations. */
5
+ export type Severity = 'low' | 'medium' | 'high' | 'critical';
6
+ /** A team-defined review rule. */
7
+ export interface GuardRule {
8
+ id: string;
9
+ description: string;
10
+ severity: Severity;
11
+ fixable: boolean;
12
+ category: string;
13
+ }
14
+ /** A single symbol-level diff unit sent to the LLM for review. */
15
+ export interface SymbolDiffUnit {
16
+ file: string;
17
+ symbol: string;
18
+ changeType: 'added' | 'modified' | 'deleted';
19
+ oldSignature?: string;
20
+ newSignature?: string;
21
+ callers: string[];
22
+ callees: string[];
23
+ diffText: string;
24
+ }
25
+ /** A violation found by the review. */
26
+ export interface Violation {
27
+ rule_id: string;
28
+ file: string;
29
+ symbol: string;
30
+ line_start: number;
31
+ line_end: number;
32
+ severity: Severity;
33
+ confidence: number;
34
+ fixable: boolean;
35
+ message: string;
36
+ suggested_fix?: string;
37
+ }
38
+ /** The structured response contract from the LLM. */
39
+ export interface ReviewResponse {
40
+ violations: Violation[];
41
+ status: 'pass' | 'fail';
42
+ summary: string;
43
+ }
44
+ /** Result of a full guard run. */
45
+ export interface GuardResult {
46
+ response: ReviewResponse;
47
+ rulesUsed: GuardRule[];
48
+ symbolsReviewed: number;
49
+ durationMs: number;
50
+ }
51
+ /** Guard configuration (provider, model, etc.). */
52
+ export interface GuardConfig {
53
+ /** LLM provider: 'gemini' | 'openai' */
54
+ provider: 'gemini' | 'openai';
55
+ /** Model name (e.g. 'gemini-2.0-flash-exp', 'gpt-4o'). */
56
+ model: string;
57
+ /** API key (resolved from env if not set). */
58
+ apiKey?: string;
59
+ /** OpenAI-compatible base URL (for openai provider). */
60
+ baseUrl?: string;
61
+ }
62
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/guard/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,gDAAgD;AAChD,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAE9D,kCAAkC;AAClC,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,kEAAkE;AAClE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,uCAAuC;AACvC,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kCAAkC;AAClC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,cAAc,CAAC;IACzB,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,wCAAwC;IACxC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAC9B,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,8CAA8C;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ /**
3
+ * Shared type definitions for the guard module.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/guard/types.ts"],"names":[],"mappings":";AAAA;;GAEG"}
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsBH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA8HpD"}
1
+ {"version":3,"file":"mcp-server.d.ts","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"AAAA;;GAEG;AAsBH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CA+KpD"}
@@ -110,6 +110,32 @@ async function startMcpServer() {
110
110
  required: ['root'],
111
111
  },
112
112
  },
113
+ {
114
+ name: 'guard_review',
115
+ description: 'Review staged or diff changes against team-defined rules. Returns structured violations with severity, confidence, and fixability.',
116
+ inputSchema: {
117
+ type: 'object',
118
+ properties: {
119
+ root: {
120
+ type: 'string',
121
+ description: 'Absolute path to the repo root',
122
+ },
123
+ diff: {
124
+ type: 'string',
125
+ description: 'Unified diff content (if omitted, reviews staged files)',
126
+ },
127
+ diff_only: {
128
+ type: 'boolean',
129
+ description: 'Skip cross-file context for faster review (default: false)',
130
+ },
131
+ fix: {
132
+ type: 'boolean',
133
+ description: 'Apply auto-fixes for fixable violations (default: false)',
134
+ },
135
+ },
136
+ required: ['root'],
137
+ },
138
+ },
113
139
  ],
114
140
  }));
115
141
  server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
@@ -156,6 +182,24 @@ async function startMcpServer() {
156
182
  content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
157
183
  };
158
184
  }
185
+ if (toolName === 'guard_review') {
186
+ const { runGuard, formatGuardJson } = await Promise.resolve().then(() => __importStar(require('./guard/index')));
187
+ const root = String(args.root ?? '');
188
+ const diffContent = typeof args.diff === 'string' && args.diff.length > 0
189
+ ? args.diff
190
+ : undefined;
191
+ const diffOnly = args.diff_only === true;
192
+ const fix = args.fix === true;
193
+ const { result } = await runGuard({
194
+ root,
195
+ diffOnly,
196
+ fix,
197
+ ...(diffContent ? { prMode: false } : {}),
198
+ });
199
+ return {
200
+ content: [{ type: 'text', text: formatGuardJson(result) }],
201
+ };
202
+ }
159
203
  throw new Error(`Unknown tool: ${toolName}`);
160
204
  });
161
205
  const transport = new stdio_js_1.StdioServerTransport();
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp-server.js","sourceRoot":"","sources":["../src/mcp-server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skannr",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A universal tool to analyze any codebase and generate semantic skeletons for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -35,7 +35,8 @@
35
35
  "ignore": "^7.0.5",
36
36
  "parse-diff": "0.12.0",
37
37
  "ts-morph": "^21.0.1",
38
- "web-tree-sitter": "0.26.10"
38
+ "web-tree-sitter": "0.26.10",
39
+ "zod": "3.23.8"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@types/jest": "^29.5.11",
package/src/cli.ts CHANGED
@@ -483,6 +483,102 @@ cacheCommand
483
483
  console.log(` Dir: ${cacheManager.getCacheDir()}\n`);
484
484
  });
485
485
 
486
+ // ---------------------------------------------------------------------------
487
+ // Subcommand: guard
488
+ // ---------------------------------------------------------------------------
489
+ const guardCommand = program
490
+ .command('guard')
491
+ .description('Review staged changes against team-defined rules')
492
+ .option('--root <path>', 'project root directory', process.cwd())
493
+ .option('--fix', 'apply auto-fixes for fixable violations')
494
+ .option('--dry-run', 'show what --fix would change without writing')
495
+ .option('--pr-mode', 'review full PR diff vs base branch')
496
+ .option('--diff-only', 'skip cross-file context (faster, cheaper)')
497
+ .option('--no-cache', 'ignore symbol cache')
498
+ .option('--json', 'output as JSON')
499
+ .action(async (cmdOpts: {
500
+ root: string;
501
+ fix?: boolean;
502
+ dryRun?: boolean;
503
+ prMode?: boolean;
504
+ diffOnly?: boolean;
505
+ noCache?: boolean;
506
+ json?: boolean;
507
+ }) => {
508
+ try {
509
+ const { runGuard, formatGuardText, formatGuardJson } = await import('./guard/index');
510
+ const { result, exitCode } = await runGuard({
511
+ root: cmdOpts.root,
512
+ fix: cmdOpts.fix,
513
+ dryRun: cmdOpts.dryRun,
514
+ prMode: cmdOpts.prMode,
515
+ diffOnly: cmdOpts.diffOnly,
516
+ noCache: cmdOpts.noCache,
517
+ json: cmdOpts.json,
518
+ });
519
+
520
+ const output = cmdOpts.json
521
+ ? formatGuardJson(result)
522
+ : formatGuardText(result);
523
+
524
+ process.stdout.write(output + (output.endsWith('\n') ? '' : '\n'));
525
+ process.exit(exitCode);
526
+ } catch (error) {
527
+ console.error(
528
+ 'Error running guard:',
529
+ error instanceof Error ? error.message : error,
530
+ );
531
+ process.exit(2);
532
+ }
533
+ });
534
+
535
+ guardCommand
536
+ .command('install')
537
+ .description('Install git pre-commit hook')
538
+ .option('--root <path>', 'project root directory', process.cwd())
539
+ .action(async (cmdOpts: { root: string }) => {
540
+ const { installHook } = await import('./guard/index');
541
+ const { installed, message } = installHook(path.resolve(cmdOpts.root));
542
+ console.log(message);
543
+ process.exit(installed ? 0 : 1);
544
+ });
545
+
546
+ guardCommand
547
+ .command('uninstall')
548
+ .description('Remove git pre-commit hook')
549
+ .option('--root <path>', 'project root directory', process.cwd())
550
+ .action(async (cmdOpts: { root: string }) => {
551
+ const { uninstallHook } = await import('./guard/index');
552
+ const { removed, message } = uninstallHook(path.resolve(cmdOpts.root));
553
+ console.log(message);
554
+ process.exit(removed ? 0 : 1);
555
+ });
556
+
557
+ guardCommand
558
+ .command('config')
559
+ .description('Show loaded rules and provider config')
560
+ .option('--root <path>', 'project root directory', process.cwd())
561
+ .action(async (cmdOpts: { root: string }) => {
562
+ const { loadRules, loadGuardConfig } = await import('./guard/index');
563
+ const root = path.resolve(cmdOpts.root);
564
+ try {
565
+ const rules = loadRules(root);
566
+ const config = loadGuardConfig(root);
567
+ console.log('\n Guard Config:');
568
+ console.log(` Provider: ${config.provider}`);
569
+ console.log(` Model: ${config.model}`);
570
+ console.log(` API Key: ${config.apiKey ? '***' + config.apiKey.slice(-4) : 'not set'}`);
571
+ console.log(`\n Rules (${rules.length}):`);
572
+ for (const r of rules) {
573
+ console.log(` [${r.id}] ${r.severity} | fixable=${r.fixable} | ${r.description}`);
574
+ }
575
+ console.log('');
576
+ } catch (error) {
577
+ console.error(error instanceof Error ? error.message : error);
578
+ process.exit(2);
579
+ }
580
+ });
581
+
486
582
  // ---------------------------------------------------------------------------
487
583
  // Help
488
584
  // ---------------------------------------------------------------------------
@@ -499,6 +595,11 @@ Examples:
499
595
  skannr risk --diff feature.patch Impact of a patch file
500
596
  skannr risk -n 3 --json 3 hops, JSON for CI
501
597
 
598
+ skannr guard Review staged changes against rules
599
+ skannr guard --fix Auto-fix fixable violations
600
+ skannr guard --pr-mode --json Review PR diff, JSON output
601
+ skannr guard install Install pre-commit hook
602
+
502
603
  skannr report Repo health summary
503
604
  skannr agent Interactive mode
504
605
  skannr cache stats Cache hit rate
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Pull caller/callee context from the existing dependency graph.
3
+ * Enriches SymbolDiffUnit[] with cross-file relationship information
4
+ * so the LLM can detect breakages beyond the changed file.
5
+ */
6
+
7
+ import * as path from 'path';
8
+ import { scanFiles, readFileContent } from '../scanner';
9
+ import { analyzeDependencyGraph } from '../ranker-enhanced';
10
+ import { getAdapter } from '../languages/registry';
11
+ import { buildReverseGraph } from '../blast-radius';
12
+ import { loadConfig } from '../config';
13
+ import type { SymbolDiffUnit } from './types';
14
+
15
+ /**
16
+ * Enrich symbol diff units with caller and callee context.
17
+ * Uses the project's dependency graph to find:
18
+ * - callers: files that import the changed file (reverse deps)
19
+ * - callees: files that the changed file imports (forward deps)
20
+ */
21
+ export function enrichWithCallContext(
22
+ units: SymbolDiffUnit[],
23
+ root: string,
24
+ ): SymbolDiffUnit[] {
25
+ if (units.length === 0) return units;
26
+
27
+ // Scan project files and build dependency graph
28
+ const config = loadConfig(root);
29
+ const allFiles = scanFiles(root, {
30
+ extensions: config.extensions,
31
+ exclude: config.exclude,
32
+ });
33
+
34
+ const forwardGraph = analyzeDependencyGraph(allFiles, root);
35
+ const reverseGraph = buildReverseGraph(forwardGraph);
36
+
37
+ // Group units by file
38
+ const fileSet = new Set(units.map((u) => u.file));
39
+
40
+ for (const unit of units) {
41
+ const fileRel = unit.file;
42
+ const fileNoExt = fileRel.replace(/\.[^.]+$/, '');
43
+ const fileBase = path.basename(fileRel, path.extname(fileRel));
44
+
45
+ // Find callers (files that import this file)
46
+ const callers: string[] = [];
47
+ for (const [key, importers] of reverseGraph.entries()) {
48
+ const normalizedKey = key.split(path.sep).join('/');
49
+ if (
50
+ normalizedKey === fileRel ||
51
+ normalizedKey === fileNoExt ||
52
+ normalizedKey.endsWith('/' + fileBase)
53
+ ) {
54
+ for (const importer of importers) {
55
+ callers.push(importer);
56
+ }
57
+ }
58
+ }
59
+
60
+ // Find callees (files this file imports)
61
+ const callees: string[] = [];
62
+ const normalizedFileRel = fileRel.split(path.sep).join('/');
63
+ for (const [key, imports] of forwardGraph.entries()) {
64
+ const normalizedKey = key.split(path.sep).join('/');
65
+ if (normalizedKey === normalizedFileRel) {
66
+ callees.push(...imports);
67
+ break;
68
+ }
69
+ }
70
+
71
+ // Format as "file:symbol" where possible
72
+ unit.callers = [...new Set(callers)].slice(0, 10).map((c) => formatCallerRef(c, root));
73
+ unit.callees = [...new Set(callees)].slice(0, 10);
74
+ }
75
+
76
+ return units;
77
+ }
78
+
79
+ /**
80
+ * Format a caller file reference, attempting to identify which symbols
81
+ * in that file actually reference the changed file.
82
+ */
83
+ function formatCallerRef(callerFile: string, root: string): string {
84
+ const absPath = path.join(root, callerFile);
85
+ const content = readFileContent(absPath);
86
+ if (!content) return callerFile;
87
+
88
+ const adapter = getAdapter(absPath);
89
+ const symbols = adapter.extractSymbols(content);
90
+
91
+ // Return the file with its top exported symbols for context
92
+ const topSymbols = symbols
93
+ .filter((s) => s.kind === 'function' || s.kind === 'class')
94
+ .slice(0, 3)
95
+ .map((s) => s.name);
96
+
97
+ if (topSymbols.length > 0) {
98
+ return `${callerFile}:${topSymbols.join(',')}`;
99
+ }
100
+ return callerFile;
101
+ }