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
9 changes: 5 additions & 4 deletions .cursor/skills/src/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Returns a nested directory hierarchy of all source files. Use for orientation.

```bash
src -g "*.rs"
src -g "*.{ts,tsx}" # brace expansion (equivalent to -g *.ts -g *.tsx)
src -g "*.ts" -g "*.tsx" # multiple globs (repeatable -g)
src -g "*.rs" --limit 5 # cap results
```
Expand Down Expand Up @@ -93,7 +94,7 @@ files:

```bash
src --symbols -g "*.rs"
src -s -g "*.ts" -g "*.tsx"
src -s -g "*.{ts,tsx}"
src -s --json # JSON output
```

Expand All @@ -116,7 +117,7 @@ Supported languages: Rust, TypeScript/JavaScript, C#, Go, Java, Kotlin, Ruby, Py
```bash
src --graph
src --graph -g "*.rs" # Rust-only
src --graph -g "*.ts" -g "*.tsx" # TypeScript-only
src --graph -g "*.{ts,tsx}" # TypeScript-only
```

Returns project-internal imports per file:
Expand Down Expand Up @@ -145,7 +146,7 @@ Returns file counts, line counts, and byte sizes grouped by language/extension.
| Flag | Short | Purpose |
|---|---|---|
| `--dir <path>` | `-d` | Set root directory (default: cwd) |
| `--glob <pattern>` | `-g` | File pattern filter (repeatable) |
| `--glob <pattern>` | `-g` | File pattern filter (repeatable; supports brace expansion: `*.{ts,tsx}`) |
| `--find <pattern>` | `-f` | Content search pattern (`\|` = OR) |
| `--regex` | `-E` | Treat `--find` as regex |
| `--count` | `-c` | Show match counts (requires `--find`) |
Expand Down Expand Up @@ -209,7 +210,7 @@ This is **dramatically faster** than three separate `Read` calls.
## Important Behavior Notes

- **`--find` returns full file contents** of every matching file (not just matching lines). Use `--count` / `-c` first to triage, then `--limit` to keep output manageable.
- **`--glob` is repeatable** — pass `-g "*.ts" -g "*.tsx"` to match multiple patterns.
- **`--glob` supports brace expansion** — `-g "*.{ts,tsx}"` expands to `*.ts` and `*.tsx`. Repeatable `-g` flags also work: `-g "*.ts" -g "*.tsx"`.
- **`--lines` is repeatable** — pass multiple `--lines` flags or space-separate specs in one string.
- **`|` in `--find`** is literal OR (not regex by default). Add `-E` for full regex.
- Built-in exclusions (node_modules, .git, target, dist, etc.) apply by default. Use `--no-defaults` to disable.
Expand Down
54 changes: 27 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ src -g "*.rs"
src -f "TODO|FIXME"
src -f "createInvoice|finalizeInvoice" -g "*.ts" -c
src --lines "src/main.rs:1:40 src/cli.rs:220:293"
src --graph -g "*.tsx" -g "*.ts"
src --graph -g "*.{ts,tsx}"
src --symbols -g "*.rs" --compact
src --callers process_file -g "*.rs"
src --stats
Expand All @@ -61,7 +61,7 @@ src --stats
Count where a state hook or factory shows up:

```bash
src -g "*.ts" -g "*.tsx" \
src -g "*.{ts,tsx}" \
-f "useMemberStore|create" \
-c -L 8
```
Expand Down Expand Up @@ -292,12 +292,12 @@ This is the core agent workflow: one command returns several focused source rang
## Modes

| Mode | Command | What it returns |
| ----------------- | ---------------------------------------- | --------------------------------------------- | ---------------------------------- |
| ----------------- | ---------------------------------------- | --------------------------------------------- |
| Tree | `src` | Directory hierarchy of source files |
| Glob | `src -g "*.ts"` | Flat file list |
| Find | `src -f "auth | token"` | Matching files with full contents |
| Find with context | `src -f "auth | token" -C 3` | Matching files with focused chunks |
| Count | `src -f "auth | token" -c` | Match counts per file |
| Find | `src -f "auth \| token"` | Matching files with full contents |
| Find with context | `src -f "auth \| token" -C 3` | Matching files with focused chunks |
| Count | `src -f "auth \| token" -c` | Match counts per file |
| Lines | `src --lines "a.rs:1:30 b.ts:40:90"` | Exact ranges from multiple files |
| Lines auto-expand | `src --lines "a.rs:88:88" --auto-expand` | Full enclosing symbol for the referenced line |
| Graph | `src --graph` | Project-internal dependency/import map |
Expand All @@ -308,25 +308,25 @@ This is the core agent workflow: one command returns several focused source rang

