Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions .cursor/rules/vfs-agent-search.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ alwaysApply: true

## How vfs Works

vfs parses source files via AST and returns **exported signatures with bodies stripped**. It supports Go, JS, TS, Python, Rust, Java, HCL, Dockerfile, Protobuf, SQL, and YAML.
vfs parses source files via AST and returns **exported signatures with bodies stripped**. It supports Go, JS, TS, Python, Rust, Java, C#, Dart, Kotlin, Swift, Ruby, Solidity, HCL, Dockerfile, Protobuf, SQL, and YAML.

**What vfs gives you:** `internal/services/fare.go:42: func CalculateFare(req *FareRequest) (*FareResponse, error)`
**What vfs hides:** The 50 lines of implementation inside that function.
Expand Down Expand Up @@ -131,7 +131,7 @@ Use Grep/Read directly when:

## Generated Files

vfs automatically skips protobuf-generated files (`*.pb.go`, `*_pb2.py`, `*_pb.js`, `*_pb.ts`, `*.pb.dart`, `*_pb.rb`, etc.) and other common generated patterns (`.g.dart`, `.generated.cs`, `.freezed.dart`). They will not appear in search results.
vfs automatically skips protobuf-generated files (`*.pb.go`, `*_pb2.py`, `*_pb.js`, `*_pb.ts`, `*.pb.dart`, `*_pb.rb`, etc.), other common generated patterns (`.g.dart`, `.generated.cs`, `.freezed.dart`), and Solidity test/script files (`*.t.sol`, `*.s.sol` from Foundry). They will not appear in search results.

If you encounter a generated file through Grep or Read, **do not modify it** — find and edit the source (`.proto`, codegen config) instead.

Expand Down Expand Up @@ -229,6 +229,12 @@ Read: handlers/upload.go L42-60 ← user gave exact file path
| Python | `.py` |
| Rust | `.rs` |
| Java | `.java` |
| C# | `.cs` |
| Dart | `.dart` |
| Kotlin | `.kt`, `.kts` |
| Swift | `.swift` |
| Ruby | `.rb` |
| Solidity | `.sol` |
| HCL / Terraform | `.tf`, `.hcl` |
| Dockerfile | `Dockerfile`, `Dockerfile.*`, `*.dockerfile` |
| Protobuf | `.proto` |
Expand Down
48 changes: 37 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

## What is vfs?

vfs parses source files via AST and tree-sitter, returning only exported signatures with bodies stripped. This reduces token consumption by **60-70%** compared to grep-based search. It supports Go, JS, TS, Python, Rust, Java, C#, Dart, HCL, Dockerfile, Protobuf, SQL, and YAML.
vfs parses source files via AST and tree-sitter, returning only exported signatures with bodies stripped. This reduces token consumption by **60-70%** compared to grep-based search. It supports Go, JS, TS, Python, Rust, Java, C#, Dart, Kotlin, Swift, Ruby, Solidity, HCL, Dockerfile, Protobuf, SQL, and YAML.

## Security & Privacy

Expand Down Expand Up @@ -70,17 +70,41 @@ Every search for function definitions, method signatures, class names, or type d

### Decision Flow

```mermaid
flowchart TD
Start["Agent needs to find code"] --> SkipCheck{"Skip condition?"}

SkipCheck -->|"Known file/line,\nbody search,\nnon-code file"| GrepDirect["Grep / Read directly"]
SkipCheck -->|"Looking for definitions,\nsignatures, types"| TryVfs["vfs search"]

TryVfs --> McpFirst{"MCP available?"}
McpFirst -->|Yes| McpCall["MCP search\n(preferred)"]
McpFirst -->|No| CliFallback{"CLI available?"}
CliFallback -->|Yes| CliCall["vfs path -f pattern"]
CliFallback -->|No| GrepDirect

McpCall --> Found{"Results?"}
CliCall --> Found

Found -->|Yes| ReadExact["Read exact file:line\n(targeted, minimal tokens)"]
Found -->|No| GrepDirect

ReadExact --> NeedCallers{"Modifying code?"}
NeedCallers -->|Yes| GrepCallers["Grep for callers/usages"]
NeedCallers -->|No| Done["Done -- full context\nwith minimal tokens"]
GrepCallers --> Done
GrepDirect --> Done
```
User asks about code
├─ Skip condition matches? → Read/Grep directly
└─ Otherwise:
1. MCP → search(paths, pattern) ← preferred, works in sandbox
CLI → vfs <path> -f <name> ← fallback
2. Found? → Read exact file + line range
3. Nothing? → Fall back to Grep
```

**Why this matters:**

| Approach | Output | Est. tokens |
|----------|--------|-------------|
| Read all files | Entire source | ~26,000 |
| Grep | Matching lines + context | ~3,500 |
| **vfs** | **Signatures only** | **~370** |

vfs gives the agent a "table of contents" via AST. Grep fills the gap for things AST can't see (string literals, error messages, callers). Together they give full context at 90%+ token savings.

## How to Use

Expand Down Expand Up @@ -169,6 +193,7 @@ vfs . -f user
→ src/hooks/useUser.ts:8: export function useUser(id: string): UserState
→ app/models/user.py:15: class User(BaseModel)
→ src/api/UserService.java:22: public class UserService
→ contracts/UserRegistry.sol:10: contract UserRegistry is Ownable { ... }
```

