skannr 0.2.2 → 0.2.4

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.
@@ -0,0 +1,229 @@
1
+ Skannr: Architecture and Design Decisions
2
+
3
+ Overview
4
+
5
+ Skannr is a CLI tool, MCP server, and library that helps developers and AI assistants understand codebases without reading every file. It scans a project, generates compressed structural skeletons of the most relevant files, and returns them ranked by relevance to a natural language question. The output is 96.5% smaller than raw source while retaining all architectural information.
6
+
7
+ The tool also includes risk analysis (quantifying the downstream impact of a code change) and guard (AI code review against team rules with auto-fix capability).
8
+
9
+ Core Problem
10
+
11
+ AI coding assistants are limited by context windows. A typical repo has thousands of files. You cannot paste them all. The assistant sees only what you show it, and guesses the rest.
12
+
13
+ Existing approaches either dump full files (expensive, noisy) or do keyword search (misses architecturally central files that do not mention the search term directly). Neither approach understands the structural relationships between files.
14
+
15
+ Skannr solves this by combining three ranking signals with structural compression, so the AI gets the right files in the right format at minimal token cost.
16
+
17
+ System Architecture
18
+
19
+ The pipeline is linear and stateless. Each stage takes input from the previous stage and produces output for the next. There is no shared mutable state.
20
+
21
+ Input (question + root path)
22
+ > File Scanner (discovers all source files)
23
+ > Ranker (scores files by relevance using hybrid retrieval)
24
+ > Skeletonizer (strips function bodies, keeps signatures and types)
25
+ > Formatter (outputs human text, markdown, or JSON)
26
+
27
+ Each stage is a separate module. They communicate through typed interfaces, not through side effects.
28
+
29
+ File Layout
30
+
31
+ src/
32
+ cli.ts Command registration (Commander.js, subcommands)
33
+ index.ts Library entry point, orchestrates the pipeline
34
+ scanner.ts File discovery with gitignore awareness
35
+ ranker.ts Basic keyword relevance scoring
36
+ ranker-enhanced.ts Hybrid ranking with dependency graph analysis
37
+ skeletonizer.ts Routes files to the correct language adapter
38
+ formatter.ts Output formatting (human, markdown, JSON)
39
+ blast-radius.ts Risk analysis (diff parsing, graph traversal, scoring)
40
+ guard/ AI code review module (schema, provider, fix applier)
41
+ languages/ Language adapters (TypeScript via ts-morph, others via WASM)
42
+ mcp-server.ts MCP tool registration for AI assistant integration
43
+ types.ts Shared type definitions
44
+
45
+ Design Decisions
46
+
47
+ 1. Hybrid Retrieval over Pure Keyword Search
48
+
49
+ Decision: Score files using three signals combined, not just keyword matching.
50
+
51
+ The three signals:
52
+ Lexical: keyword frequency in file path and content, with synonym expansion
53
+ Structural: export density, import relevance, symbol count
54
+ Dependency graph: how many other files import this file (centrality)
55
+
56
+ Why: A file named utils.ts might contain the most architecturally important auth logic but never mention the word "auth" in its filename. Pure keyword search misses it. Dependency centrality catches it because everything imports it.
57
+
58
+ The weights are: lexical 50%, structural 20%, enhanced score 30%, with a dependency boost of 15% blended in. These were tuned empirically against large TypeScript monorepos.
59
+
60
+ 2. AST Skeleton Generation over Summarization
61
+
62
+ Decision: Generate skeletons by stripping function bodies from the AST, not by asking an LLM to summarize.
63
+
64
+ Why:
65
+ Deterministic (same input always produces same output)
66
+ Fast (milliseconds, not seconds)
67
+ Free (no API call)
68
+ Lossless for architecture (all types, signatures, imports, class hierarchies preserved)
69
+ Only lossy for implementation details (function bodies replaced with a placeholder)
70
+
71
+ The TypeScript adapter uses ts-morph (a TypeScript compiler API wrapper) which gives perfect AST access. For Python, Go, Rust, and Java, the tool uses web-tree-sitter (WASM compiled grammars) which gives equivalent structural parsing without requiring native C++ compilation.
72
+
73
+ 3. WASM over Native Tree-sitter
74
+
75
+ Decision: Ship tree-sitter grammars as WASM files instead of native bindings.
76
+
77
+ Why: Native tree-sitter requires a C++ compiler (Visual Studio on Windows, Xcode on Mac). Most JavaScript developers do not have these installed. The npm install would fail for the majority of users.
78
+
79
+ WASM grammars are platform-independent. They run everywhere Node.js runs. The tradeoff is slightly slower parsing (roughly 2x), but since we only parse files the ranker already selected as relevant (typically 5-10 files), the total time remains under 2 seconds.
80
+
81
+ The WASM files add 2MB to the package. This is acceptable because it eliminates a class of installation failures entirely.
82
+
83
+ 4. File-level Dependency Graph (not Symbol-level)
84
+
85
+ Decision: Track dependencies at file granularity (file A imports file B) not at symbol granularity (function X calls function Y).
86
+
87
+ Why: Symbol-level call graphs require type resolution, which in turn requires a full project compilation context. TypeScript's type checker can do this but only for TS/JS. Python, Go, Rust, and Java would each need their own type resolver. The engineering cost is disproportionate to the accuracy gain for the primary use case (ranking files by relevance).
88
+
89
+ File-level import graphs are trivially extractable from any language via regex or AST import node detection. They provide sufficient signal for ranking and risk analysis.
90
+
91
+ This is documented as a stated limitation. Symbol-level resolution is planned for a future version.
92
+
93
+ 5. Deterministic Risk Scoring over LLM-based Assessment
94
+
95
+ Decision: Compute risk as a weighted formula with named constants, not by asking an LLM "how risky is this."
96
+
97
+ The formula:
98
+ risk = 2.5 * (affected files / total files)
99
+ + 2.5 * (average centrality of affected files)
100
+ + 3.5 * (fraction of affected files with no test coverage)
101
+ + 1.5 * (max hop depth reached / max hops configured)
102
+
103
+ Why:
104
+ Deterministic: same diff always produces same score
105
+ Fast: runs in under 2 seconds with zero API calls
106
+ CI-gateable: you can block merges on a number, not on an LLM's mood
107
+ Transparent: the formula is visible, the weights are named constants, the inputs are shown
108
+ Reproducible: another developer running the same command gets the same result
109
+
110
+ The untested ratio has the highest weight (3.5 out of 10) because untested downstream code is the strongest signal that a merge is risky. This weighting was a deliberate choice: the number that should most influence whether someone trusts a merge is whether the things it might break have tests.
111
+
112
+ 6. Guard: Structured JSON Contract over Text Parsing
113
+
114
+ Decision: Enforce schema-validated JSON responses from the LLM. Never parse free text looking for status keywords.
115
+
116
+ Why: This is a direct response to a known failure mode. Other tools in this space parse LLM responses as raw text, looking for a specific line (like "STATUS: PASSED") at a fixed position. This breaks when providers prepend acknowledgment text ("Sure! Here's my analysis:") before the actual response.
117
+
118
+ Skannr Guard uses:
119
+ Gemini API's responseMimeType: 'application/json' (forces JSON output)
120
+ OpenAI's response_format: { type: 'json_object' } (same)
121
+ Zod schema validation on every response before using it
122
+ A JSON extraction fallback that finds the first { to last } regardless of surrounding text
123
+ One retry with an explicit correction prompt on validation failure
124
+
125
+ The violation object has a fixed schema. The fixable field is set by the rule definition, not by the LLM. This prevents the LLM from deciding on its own what it can auto-fix.
126
+
127
+ 7. Guard: CLI Auto-detection over Mandatory API Keys
128
+
129
+ Decision: If the user has Claude Code, Gemini CLI, Kiro, or Ollama installed, use it directly. Do not require a separate API key.
130
+
131
+ Detection priority:
132
+ 1. claude CLI (uses existing Claude Code authenticated session)
133
+ 2. gemini CLI (uses existing Gemini authenticated session)
134
+ 3. kiro-cli (uses existing Kiro session)
135
+ 4. ollama (local, no auth needed)
136
+ 5. Gemini API (needs GEMINI_API_KEY)
137
+ 6. OpenAI API (needs OPENAI_API_KEY)
138
+
139
+ Why: The biggest friction in AI-powered dev tools is API key setup. If someone already has a coding agent installed and authenticated, they should not need to configure anything else. The tool should use what is already there.
140
+
141
+ This mirrors how GGA (gentleman-guardian-angel) works: it shells out to existing CLI tools rather than making its own API calls. The difference is that Skannr Guard also supports direct API calls as a fallback for users who do not have any CLI tool installed.
142
+
143
+ 8. Subcommands over Flags
144
+
145
+ Decision: Structure the CLI as subcommands (skannr risk, skannr guard, skannr agent) rather than a single command with 20+ flags.
146
+
147
+ Why: The original CLI had --question, --root, --report, --cache-clear, --cache-stats, --diff, --watch, --mcp, --telemetry-on, --telemetry-off all on one command. Users had to read --help every time to find which flags go together.
148
+
149
+ Subcommands group related functionality. Each subcommand has only the flags relevant to it. The most common operation (asking a question) takes a bare positional argument:
150
+
151
+ skannr "how does auth work?"
152
+
153
+ No flag needed. The tool should be typeable from memory after seeing it once.
154
+
155
+ Backward compatibility: all old flags still work. The new commands are aliases, not replacements.
156
+
157
+ 9. Built-in Default Rules over Mandatory Configuration
158
+
159
+ Decision: Guard works without any rules file. Built-in defaults cover universal best practices.
160
+
161
+ Default rules: no-any-type, no-console-log, error-handling, no-hardcoded-secrets, function-complexity, unused-imports.
162
+
163
+ Why: Every extra file a tool requires before it works is friction that prevents adoption. If the user has to create .skannr/rules.json before they can try the tool, most will not try it.
164
+
165
+ The custom rules file is optional. When present, it overrides defaults. Teams that need domain-specific rules can add them. Everyone else gets reasonable defaults out of the box.
166
+
167
+ 10. MCP as the Integration Layer
168
+
169
+ Decision: Expose all features as MCP tools (scan_codebase, blast_radius, guard_review) over stdio.
170
+
171
+ Why: MCP (Model Context Protocol) is the emerging standard for giving AI assistants access to external tools. One protocol works with Cursor, Claude Code, Gemini CLI, VS Code Copilot, and any future MCP-compatible tool.
172
+
173
+ The alternative would be building separate plugins for each editor. MCP avoids that entirely. One implementation, every tool.
174
+
175
+ The MCP server and CLI share the same internal functions. There is no code duplication between the two surfaces.
176
+
177
+ Technology Choices
178
+
179
+ TypeScript: The project itself is written in TypeScript. This aligns with the primary audience (JavaScript/TypeScript developers) and allows dogfooding the TypeScript adapter on the project itself.
180
+
181
+ Commander.js: CLI framework. Mature, well-typed, supports subcommands and positional arguments. Already a standard choice in the Node.js ecosystem.
182
+
183
+ ts-morph: TypeScript/JavaScript AST access. Wraps the TypeScript compiler API with a cleaner interface. Gives perfect type information, symbol extraction, and skeleton generation for TS/JS files.
184
+
185
+ web-tree-sitter: Multi-language parsing via WASM. No native compilation. Supports 40+ languages via grammar files. We bundle Python, Go, Rust, Java (2MB total).
186
+
187
+ parse-diff: Unified diff parsing. Small focused library, well-maintained, has types. Used by blast-radius to extract changed files and line ranges from git diffs.
188
+
189
+ zod: Schema validation for Guard's rules file and LLM response contract. Runtime type checking that integrates with TypeScript's type system.
190
+
191
+ @modelcontextprotocol/sdk: Official MCP SDK. Handles the stdio transport protocol so we only implement tool logic.
192
+
193
+ @google/generative-ai: Gemini SDK for Guard's direct API calls and the interactive agent mode.
194
+
195
+ What is Not Included (and Why)
196
+
197
+ No vector embeddings: Skannr does not use embeddings or a vector store. The hybrid ranking (lexical + structural + graph centrality) is sufficient for finding relevant files. Embeddings would add a model dependency, increase latency, and require a build step to generate the index.
198
+
199
+ No persistent index: Each run scans fresh. The cache is keyed by file hash (MD5) and invalidates automatically when files change. A persistent index would be faster for repeated queries on unchanged codebases, but adds complexity around invalidation and stale results.
200
+
201
+ No UI: Skannr is a CLI tool and MCP server. There is no web dashboard or VS Code extension. The landing page is marketing only. The tool's value is in the terminal and in the AI assistant context window.
202
+
203
+ No custom language grammars: Adding a new language means adding one config entry (which AST node types represent functions, classes, imports) and one WASM grammar file. We do not write custom parsers per language.
204
+
205
+ Testing Approach
206
+
207
+ Jest with ts-jest. Tests are in a flat tests/ directory, named *.test.ts.
208
+
209
+ Test patterns:
210
+ Temp directories with real files (not mocks of the filesystem)
211
+ beforeEach creates fixtures, afterEach cleans up
212
+ WASM dependencies mocked in tests to avoid loading .wasm files in CI
213
+ Integration tests run git commands in real temp repos
214
+
215
+ Coverage areas:
216
+ Schema validation (valid input accepted, invalid input rejected)
217
+ Graph traversal (correct hop distances, cycle handling, max-hop limits)
218
+ Risk scoring (known input/output pairs)
219
+ Fix scoping (never touches non-fixable violations)
220
+ Hook installation (append-safe, idempotent, clean uninstall)
221
+
222
+ Performance Characteristics
223
+
224
+ Typical query: 1-2 seconds end to end on a 2000-file TypeScript repo
225
+ Risk analysis: under 2 seconds (no API call)
226
+ Guard review: 5-15 seconds (LLM round-trip)
227
+ Package size: 450KB compressed, 2.8MB unpacked
228
+ Memory: under 100MB for typical repos
229
+ No background processes: runs, outputs, exits
package/README.md CHANGED
@@ -1,260 +1,209 @@
1
- # Skannr
2
-
3
- A CLI tool and MCP server that helps AI agents understand **any codebase**.
4
- It scans files, generates compressed structural "skeletons", and ranks results
5
- using hybrid retrieval — reducing token usage by ~96% vs full file scans.
6
-
7
- ## How It Works
8
-
9
- 1. You point it at any repo with `--root /path/to/repo`
10
- 2. It auto-detects the language (TypeScript, JavaScript, Python, or generic fallback)
11
- 3. It auto-discovers your project's modules from the folder structure
12
- 4. For your question, it ranks the most relevant files using lexical + structural + dependency-graph analysis
13
- 5. It strips function bodies from files (keeping types, signatures, imports)
14
- 6. Returns compact skeletons an AI can reason over without blowing the context window
15
-
16
- ## Install
17
-
18
- ```bash
19
- npm install -g skannr
20
- ```
21
-
22
- ## Usage
23
-
24
- ### CLI
25
-
26
- ```bash
27
- # Basic
28
- skannr --question "how does authentication work?" --root /path/to/any/repo
29
-
30
- # Limit results
31
- skannr --question "database connection setup" --root . --limit 5
32
-
33
- # Focus on specific modules (auto-discovered if not specified)
34
- skannr --question "permission checks" --root . --modules auth,middleware
35
-
36
- # Force language
37
- skannr --question "class structure" --root /path/to/python/project --lang python
38
-
39
- # Interactive agent mode
40
- skannr-agent --root /path/to/repo
41
-
42
- # Cache management
43
- skannr --cache-stats
44
- skannr --cache-clear
45
- skannr --question "..." --skip-cache
46
-
47
- # Output: human (default), markdown, or json
48
- skannr --question "..." --root . --format markdown
49
- skannr --question "..." --root . --format json
50
-
51
- # Repository health report (JSON on stdout)
52
- skannr --report --root .
53
-
54
- # Re-run analysis when files change (debounced; uses fresh analysis each pass)
55
- skannr --question "..." --root . --watch
56
- ```
57
-
58
- ### Watch mode (`--watch`)
59
-
60
- Use `--watch` when you want Skannr to stay running and **re-analyze after relevant file changes** (large repos you edit often). Changes under common build/vendor dirs are ignored (`node_modules`, `.git`, `dist`, etc.). Press **Ctrl+C** to stop.
61
-
62
- Each run bypasses the analysis cache so results reflect the tree as it is now.
63
-
64
- ### Anonymous telemetry (opt-in)
65
-
66
- Telemetry is **off unless you enable it** (or accept deferred opt-in — see below). It sends **only which CLI flags were used** — never your question text, file paths, or code.
67
-
68
- - Enable: `skannr --telemetry-on`
69
- - Disable: `skannr --telemetry-off`
70
-
71
- Settings are stored under `~/.skannr/config.json`. The first time you run an analyze command with no config yet, Skannr may show a short notice; you can opt in or out anytime with the flags above.
72
-
73
- If you take no action, telemetry may turn on automatically **after 7 days** from that notice unless you explicitly disabled it with `--telemetry-off`.
74
-
75
- ## MCP Server
76
-
77
- `skannr` ships with a built-in MCP server. One-time setup per AI tool:
78
-
79
- ### Cursor
80
-
81
- `~/.cursor/mcp.json`:
82
-
83
- ```json
84
- {
85
- "mcpServers": {
86
- "skannr": {
87
- "command": "npx",
88
- "args": ["-y", "skannr", "--mcp"]
89
- }
90
- }
91
- }
92
- ```
93
-
94
- ### Claude Desktop / Claude Code
95
-
96
- `~/Library/Application Support/Claude/claude_desktop_config.json`:
97
-
98
- ```json
99
- {
100
- "mcpServers": {
101
- "skannr": {
102
- "command": "npx",
103
- "args": ["-y", "skannr", "--mcp"]
104
- }
105
- }
106
- }
107
- ```
108
-
109
- ### Gemini CLI
110
-
111
- `~/.gemini/config.json`:
112
-
113
- ```json
114
- {
115
- "mcpServers": {
116
- "skannr": {
117
- "command": "npx",
118
- "args": ["-y", "skannr", "--mcp"]
119
- }
120
- }
121
- }
122
- ```
123
-
124
- Alternatively run the dedicated binary: `skannr-mcp` (same stdio server).
125
-
126
- ## Config File (optional)
127
-
128
- Drop a `code-analyzer.config.json` in your repo root:
129
-
130
- ```json
131
- {
132
- "modules": {
133
- "auth": ["src/auth", "lib/auth"],
134
- "api": ["src/api", "src/routes"]
135
- },
136
- "exclude": ["**/generated/**", "**/migrations/**"],
137
- "extensions": [".ts", ".js"],
138
- "defaultLimit": 10
139
- }
140
- ```
141
-
142
- ## Supported Languages
143
-
144
- | Language | Skeleton Support |
145
- |---|---|
146
- | TypeScript / TSX | Full AST (ts-morph) |
147
- | JavaScript / JSX | Full AST (ts-morph) |
148
- | Python | Regex-based (signatures + types) |
149
- | Others | First 50 lines (generic fallback) |
150
-
151
- ## Performance
152
-
153
- Benchmarked on large TypeScript monorepo:
154
-
155
- | Metric | Result |
156
- |---|---|
157
- | Token Reduction vs Full Scan | ~96.5% |
158
- | Token Reduction vs Top-N | ~78.2% |
159
- | Avg keyword coverage | ~75% |
160
- | Directory diversity (top-5) | ~80% |
161
- | Average execution time | ~1.2s |
162
-
163
- ## Interactive Agent Commands
164
-
165
- ```
166
- /help Show all commands
167
- /files List retrieved files
168
- /symbols <query> Search for symbols
169
- /symbol <id> Get full implementation
170
- /deps <file> Show imports for a file
171
- /refresh Re-analyze with new context
172
- /stats Show mapping statistics
173
- /exit Quit
174
- ```
175
-
176
- ## Blast Radius
177
-
178
- Analyze the downstream impact and risk of a code change. Given a git diff,
179
- blast-radius identifies which files are affected, how central they are in the
180
- dependency graph, and whether they have test coverage.
181
-
182
- **Limitation (v1):** Traversal is computed at file granularity — "file A
183
- imports file B" — not at function-call level. Function-level resolution is
184
- planned for a future version.
185
-
186
- ### CLI
187
-
188
- ```bash
189
- # Analyze working tree changes vs HEAD (default)
190
- skannr blast-radius --root .
191
-
192
- # Analyze a specific diff file, 3 hops deep
193
- skannr blast-radius --root . --diff changes.patch --hops 3
194
-
195
- # JSON output for CI pipelines
196
- skannr blast-radius --root . --json
197
- ```
198
-
199
- Sample terminal output:
200
-
201
- ```
202
- Blast Radius Analysis (2-hop traversal)
203
- ──────────────────────────────────────────────────
204
-
205
- Risk Score: 5.8/10
206
- Risk 5.8/10 (moderate): 1 file(s) changed, 4 downstream affected, 3 untested.
207
-
208
- Changed files:
209
- src/auth/session.ts
210
-
211
- Changed symbols:
212
- function validateToken (src/auth/session.ts)
213
-
214
- Hop 1 (2 files):
215
- src/middleware/auth-guard.ts centrality=0.85 [NO TEST]
216
- src/api/login.ts centrality=0.60
217
-
218
- Hop 2 (2 files):
219
- src/routes/index.ts centrality=0.45 [NO TEST]
220
- src/api/admin.ts centrality=0.30 [NO TEST]
221
-
222
- Formula inputs:
223
- affected_count (normalized): 0.080
224
- avg_centrality: 0.550
225
- untested_ratio: 0.750
226
- hop_spread (normalized): 1.000
227
- ```
228
-
229
- ### MCP
230
-
231
- The `blast_radius` tool is exposed via the same MCP server (`skannr --mcp`).
232
-
233
- Input schema:
234
-
235
- ```json
236
- {
237
- "root": "/path/to/repo",
238
- "diff": "<optional unified diff content>",
239
- "hops": 2
240
- }
241
- ```
242
-
243
- If `diff` is omitted, the tool runs `git diff HEAD` in the given root.
244
- The output is the same JSON structure as `skannr blast-radius --json`.
245
-
246
- ### Risk Score Formula
247
-
248
- ```
249
- risk = 2.5 × normalizedAffectedCount
250
- + 2.5 × avgCentrality
251
- + 3.5 × untestedRatio
252
- + 1.5 × normalizedMaxHopSpread
253
- ```
254
-
255
- | Input | Range | Meaning |
256
- |-------|-------|---------|
257
- | normalizedAffectedCount | 0–1 | Affected files / total project files |
258
- | avgCentrality | 0–1 | Mean in-degree centrality of affected files |
259
- | untestedRatio | 0–1 | Fraction of affected files with no test file |
260
- | normalizedMaxHopSpread | 0–1 | Deepest hop reached / max configured hops |
1
+ # Skannr
2
+
3
+ A CLI tool and MCP server that helps developers and AI assistants understand any codebase. Scans files, generates compressed structural skeletons, and ranks results using hybrid retrieval. Reduces token usage by 96.5% vs full file reads.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g skannr
9
+ ```
10
+
11
+ Or use without installing:
12
+
13
+ ```bash
14
+ npx skannr "how does auth work?"
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ### Ask about any codebase
20
+
21
+ ```bash
22
+ skannr "how does authentication work?"
23
+ skannr "database queries" -n 5
24
+ skannr "API endpoints" --json
25
+ skannr "class structure" --lang python
26
+ ```
27
+
28
+ No config needed. Auto-detects language and module structure.
29
+
30
+ ### Risk analysis
31
+
32
+ Check the downstream impact of your changes before pushing:
33
+
34
+ ```bash
35
+ skannr risk
36
+ skannr risk --json
37
+ skannr risk -n 3
38
+ skannr risk --diff feature.patch
39
+ ```
40
+
41
+ Walks the dependency graph, finds affected files, flags untested code, gives you a 0-10 score. No LLM, no API key, runs in under 2 seconds.
42
+
43
+ ### Guard (AI code review)
44
+
45
+ Reviews staged changes against coding rules at the symbol level:
46
+
47
+ ```bash
48
+ git add .
49
+ skannr guard
50
+ skannr guard --fix
51
+ skannr guard --fix --dry-run
52
+ skannr guard --pr-mode
53
+ skannr guard --json
54
+ ```
55
+
56
+ Auto-detects your coding agent (Claude, Gemini, Kiro, Ollama). No API key needed if you already have one installed. Works out of the box with built-in rules.
57
+
58
+ Install as a pre-commit hook:
59
+
60
+ ```bash
61
+ skannr guard install
62
+ skannr guard uninstall
63
+ ```
64
+
65
+ Custom rules (optional):
66
+
67
+ ```json
68
+ // .skannr/rules.json
69
+ {
70
+ "rules": [
71
+ {
72
+ "id": "no-mutations",
73
+ "description": "Do not mutate function arguments",
74
+ "severity": "high",
75
+ "fixable": false,
76
+ "category": "functional"
77
+ }
78
+ ]
79
+ }
80
+ ```
81
+
82
+ ### Other commands
83
+
84
+ ```bash
85
+ skannr report # repo health summary (JSON)
86
+ skannr agent # interactive exploration mode
87
+ skannr cache stats # cache hit rate
88
+ skannr cache clear # wipe cache
89
+ ```
90
+
91
+ ## Supported Languages
92
+
93
+ | Language | Support | Method |
94
+ |---|---|---|
95
+ | TypeScript / TSX | Full AST | ts-morph |
96
+ | JavaScript / JSX | Full AST | ts-morph |
97
+ | Python | Full AST | tree-sitter WASM |
98
+ | Go | Full AST | tree-sitter WASM |
99
+ | Rust | Full AST | tree-sitter WASM |
100
+ | Java | Full AST | tree-sitter WASM |
101
+ | Others | Basic | First 50 lines fallback |
102
+
103
+ No native build tools required. WASM grammars run everywhere npm runs.
104
+
105
+ ## MCP Server
106
+
107
+ Exposes three tools: `scan_codebase`, `blast_radius`, `guard_review`.
108
+
109
+ One-time setup for your AI tool:
110
+
111
+ ```json
112
+ {
113
+ "mcpServers": {
114
+ "skannr": {
115
+ "command": "npx",
116
+ "args": ["-y", "skannr", "--mcp"]
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ Works with Cursor, Claude Code, Gemini CLI, Kiro, VS Code Copilot, and any MCP-compatible tool.
123
+
124
+ After setup, just ask your assistant:
125
+ - "How does auth work in this repo?"
126
+ - "Review my staged changes"
127
+ - "What's the risk of my current diff?"
128
+
129
+ ## Risk Score Formula
130
+
131
+ ```
132
+ risk = 2.5 x (affected files / total files)
133
+ + 2.5 x (avg centrality of affected files)
134
+ + 3.5 x (% of affected files with no tests)
135
+ + 1.5 x (max hop reached / max hops configured)
136
+ ```
137
+
138
+ Deterministic. Same diff always produces the same score. CI-gateable:
139
+
140
+ ```bash
141
+ RISK=$(skannr risk --json | jq '.riskScore')
142
+ if [ $(echo "$RISK > 7" | bc -l) -eq 1 ]; then exit 1; fi
143
+ ```
144
+
145
+ ## Guard Providers
146
+
147
+ Guard auto-detects in this order (no config needed):
148
+
149
+ 1. `claude` CLI (uses your existing session)
150
+ 2. `gemini` CLI (uses your existing session)
151
+ 3. `kiro-cli` (uses your existing session)
152
+ 4. `ollama` (local, no auth)
153
+ 5. Gemini API (set `GEMINI_API_KEY`)
154
+ 6. OpenAI API (set `OPENAI_API_KEY`)
155
+
156
+ Override with `SKANNR_GUARD_PROVIDER` env var or `.skannr/guard.json`.
157
+
158
+ ## Config File (optional)
159
+
160
+ ```json
161
+ // code-analyzer.config.json (project root)
162
+ {
163
+ "modules": {
164
+ "auth": ["src/auth", "lib/auth"],
165
+ "api": ["src/api", "src/routes"]
166
+ },
167
+ "exclude": ["**/generated/**", "**/migrations/**"],
168
+ "extensions": [".ts", ".js"],
169
+ "defaultLimit": 10
170
+ }
171
+ ```
172
+
173
+ ## Performance
174
+
175
+ | Metric | Result |
176
+ |---|---|
177
+ | Token reduction vs full scan | 96.5% |
178
+ | Typical query time | 1-2 seconds |
179
+ | Risk analysis | under 2 seconds (no API call) |
180
+ | Guard review | 5-15 seconds (LLM round-trip) |
181
+ | Package size | 450KB compressed |
182
+
183
+ ## Interactive Agent
184
+
185
+ ```bash
186
+ skannr agent
187
+ ```
188
+
189
+ Commands inside the agent:
190
+
191
+ ```
192
+ /files List retrieved files
193
+ /symbols <query> Search for symbols
194
+ /symbol <id> Get full implementation
195
+ /deps <file> Show imports for a file
196
+ /refresh Re-analyze with new context
197
+ /stats Show mapping statistics
198
+ /exit Quit
199
+ ```
200
+
201
+ Requires `GEMINI_API_KEY` for the interactive agent mode.
202
+
203
+ ## Architecture
204
+
205
+ See [ARCHITECTURE.md](./ARCHITECTURE.md) for design decisions, technology choices, and the reasoning behind each one.
206
+
207
+ ## License
208
+
209
+ MIT
@@ -1 +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"}
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;AA2OtF;;;GAGG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,SAAS,EAAE,EAClB,KAAK,EAAE,cAAc,EAAE,GACtB,OAAO,CAAC,cAAc,CAAC,CAgEzB"}
@@ -109,6 +109,82 @@ function extractJson(text) {
109
109
  }
110
110
  return text.slice(firstBrace, lastBrace + 1);
111
111
  }
112
+ /**
113
+ * Call a CLI-based provider (claude, gemini, kiro).
114
+ * These use the user's existing authenticated session — no API key needed.
115
+ */
116
+ async function callCli(command, fullPrompt) {
117
+ const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
118
+ const fs = await Promise.resolve().then(() => __importStar(require('fs')));
119
+ const path = await Promise.resolve().then(() => __importStar(require('path')));
120
+ const os = await Promise.resolve().then(() => __importStar(require('os')));
121
+ // Write prompt to a temp file (avoids ARG_MAX limits on large diffs)
122
+ const tmpFile = path.join(os.tmpdir(), `skannr-guard-${Date.now()}.txt`);
123
+ fs.writeFileSync(tmpFile, fullPrompt, 'utf-8');
124
+ try {
125
+ let result;
126
+ switch (command) {
127
+ case 'claude-cli':
128
+ // Claude CLI accepts prompt via stdin pipe with --print flag
129
+ result = execSync(`cat "${tmpFile}" | claude --print`, {
130
+ encoding: 'utf-8',
131
+ timeout: 120_000,
132
+ maxBuffer: 10 * 1024 * 1024,
133
+ });
134
+ break;
135
+ case 'gemini-cli':
136
+ // Gemini CLI accepts prompt via -p flag
137
+ result = execSync(`gemini -p "$(cat "${tmpFile}")"`, {
138
+ encoding: 'utf-8',
139
+ timeout: 120_000,
140
+ maxBuffer: 10 * 1024 * 1024,
141
+ });
142
+ break;
143
+ case 'kiro-cli':
144
+ // Kiro CLI accepts prompt via stdin in non-interactive mode
145
+ result = execSync(`cat "${tmpFile}" | kiro-cli chat --no-interactive "Review and respond with JSON only."`, {
146
+ encoding: 'utf-8',
147
+ timeout: 120_000,
148
+ maxBuffer: 10 * 1024 * 1024,
149
+ });
150
+ break;
151
+ default:
152
+ throw new Error(`Unknown CLI provider: ${command}`);
153
+ }
154
+ return result;
155
+ }
156
+ finally {
157
+ try {
158
+ fs.unlinkSync(tmpFile);
159
+ }
160
+ catch { }
161
+ }
162
+ }
163
+ /**
164
+ * Call Ollama local inference.
165
+ */
166
+ async function callOllama(model, systemPrompt, userPrompt) {
167
+ const baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
168
+ const url = `${baseUrl}/api/chat`;
169
+ const response = await fetch(url, {
170
+ method: 'POST',
171
+ headers: { 'Content-Type': 'application/json' },
172
+ body: JSON.stringify({
173
+ model: model || 'llama3',
174
+ messages: [
175
+ { role: 'system', content: systemPrompt },
176
+ { role: 'user', content: userPrompt },
177
+ ],
178
+ stream: false,
179
+ format: 'json',
180
+ }),
181
+ });
182
+ if (!response.ok) {
183
+ throw new Error(`Ollama error: ${response.status} ${response.statusText}`);
184
+ }
185
+ const data = await response.json();
186
+ return data.message?.content ?? '';
187
+ }
112
188
  /**
113
189
  * Call Gemini API with structured output enforcement.
114
190
  */
@@ -175,9 +251,24 @@ async function runLlmReview(config, rules, units) {
175
251
  : `${userPrompt}\n\n[RETRY: Your last response didn't match the required schema. Error: ${lastError}. Return ONLY valid JSON matching the schema.]`;
176
252
  let rawResponse;
177
253
  try {
178
- rawResponse = config.provider === 'openai'
179
- ? await callOpenAI(config, systemPrompt, prompt)
180
- : await callGemini(config, systemPrompt, prompt);
254
+ switch (config.provider) {
255
+ case 'openai':
256
+ rawResponse = await callOpenAI(config, systemPrompt, prompt);
257
+ break;
258
+ case 'gemini':
259
+ rawResponse = await callGemini(config, systemPrompt, prompt);
260
+ break;
261
+ case 'ollama':
262
+ rawResponse = await callOllama(config.model, systemPrompt, prompt);
263
+ break;
264
+ case 'claude-cli':
265
+ case 'gemini-cli':
266
+ case 'kiro-cli':
267
+ rawResponse = await callCli(config.provider, systemPrompt + '\n\n' + prompt);
268
+ break;
269
+ default:
270
+ throw new Error(`Unsupported provider: ${config.provider}`);
271
+ }
181
272
  }
182
273
  catch (err) {
183
274
  throw new Error(`Provider call failed (${config.provider}): ${err instanceof Error ? err.message : String(err)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/guard/provider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/guard/provider.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"rules-loader.d.ts","sourceRoot":"","sources":["../../src/guard/rules-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAwDtD,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAwBnD;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA2BzD"}
1
+ {"version":3,"file":"rules-loader.d.ts","sourceRoot":"","sources":["../../src/guard/rules-loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAwDtD,+FAA+F;AAC/F,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,CAwBnD;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CA8BzD"}
@@ -42,7 +42,6 @@ exports.loadGuardConfig = loadGuardConfig;
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
44
  const schema_1 = require("./schema");
45
- const schema_2 = require("./schema");
46
45
  /** Default locations to search for the rules file. */
47
46
  const RULES_PATHS = [
48
47
  '.skannr/rules.json',
@@ -130,18 +129,70 @@ function loadGuardConfig(root) {
130
129
  }
131
130
  }
132
131
  // Env vars override file config
133
- const provider = (process.env.SKANNR_GUARD_PROVIDER || rawConfig.provider || 'gemini');
134
- const model = (process.env.SKANNR_GUARD_MODEL || rawConfig.model || 'gemini-2.0-flash-exp');
132
+ const explicitProvider = process.env.SKANNR_GUARD_PROVIDER || rawConfig.provider;
133
+ const model = (process.env.SKANNR_GUARD_MODEL || rawConfig.model || '');
135
134
  const apiKey = process.env.SKANNR_GUARD_API_KEY
136
135
  || process.env.GEMINI_API_KEY
137
136
  || process.env.OPENAI_API_KEY
138
137
  || rawConfig.apiKey;
139
138
  const baseUrl = process.env.OPENAI_BASE_URL || rawConfig.baseUrl;
140
- const result = schema_2.GuardConfigSchema.safeParse({ provider, model, apiKey, baseUrl });
141
- if (!result.success) {
142
- throw new Error(`Invalid guard config: ${result.error.issues.map((i) => i.message).join(', ')}`);
139
+ // Auto-detect provider if not explicitly set
140
+ const provider = explicitProvider || autoDetectProvider(apiKey);
141
+ return {
142
+ provider: provider,
143
+ model: model || getDefaultModel(provider),
144
+ apiKey,
145
+ baseUrl,
146
+ };
147
+ }
148
+ /**
149
+ * Auto-detect the best available provider.
150
+ * Priority: existing CLI tools (free, already authenticated) > API keys.
151
+ */
152
+ function autoDetectProvider(apiKey) {
153
+ const { execSync } = require('child_process');
154
+ // Check for CLI tools first (no API key needed)
155
+ const cliChecks = [
156
+ ['claude', 'claude-cli'],
157
+ ['gemini', 'gemini-cli'],
158
+ ['kiro-cli', 'kiro-cli'],
159
+ ];
160
+ for (const [cmd, provider] of cliChecks) {
161
+ try {
162
+ execSync(`${cmd} --version`, { stdio: 'ignore', timeout: 3000 });
163
+ return provider;
164
+ }
165
+ catch {
166
+ // Not available, try next
167
+ }
168
+ }
169
+ // Check for Ollama (local, no key needed)
170
+ try {
171
+ execSync('ollama --version', { stdio: 'ignore', timeout: 3000 });
172
+ return 'ollama';
173
+ }
174
+ catch {
175
+ // Not available
176
+ }
177
+ // Fall back to API-based providers if key is available
178
+ if (apiKey) {
179
+ if (process.env.GEMINI_API_KEY)
180
+ return 'gemini';
181
+ if (process.env.OPENAI_API_KEY)
182
+ return 'openai';
183
+ return 'gemini'; // default API provider
184
+ }
185
+ // Nothing found — default to gemini-cli and let it fail with a clear error
186
+ return 'gemini-cli';
187
+ }
188
+ /** Get the default model for a provider. */
189
+ function getDefaultModel(provider) {
190
+ switch (provider) {
191
+ case 'gemini': return 'gemini-2.0-flash-exp';
192
+ case 'openai': return 'gpt-4o';
193
+ case 'ollama': return 'llama3';
194
+ default: return ''; // CLI providers don't need a model specified
143
195
  }
144
- return result.data;
145
196
  }
146
197
  /** Find the first existing rules file in the project. */
147
198
  function findRulesFile(root) {
@@ -1 +1 @@
1
- {"version":3,"file":"rules-loader.js","sourceRoot":"","sources":["../../src/guard/rules-loader.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rules-loader.js","sourceRoot":"","sources":["../../src/guard/rules-loader.ts"],"names":[],"mappings":"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"}
@@ -164,19 +164,19 @@ export declare const ReviewResponseSchema: z.ZodObject<{
164
164
  summary: string;
165
165
  }>;
166
166
  export declare const GuardConfigSchema: z.ZodObject<{
167
- provider: z.ZodDefault<z.ZodEnum<["gemini", "openai"]>>;
167
+ provider: z.ZodDefault<z.ZodEnum<["gemini", "openai", "claude-cli", "gemini-cli", "kiro-cli", "ollama"]>>;
168
168
  model: z.ZodDefault<z.ZodString>;
169
169
  apiKey: z.ZodOptional<z.ZodString>;
170
170
  baseUrl: z.ZodOptional<z.ZodString>;
171
171
  }, "strip", z.ZodTypeAny, {
172
172
  model: string;
173
- provider: "gemini" | "openai";
173
+ provider: "gemini" | "openai" | "claude-cli" | "gemini-cli" | "kiro-cli" | "ollama";
174
174
  apiKey?: string | undefined;
175
175
  baseUrl?: string | undefined;
176
176
  }, {
177
177
  apiKey?: string | undefined;
178
178
  model?: string | undefined;
179
- provider?: "gemini" | "openai" | undefined;
179
+ provider?: "gemini" | "openai" | "claude-cli" | "gemini-cli" | "kiro-cli" | "ollama" | undefined;
180
180
  baseUrl?: string | undefined;
181
181
  }>;
182
182
  //# sourceMappingURL=schema.d.ts.map
@@ -45,8 +45,8 @@ exports.ReviewResponseSchema = zod_1.z.object({
45
45
  // Guard config schema (optional .skannr/guard.json)
46
46
  // ---------------------------------------------------------------------------
47
47
  exports.GuardConfigSchema = zod_1.z.object({
48
- provider: zod_1.z.enum(['gemini', 'openai']).default('gemini'),
49
- model: zod_1.z.string().default('gemini-2.0-flash-exp'),
48
+ provider: zod_1.z.enum(['gemini', 'openai', 'claude-cli', 'gemini-cli', 'kiro-cli', 'ollama']).default('auto'),
49
+ model: zod_1.z.string().default(''),
50
50
  apiKey: zod_1.z.string().optional(),
51
51
  baseUrl: zod_1.z.string().optional(),
52
52
  });
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/guard/schema.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,6BAAwB;AAExB,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAEjE,QAAA,cAAc,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;AAE/D,QAAA,UAAU,GAAG,OAAC,CAAC,MAAM,CAAC;IACjC,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,QAAQ,EAAE,sBAAc;IACxB,OAAO,EAAE,OAAC,CAAC,OAAO,EAAE;IACpB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC5B,CAAC,CAAC;AAEU,QAAA,eAAe,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,KAAK,EAAE,OAAC,CAAC,KAAK,CAAC,kBAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAClC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,kCAAkC;AAClC,8EAA8E;AAEjE,QAAA,eAAe,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC1C,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACxC,QAAQ,EAAE,sBAAc;IACxB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,OAAC,CAAC,OAAO,EAAE;IACpB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAEU,QAAA,oBAAoB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3C,UAAU,EAAE,OAAC,CAAC,KAAK,CAAC,uBAAe,CAAC;IACpC,MAAM,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC3B,CAAC,CAAC;AAEH,8EAA8E;AAC9E,oDAAoD;AACpD,8EAA8E;AAEjE,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,QAAQ,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACxD,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,sBAAsB,CAAC;IACjD,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAC"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/guard/schema.ts"],"names":[],"mappings":";AAAA;;;;GAIG;;;AAEH,6BAAwB;AAExB,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAEjE,QAAA,cAAc,GAAG,OAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;AAE/D,QAAA,UAAU,GAAG,OAAC,CAAC,MAAM,CAAC;IACjC,EAAE,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,WAAW,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,QAAQ,EAAE,sBAAc;IACxB,OAAO,EAAE,OAAC,CAAC,OAAO,EAAE;IACpB,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC5B,CAAC,CAAC;AAEU,QAAA,eAAe,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,KAAK,EAAE,OAAC,CAAC,KAAK,CAAC,kBAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAClC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,kCAAkC;AAClC,8EAA8E;AAEjE,QAAA,eAAe,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;IAClB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IAC1C,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE;IACxC,QAAQ,EAAE,sBAAc;IACxB,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpC,OAAO,EAAE,OAAC,CAAC,OAAO,EAAE;IACpB,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,aAAa,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACrC,CAAC,CAAC;AAEU,QAAA,oBAAoB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC3C,UAAU,EAAE,OAAC,CAAC,KAAK,CAAC,uBAAe,CAAC;IACpC,MAAM,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CAC3B,CAAC,CAAC;AAEH,8EAA8E;AAC9E,oDAAoD;AACpD,8EAA8E;AAEjE,QAAA,iBAAiB,GAAG,OAAC,CAAC,MAAM,CAAC;IACxC,QAAQ,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,MAAa,CAAC;IAC/G,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7B,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAC"}
@@ -50,11 +50,11 @@ export interface GuardResult {
50
50
  }
51
51
  /** Guard configuration (provider, model, etc.). */
52
52
  export interface GuardConfig {
53
- /** LLM provider: 'gemini' | 'openai' */
54
- provider: 'gemini' | 'openai';
53
+ /** LLM provider: 'gemini' | 'openai' | 'claude-cli' | 'gemini-cli' | 'kiro-cli' | 'ollama' */
54
+ provider: 'gemini' | 'openai' | 'claude-cli' | 'gemini-cli' | 'kiro-cli' | 'ollama';
55
55
  /** Model name (e.g. 'gemini-2.0-flash-exp', 'gpt-4o'). */
56
56
  model: string;
57
- /** API key (resolved from env if not set). */
57
+ /** API key (resolved from env if not set). Only needed for 'gemini' and 'openai' providers. */
58
58
  apiKey?: string;
59
59
  /** OpenAI-compatible base URL (for openai provider). */
60
60
  baseUrl?: string;
@@ -1 +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"}
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,8FAA8F;IAC9F,QAAQ,EAAE,QAAQ,GAAG,QAAQ,GAAG,YAAY,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAC;IACpF,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAC;IACd,+FAA+F;IAC/F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skannr",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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",
@@ -79,6 +79,97 @@ function extractJson(text: string): string {
79
79
  return text.slice(firstBrace, lastBrace + 1);
80
80
  }
81
81
 
82
+ /**
83
+ * Call a CLI-based provider (claude, gemini, kiro).
84
+ * These use the user's existing authenticated session — no API key needed.
85
+ */
86
+ async function callCli(
87
+ command: string,
88
+ fullPrompt: string,
89
+ ): Promise<string> {
90
+ const { execSync } = await import('child_process');
91
+ const fs = await import('fs');
92
+ const path = await import('path');
93
+ const os = await import('os');
94
+
95
+ // Write prompt to a temp file (avoids ARG_MAX limits on large diffs)
96
+ const tmpFile = path.join(os.tmpdir(), `skannr-guard-${Date.now()}.txt`);
97
+ fs.writeFileSync(tmpFile, fullPrompt, 'utf-8');
98
+
99
+ try {
100
+ let result: string;
101
+
102
+ switch (command) {
103
+ case 'claude-cli':
104
+ // Claude CLI accepts prompt via stdin pipe with --print flag
105
+ result = execSync(`cat "${tmpFile}" | claude --print`, {
106
+ encoding: 'utf-8',
107
+ timeout: 120_000,
108
+ maxBuffer: 10 * 1024 * 1024,
109
+ });
110
+ break;
111
+
112
+ case 'gemini-cli':
113
+ // Gemini CLI accepts prompt via -p flag
114
+ result = execSync(`gemini -p "$(cat "${tmpFile}")"`, {
115
+ encoding: 'utf-8',
116
+ timeout: 120_000,
117
+ maxBuffer: 10 * 1024 * 1024,
118
+ });
119
+ break;
120
+
121
+ case 'kiro-cli':
122
+ // Kiro CLI accepts prompt via stdin in non-interactive mode
123
+ result = execSync(`cat "${tmpFile}" | kiro-cli chat --no-interactive "Review and respond with JSON only."`, {
124
+ encoding: 'utf-8',
125
+ timeout: 120_000,
126
+ maxBuffer: 10 * 1024 * 1024,
127
+ });
128
+ break;
129
+
130
+ default:
131
+ throw new Error(`Unknown CLI provider: ${command}`);
132
+ }
133
+
134
+ return result;
135
+ } finally {
136
+ try { fs.unlinkSync(tmpFile); } catch {}
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Call Ollama local inference.
142
+ */
143
+ async function callOllama(
144
+ model: string,
145
+ systemPrompt: string,
146
+ userPrompt: string,
147
+ ): Promise<string> {
148
+ const baseUrl = process.env.OLLAMA_BASE_URL || 'http://localhost:11434';
149
+ const url = `${baseUrl}/api/chat`;
150
+
151
+ const response = await fetch(url, {
152
+ method: 'POST',
153
+ headers: { 'Content-Type': 'application/json' },
154
+ body: JSON.stringify({
155
+ model: model || 'llama3',
156
+ messages: [
157
+ { role: 'system', content: systemPrompt },
158
+ { role: 'user', content: userPrompt },
159
+ ],
160
+ stream: false,
161
+ format: 'json',
162
+ }),
163
+ });
164
+
165
+ if (!response.ok) {
166
+ throw new Error(`Ollama error: ${response.status} ${response.statusText}`);
167
+ }
168
+
169
+ const data = await response.json() as any;
170
+ return data.message?.content ?? '';
171
+ }
172
+
82
173
  /**
83
174
  * Call Gemini API with structured output enforcement.
84
175
  */
@@ -170,9 +261,24 @@ export async function runLlmReview(
170
261
 
171
262
  let rawResponse: string;
172
263
  try {
173
- rawResponse = config.provider === 'openai'
174
- ? await callOpenAI(config, systemPrompt, prompt)
175
- : await callGemini(config, systemPrompt, prompt);
264
+ switch (config.provider) {
265
+ case 'openai':
266
+ rawResponse = await callOpenAI(config, systemPrompt, prompt);
267
+ break;
268
+ case 'gemini':
269
+ rawResponse = await callGemini(config, systemPrompt, prompt);
270
+ break;
271
+ case 'ollama':
272
+ rawResponse = await callOllama(config.model, systemPrompt, prompt);
273
+ break;
274
+ case 'claude-cli':
275
+ case 'gemini-cli':
276
+ case 'kiro-cli':
277
+ rawResponse = await callCli(config.provider, systemPrompt + '\n\n' + prompt);
278
+ break;
279
+ default:
280
+ throw new Error(`Unsupported provider: ${config.provider}`);
281
+ }
176
282
  } catch (err) {
177
283
  throw new Error(
178
284
  `Provider call failed (${config.provider}): ${err instanceof Error ? err.message : String(err)}`,
@@ -103,20 +103,75 @@ export function loadGuardConfig(root: string): GuardConfig {
103
103
  }
104
104
 
105
105
  // Env vars override file config
106
- const provider = (process.env.SKANNR_GUARD_PROVIDER || rawConfig.provider || 'gemini') as string;
107
- const model = (process.env.SKANNR_GUARD_MODEL || rawConfig.model || 'gemini-2.0-flash-exp') as string;
106
+ const explicitProvider = process.env.SKANNR_GUARD_PROVIDER || rawConfig.provider as string | undefined;
107
+ const model = (process.env.SKANNR_GUARD_MODEL || rawConfig.model || '') as string;
108
108
  const apiKey = process.env.SKANNR_GUARD_API_KEY
109
109
  || process.env.GEMINI_API_KEY
110
110
  || process.env.OPENAI_API_KEY
111
111
  || (rawConfig.apiKey as string | undefined);
112
112
  const baseUrl = process.env.OPENAI_BASE_URL || (rawConfig.baseUrl as string | undefined);
113
113
 
114
- const result = GuardConfigSchema.safeParse({ provider, model, apiKey, baseUrl });
115
- if (!result.success) {
116
- throw new Error(`Invalid guard config: ${result.error.issues.map((i) => i.message).join(', ')}`);
114
+ // Auto-detect provider if not explicitly set
115
+ const provider = explicitProvider || autoDetectProvider(apiKey);
116
+
117
+ return {
118
+ provider: provider as GuardConfig['provider'],
119
+ model: model || getDefaultModel(provider),
120
+ apiKey,
121
+ baseUrl,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Auto-detect the best available provider.
127
+ * Priority: existing CLI tools (free, already authenticated) > API keys.
128
+ */
129
+ function autoDetectProvider(apiKey?: string): string {
130
+ const { execSync } = require('child_process');
131
+
132
+ // Check for CLI tools first (no API key needed)
133
+ const cliChecks: Array<[string, string]> = [
134
+ ['claude', 'claude-cli'],
135
+ ['gemini', 'gemini-cli'],
136
+ ['kiro-cli', 'kiro-cli'],
137
+ ];
138
+
139
+ for (const [cmd, provider] of cliChecks) {
140
+ try {
141
+ execSync(`${cmd} --version`, { stdio: 'ignore', timeout: 3000 });
142
+ return provider;
143
+ } catch {
144
+ // Not available, try next
145
+ }
146
+ }
147
+
148
+ // Check for Ollama (local, no key needed)
149
+ try {
150
+ execSync('ollama --version', { stdio: 'ignore', timeout: 3000 });
151
+ return 'ollama';
152
+ } catch {
153
+ // Not available
117
154
  }
118
155
 
119
- return result.data as GuardConfig;
156
+ // Fall back to API-based providers if key is available
157
+ if (apiKey) {
158
+ if (process.env.GEMINI_API_KEY) return 'gemini';
159
+ if (process.env.OPENAI_API_KEY) return 'openai';
160
+ return 'gemini'; // default API provider
161
+ }
162
+
163
+ // Nothing found — default to gemini-cli and let it fail with a clear error
164
+ return 'gemini-cli';
165
+ }
166
+
167
+ /** Get the default model for a provider. */
168
+ function getDefaultModel(provider: string): string {
169
+ switch (provider) {
170
+ case 'gemini': return 'gemini-2.0-flash-exp';
171
+ case 'openai': return 'gpt-4o';
172
+ case 'ollama': return 'llama3';
173
+ default: return ''; // CLI providers don't need a model specified
174
+ }
120
175
  }
121
176
 
122
177
  /** Find the first existing rules file in the project. */
@@ -52,8 +52,8 @@ export const ReviewResponseSchema = z.object({
52
52
  // ---------------------------------------------------------------------------
53
53
 
54
54
  export const GuardConfigSchema = z.object({
55
- provider: z.enum(['gemini', 'openai']).default('gemini'),
56
- model: z.string().default('gemini-2.0-flash-exp'),
55
+ provider: z.enum(['gemini', 'openai', 'claude-cli', 'gemini-cli', 'kiro-cli', 'ollama']).default('auto' as any),
56
+ model: z.string().default(''),
57
57
  apiKey: z.string().optional(),
58
58
  baseUrl: z.string().optional(),
59
59
  });
@@ -57,11 +57,11 @@ export interface GuardResult {
57
57
 
58
58
  /** Guard configuration (provider, model, etc.). */
59
59
  export interface GuardConfig {
60
- /** LLM provider: 'gemini' | 'openai' */
61
- provider: 'gemini' | 'openai';
60
+ /** LLM provider: 'gemini' | 'openai' | 'claude-cli' | 'gemini-cli' | 'kiro-cli' | 'ollama' */
61
+ provider: 'gemini' | 'openai' | 'claude-cli' | 'gemini-cli' | 'kiro-cli' | 'ollama';
62
62
  /** Model name (e.g. 'gemini-2.0-flash-exp', 'gpt-4o'). */
63
63
  model: string;
64
- /** API key (resolved from env if not set). */
64
+ /** API key (resolved from env if not set). Only needed for 'gemini' and 'openai' providers. */
65
65
  apiKey?: string;
66
66
  /** OpenAI-compatible base URL (for openai provider). */
67
67
  baseUrl?: string;