## Flags That Matter In Practice

| Flag | Meaning |
| ------------------------ | ------------------------------------------------------ | ----------------------- |
| `--dir`, `-d <path>` | Scan another repo without changing directories |
| `--glob`, `-g <pattern>` | Restrict by file pattern; repeatable |
| `--find`, `-f <pattern>` | Search contents; ` | ` works as a literal OR |
| `--regex`, `-E` | Treat `--find` as regex |
| `--count`, `-c` | Return counts instead of file contents |
| `--context`, `-C <n>` | Return match windows instead of full files |
| `--lines "<specs>"` | Extract exact file ranges in one call |
| `--auto-expand` | Expand a `--lines` location to the enclosing symbol |
| `--graph` | Build an internal dependency graph |
| `--symbols`, `-s` | Extract declarations |
| `--compact` | Condense symbol output for scanning |
| `--with-comments` | Include doc comments in symbol output |
| `--with-tests` | Include test files normally skipped by source scanning |
| `--callers <name>` | Find declaration(s) and call sites for a symbol |
| `--limit`, `-L <n>` | Cap result size |
| `--json` | Emit JSON instead of YAML |
| `--output`, `-o <path>` | Save results as an artifact |
| Flag | Meaning |
| ------------------------ | ----------------------------------------------------------------------------- |
| `--dir`, `-d <path>` | Scan another repo without changing directories |
| `--glob`, `-g <pattern>` | Restrict by file pattern; repeatable; supports brace expansion (`*.{ts,tsx}`) |
| `--find`, `-f <pattern>` | Search contents; `\|` works as a literal OR |
| `--regex`, `-E` | Treat `--find` as regex |
| `--count`, `-c` | Return counts instead of file contents |
| `--context`, `-C <n>` | Return match windows instead of full files |
| `--lines "<specs>"` | Extract exact file ranges in one call |
| `--auto-expand` | Expand a `--lines` location to the enclosing symbol |
| `--graph` | Build an internal dependency graph |
| `--symbols`, `-s` | Extract declarations |
| `--compact` | Condense symbol output for scanning |
| `--with-comments` | Include doc comments in symbol output |
| `--with-tests` | Include test files normally skipped by source scanning |
| `--callers <name>` | Find declaration(s) and call sites for a symbol |
| `--limit`, `-L <n>` | Cap result size |
| `--json` | Emit JSON instead of YAML |
| `--output`, `-o <path>` | Save results as an artifact |

## Output Shape

Expand Down Expand Up @@ -375,8 +375,8 @@ Other file types still work with tree, glob, find, lines, and stats modes.
Use these in order when you are dropped into an unfamiliar repo:

1. `src --stats`
2. `src --graph -g "*.ts" -g "*.tsx"` or `src --graph -g "*.rs"`
3. `src --symbols --compact -g "*.ts" -g "*.tsx"` or `src --symbols --compact -g "*.rs"`
2. `src --graph -g "*.{ts,tsx}"` or `src --graph -g "*.rs"`
3. `src --symbols --compact -g "*.{ts,tsx}"` or `src --symbols --compact -g "*.rs"`
4. `src -f "termA|termB" -c`
5. `src --lines "file:line:line" --auto-expand`
6. `src --callers symbolName`
Expand Down
106 changes: 106 additions & 0 deletions specs/2026-05-29-brace-glob-expansion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Brace Glob Expansion

## Requirements

Goal: Allow users to write `*.{ts,tsx}` or `src/**/*.{js,jsx,ts,tsx}` as a
single `-g` value instead of repeating `--glob` for every extension.

R1. When a `--glob` value contains a brace group `{a,b,...}`, the system shall
expand it into multiple glob patterns before matching.
- Acceptance: `src -g "*.{rs,toml}"` returns the same files as
`src -g "*.rs" -g "*.toml"`.
- Acceptance: `src -g "src/**/*.{ts,tsx}"` returns the same files as
`src -g "src/**/*.ts" -g "src/**/*.tsx"`.

R2. Brace expansion shall support an arbitrary number of comma-separated
alternatives.
- Acceptance: `*.{a,b,c,d}` expands to `*.a`, `*.b`, `*.c`, `*.d`.
- Acceptance: single-element braces `*.{rs}` expand to `*.rs` (no-op).