**When grep IS correct:**
Expand Down Expand Up @@ -238,6 +263,7 @@ See [README.md](README.md#setup-for-ai-tools) for detailed per-tool setup instru
| Kotlin | `.kt`, `.kts` |
| Swift | `.swift` |
| Ruby | `.rb` |
| Solidity | `.sol` |
| HCL/Terraform | `.tf`, `.hcl` |
| Dockerfile | `Dockerfile`, `Dockerfile.*`, `*.dockerfile` |
| Protobuf | `.proto` |
Expand Down
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,31 @@ It works with any AI coding tool -- Cursor, Claude Code, Antigravity, Windsurf,

## How It Works

```mermaid
flowchart TD
A["Agent classifies intent"] --> B{"Intent?"}

B -->|Locate| C["vfs search"]
B -->|Understand| C
B -->|Modify| C
B -->|Debug| D["Grep / Read"]

C --> E["file:line + signature\n~370 tokens"]
E --> F{"Need behavior\nor just location?"}

F -->|"Location only"| Done["Done"]
F -->|"Need behavior"| G["Read exact lines\n(body + imports)"]

G --> H{"Modifying?"}
H -->|Yes| I["Grep for callers"]
H -->|No| Done

I --> Done
D --> Done
```

> The agent classifies its intent first. For **Locate**, **Understand**, and **Modify** intents, vfs runs first to get signatures (~370 tokens vs ~26,000 for reading files). Only then does the agent Read exact lines or Grep for callers as needed. For **Debug** intent, Grep goes first since you need to search inside function bodies.

Given a Go project with thousands of lines, asking "where is the login handler?" traditionally means grepping or reading entire files. vfs gives you just the signatures:

```
Expand All @@ -27,7 +52,7 @@ internal/middleware/jwt.go:45: func RequireLogin(next http.Handler) http.Handle

Each line tells you the **file**, **line number**, and **full signature** -- no function bodies, no imports, no noise. You (or your AI agent) can then read only the exact lines needed.

This works across 13 languages:
This works across 17 languages:

```
$ vfs ./frontend -f auth
Expand Down Expand Up @@ -82,6 +107,7 @@ vfs bench -f Login /path/to/project --show-output # show actual output
| Kotlin | `.kt`, `.kts` | tree-sitter |
| Swift | `.swift` | tree-sitter |
| Ruby | `.rb` | tree-sitter |
| Solidity | `.sol` | tree-sitter |
| HCL / Terraform | `.tf`, `.hcl` | tree-sitter |
| Dockerfile | `Dockerfile`, `Dockerfile.*` | line-based |
| Protobuf | `.proto` | line-based |
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.12
1.2.12
5 changes: 3 additions & 2 deletions cmd/vfs/mcp_instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

## How vfs Works

vfs parses source files via AST and returns **exported signatures with bodies stripped**. It supports Go, JS, TS, Python, Rust, Java, C#, Dart, Kotlin, Swift, Ruby, HCL, Dockerfile, Protobuf, SQL, and YAML.
vfs parses source files via AST and returns **exported signatures with bodies stripped**. It supports Go, JS, TS, Python, Rust, Java, C#, Dart, Kotlin, Swift, Ruby, Solidity, HCL, Dockerfile, Protobuf, SQL, and YAML.

**What vfs gives you:** `internal/services/fare.go:42: func CalculateFare(req *FareRequest) (*FareResponse, error)`
**What vfs hides:** The 50 lines of implementation inside that function.
Expand Down Expand Up @@ -103,7 +103,7 @@ Use Grep/Read directly when:

## Generated Files

vfs automatically skips protobuf-generated files (`*.pb.go`, `*_pb2.py`, `*_pb.js`, `*_pb.ts`, `*.pb.dart`, `*_pb.rb`, etc.) and other common generated patterns (`.g.dart`, `.generated.cs`, `.freezed.dart`). They will not appear in search results.
vfs automatically skips protobuf-generated files (`*.pb.go`, `*_pb2.py`, `*_pb.js`, `*_pb.ts`, `*.pb.dart`, `*_pb.rb`, etc.), other common generated patterns (`.g.dart`, `.generated.cs`, `.freezed.dart`), and Solidity test/script files (`*.t.sol`, `*.s.sol` from Foundry). They will not appear in search results.

If you encounter a generated file through Grep or Read, **do not modify it** — find and edit the source (`.proto`, codegen config) instead.

Expand Down Expand Up @@ -201,6 +201,7 @@ After searching and reading, ask yourself these questions before responding:
| Kotlin | `.kt`, `.kts` |
| Swift | `.swift` |
| Ruby | `.rb` |
| Solidity | `.sol` |
| HCL / Terraform | `.tf`, `.hcl` |
| Dockerfile | `Dockerfile`, `Dockerfile.*`, `*.dockerfile` |
| Protobuf | `.proto` |
Expand Down
1 change: 1 addition & 0 deletions cmd/vfs/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ func supportedLanguages() []struct {
{"Kotlin", []string{".kt", ".kts"}},
{"Swift", []string{".swift"}},
{"Ruby", []string{".rb"}},
{"Solidity", []string{".sol"}},
{"HCL/Terraform", []string{".tf", ".hcl"}},
{"Dockerfile", []string{"Dockerfile", "Dockerfile.*", "*.dockerfile"}},
{"Protobuf", []string{".proto"}},
Expand Down
15 changes: 15 additions & 0 deletions internal/parser/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/TrNgTien/vfs/internal/parser/pyparser"
"github.com/TrNgTien/vfs/internal/parser/rubyparser"
"github.com/TrNgTien/vfs/internal/parser/rustparser"
"github.com/TrNgTien/vfs/internal/parser/solidityparser"
"github.com/TrNgTien/vfs/internal/parser/sig"
"github.com/TrNgTien/vfs/internal/parser/sqlparser"
"github.com/TrNgTien/vfs/internal/parser/swiftparser"
Expand Down Expand Up @@ -47,6 +48,7 @@ func init() {
registerKotlin()
registerSwift()
registerRuby()
registerSolidity()
registerHCL()
registerDockerfile()
registerProto()
Expand Down Expand Up @@ -258,6 +260,19 @@ func registerRuby() {
})
}

func registerSolidity() {
Register(Extractor{
Extensions: []string{".sol"},
Skip: func(name string) bool {
return strings.HasSuffix(name, ".t.sol") ||
strings.HasSuffix(name, ".s.sol")
},
Extract: func(filePath string, src []byte) ([]sig.Sig, error) {
return solidityparser.ExtractExportedFuncs(filePath, src)
},
})
}

func registerHCL() {
Register(Extractor{
Extensions: []string{".tf", ".hcl"},
Expand Down
5 changes: 5 additions & 0 deletions internal/parser/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ func TestFindExtractor_SkipsProtoGenerated(t *testing.T) {
{"service.cs", false},
{"service.generated.cs", true},

// Solidity
{"contract.sol", false},
{"contract.t.sol", true},
{"deploy.s.sol", true},

// Proto source files should NOT be skipped
{"service.proto", false},
}
Expand Down
18 changes: 18 additions & 0 deletions internal/parser/solidityparser/binding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package solidityparser

// Vendor the tree-sitter-solidity parser (v1.2.13) because the upstream
// Go binding (github.com/JoranHonig/tree-sitter-solidity) declares a
// mismatched module path and depends on the legacy smacker/go-tree-sitter
// library instead of the official tree-sitter/go-tree-sitter used by vfs.

// #cgo CFLAGS: -std=c11 -I${SRCDIR}/src
// #cgo !windows CFLAGS: -fPIC
// #include "src/parser.c"
import "C"

import "unsafe"

// Language returns the tree-sitter Language pointer for Solidity.
func Language() unsafe.Pointer {
return unsafe.Pointer(C.tree_sitter_solidity())
}
Loading
Loading