R3. Patterns that do not contain braces shall pass through unchanged.
- Acceptance: `*.rs` remains `*.rs` after expansion.

R4. Nested braces and escaped braces are **out of scope**; the expansion
handles only one flat `{alt,alt,...}` group per pattern.
- Acceptance: `{a,{b,c}}` is treated as three alternatives `a`, `{b`, `c}}`
(best-effort, no error).

R5. The expansion shall happen early, before any downstream matching, so every
mode (tree filter, search, symbols, graph, callers, stats, count, file
listing) benefits automatically.
- Acceptance: existing integration tests continue to pass.

## Design

### Touch points

| File | Change |
|---|---|
| `src/glob.rs` | Add `pub fn expand_braces(pattern: &str) -> Vec<String>` and unit tests. |
| `src/main.rs` → `resolve_globs` | Apply `expand_braces` to each glob before returning, so every downstream consumer sees pre-expanded patterns. |
| `src/cli.rs` | Update `print_help` examples and options text to mention brace syntax. |
| `README.md` | Add brace-glob examples to Quick Start, Workflow 1, and Options. |
| `.cursor/skills/src/SKILL.md` | Add brace-glob examples in the Glob section and Common Options. |
| `AGENTS.md` | No change needed (already says "update integration tests"). |
| `tests/integration_test.rs` | Add integration tests for brace expansion via CLI. |

### Flow

```
CLI input: -g "*.{ts,tsx}"
parse_args → globs = ["*.{ts,tsx}"]
resolve_globs → for each glob, call expand_braces
→ ["*.ts", "*.tsx"]
scanner / modes → see flat glob list, match as today
```

Expansion is a pure string transform. No new dependencies needed. The single
call site in `resolve_globs` means every mode picks it up for free.

### Decisions

- **Where to expand**: `resolve_globs` in `main.rs` is the single choke point
that all modes use. Expanding there keeps `glob.rs` a pure matching library
and avoids touching `scanner.rs` or mode functions.
- **Flat only**: supporting nested braces adds complexity with near-zero
practical value. Keeping it flat matches Bash/Zsh brace behavior for the
common case.
- **No new deps**: the expansion is simple enough for ~20 lines of hand-written
code with existing `&str` primitives.

### Risks

- A pattern containing `{` without `}` (or vice-versa) should pass through
unchanged rather than erroring, so typos degrade gracefully.

## Tasks

1. [ ] Add `pub fn expand_braces(pattern: &str) -> Vec<String>` to
`src/glob.rs` with unit tests covering: basic `*.{rs,toml}`, single-element
`*.{rs}`, no-braces passthrough, path-prefixed `src/**/*.{ts,tsx}`,
unmatched braces, empty alternatives. (R1, R2, R3, R4)

2. [ ] Update `resolve_globs` in `src/main.rs` to flat-map each glob through
`expand_braces` before returning the final list. (R5)

3. [ ] Add integration tests in `tests/integration_test.rs` for brace-glob
expansion exercised end-to-end (e.g. `-g "*.{rs,toml}"` returns expected
files). (R1, R5)

4. [ ] Run `cargo test` and fix any failures. (R1–R5)

5. [ ] Update `print_help` in `src/cli.rs` — add `*.{ts,tsx}` brace syntax
mention in the `--glob` option description and an example line. (R1)

6. [ ] Update `README.md` — replace `src -g "*.ts" -g "*.tsx"` examples with
the shorter `src -g "*.{ts,tsx}"` form where appropriate; keep at least one
example of the repeated-flag style for clarity. (R1)

7. [ ] Update `.cursor/skills/src/SKILL.md` — add brace-glob syntax to the
Glob section and the Important Behavior Notes. (R1)
126 changes: 126 additions & 0 deletions specs/2026-06-03-default-context-search/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# Design Document

## Overview

The change is surgically small: introduce a default context value when `--find`
is used without an explicit `-C`, and add a `--full` flag that restores the
current full-file behavior. The context-windowed code path already exists and is
well-tested — this feature makes it the default instead of the opt-in.

## Architecture

The search pipeline today:

```
CLI parse → resolve_globs → find candidate files → searcher::search_files → emit
```

`searcher::search_files` already accepts `context: Option<usize>`. When `Some`,
it produces `chunks:`; when `None`, it dumps full `contents:`. The only change
is where `None` originates.

### Before

```
args.context = None → full file
args.context = Some(n) → n-line context
```

### After

```
args.context = None, no --full → treated as Some(DEFAULT_CONTEXT) at call site
args.context = Some(n) → n-line context (explicit user override)
args.full = true → passed as None to searcher (full file)
```

The branching happens in `execute_search` in `main.rs`, not in the CLI parser.
The parser stores the raw user intent; the executor resolves the effective
context value. This keeps `CliArgs` honest about what the user actually typed
and avoids defaulting in the parser where it would be harder to distinguish
"user didn't pass -C" from "user wants default context."

## Components and Interfaces

### Touch points

| File | Change |
|---|---|
| `src/cli.rs` | Add `full: bool` field to `CliArgs`. Parse `--full`. Add validation: `--full` requires `--find`, `--full` + `-C` is an error. Update `print_help`. |
| `src/main.rs` | In `execute_search`, resolve effective context: if `args.full` → `None`; else if `args.context.is_some()` → use it; else → `Some(DEFAULT_CONTEXT)`. Define `const DEFAULT_CONTEXT: usize = 3;`. |
| `src/searcher.rs` | No changes needed. Already handles `Some(n)` and `None` correctly. |
| `src/models.rs` | No changes needed. `FileEntry` already supports both `contents` and `chunks`. |
| `src/yaml_output.rs` | No changes needed. Already serializes both shapes. |
| `tests/integration_test.rs` | Update `search_returns_full_file_content` → verify `chunks:` in default output. Add tests for `--full`, `--full` without `--find`, `--full` + `-C` error, `-C 0`. |
| `README.md` | Update Find section, flags table, examples. |
| `.cursor/skills/src/SKILL.md` | Update Find section, behavior notes, workflow guidance. |

### CLI changes

```
--full Return full file contents instead of context windows (requires --find)
--context, -C <n> Context lines around matches (default: 3; use --full for complete files)
```

### Default constant

```rust
const DEFAULT_CONTEXT: usize = 3;
```

3 lines is the sweet spot: enough to see the surrounding block structure without
swamping output. It matches the convention used in `grep -C 3` and in the
SKILL.md examples that already recommend `-C 2` or `-C 3`.

## Data Models

No new data models. The existing `FileEntry` struct already has both `contents: Option<String>` and `chunks: Option<Vec<FileChunk>>`. The change only affects which field gets populated by default.

```rust
pub struct FileEntry {
pub path: String,
pub contents: Option<String>, // used by --full
pub error: Option<String>,
pub chunks: Option<Vec<FileChunk>>, // used by default context mode
}
```

## Error Handling

| Scenario | Behavior |
|---|---|
| `--full` without `--find` | CLI parser returns error: `"--full requires --find"` |
| `--full` with `-C <n>` | CLI parser returns error: `"--full and --context are mutually exclusive"` |
| `-C` with non-integer | Existing error: `"Invalid integer for --context: ..."` |
| Context window exceeds file length | Existing clamping in `merge_ranges` handles this correctly |

## Key Design Decisions

1. **Default in executor, not parser.** The CLI parser records raw user intent
(`context: Option<usize>`, `full: bool`). The executor in `main.rs` resolves
the effective context value. This keeps the parser simple, makes the default
value visible in one place, and avoids ambiguity about whether `None` means
"user didn't say" or "user wants full."

2. **`--full` instead of `--expand`.** The `--auto-expand` flag already exists
for `--lines` mode. Using `--full` avoids overloading the "expand" concept
and is self-explanatory: "give me the full file."

3. **Constant at 3 lines.** Three lines of context matches `grep -C 3`
convention, is enough to show enclosing blocks in most languages, and keeps
output compact. Users who want more can pass `-C 5` or `-C 10`.

4. **No change to `searcher.rs`.** The searcher already handles both paths
cleanly. The only code change is in the call site that decides which path
to take.

## Testing Strategy

- Update `search_returns_full_file_content` to assert `chunks:` output and
the absence of `contents:` in default mode.
- New test `search_full_flag` verifying `contents:` output with `--full`.
- New test `full_without_find_error` verifying the validation error.
- New test `full_with_context_error` verifying mutual exclusivity.
- New test `context_zero_shows_only_match` verifying `-C 0` behavior.
- Existing context tests (`legacy_context_flags_accepted`, etc.) continue
to pass unchanged.
Loading
Loading