From a5f691a82fd37519111a4d736678d0c7d9b035f4 Mon Sep 17 00:00:00 2001 From: derricksimpson Date: Thu, 18 Jun 2026 10:55:56 -0400 Subject: [PATCH 1/3] feat(cli): introduce default context for `--find` and add `--full` flag This commit changes the default behavior of the `--find` command to return context-windowed output (3 lines above and below each match) instead of full file contents. A new `--full` flag is added to allow users to opt into the previous full-file behavior. Updates include modifications to the CLI parser, main execution logic, and documentation across README.md, SKILL.md, and integration tests to reflect these changes and ensure backward compatibility. --- .../design.md | 126 ++++++++++++++++++ .../journal.md | 52 ++++++++ .../requirements.md | 116 ++++++++++++++++ .../tasks.md | 61 +++++++++ 4 files changed, 355 insertions(+) create mode 100644 specs/2026-06-03-default-context-search/design.md create mode 100644 specs/2026-06-03-default-context-search/journal.md create mode 100644 specs/2026-06-03-default-context-search/requirements.md create mode 100644 specs/2026-06-03-default-context-search/tasks.md diff --git a/specs/2026-06-03-default-context-search/design.md b/specs/2026-06-03-default-context-search/design.md new file mode 100644 index 0000000..296e9a4 --- /dev/null +++ b/specs/2026-06-03-default-context-search/design.md @@ -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`. 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 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` and `chunks: Option>`. The change only affects which field gets populated by default. + +```rust +pub struct FileEntry { + pub path: String, + pub contents: Option, // used by --full + pub error: Option, + pub chunks: Option>, // used by default context mode +} +``` + +## Error Handling + +| Scenario | Behavior | +|---|---| +| `--full` without `--find` | CLI parser returns error: `"--full requires --find"` | +| `--full` with `-C ` | 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`, `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. diff --git a/specs/2026-06-03-default-context-search/journal.md b/specs/2026-06-03-default-context-search/journal.md new file mode 100644 index 0000000..ef68593 --- /dev/null +++ b/specs/2026-06-03-default-context-search/journal.md @@ -0,0 +1,52 @@ +# Journal + +## Summary + +The default `--find` behavior was changed from returning full file contents to +returning context-windowed output (3 lines above and below each match). A new +`--full` flag was added to explicitly opt into the old full-file behavior. The +SKILL.md, README.md, and CLI help text were updated to reflect the new defaults +and promote the "triage → expand" workflow. + +## What Changed + +- **`src/main.rs`**: Added `DEFAULT_CONTEXT` constant (3). `execute_search` now + resolves the effective context: `--full` → `None`, explicit `-C` → user value, + otherwise → `Some(3)`. No changes to `searcher.rs` — the existing context + path handles everything. + +- **`src/cli.rs`**: Added `full: bool` to `CliArgs`, parsed `--full`, added + validation for `--full` requires `--find` and mutual exclusivity with `-C`. + Updated `print_help` to document the new default and the `--full` flag. + +- **`tests/integration_test.rs`**: Updated `search_returns_full_file_content` + to assert `chunks:` output in default mode. Added tests for `--full` output, + `--full` without `--find` error, `--full` + `-C` error, and `-C 0` behavior. + +- **`README.md`**: Updated Find mode description, flags table, and workflow + examples. Added `--full` documentation. Removed "full file contents" + phrasing from the default behavior description. + +- **`.cursor/skills/src/SKILL.md`**: Updated Find section to describe + context-windowed defaults. Added `--full` to options. Updated behavior notes + and promoted the count → find → full workflow. + +## Expected Validation + +- All existing integration tests pass with the new default (context-windowed + output uses the same `chunks:` format already tested by the `-C` code path). +- New tests confirm `--full` produces `contents:` output, and that validation + errors fire correctly for invalid flag combinations. +- `cargo test` passes cleanly with no regressions. +- Manual verification: `src -f "pattern"` now returns compact, focused output; + `src -f "pattern" --full` returns the old verbose output. + +## Follow-Through + +- Consider adding a `--auto-expand` equivalent for `--find` that expands + matching context to the enclosing symbol boundary (function/struct/class), + rather than using a fixed line count. +- Monitor agent pipeline feedback — if 3 lines is too tight for some use + cases, the default could be bumped to 5 without any structural changes. +- Consider a `SRC_DEFAULT_CONTEXT` environment variable for users who want + a persistent custom default without typing `-C` every time. diff --git a/specs/2026-06-03-default-context-search/requirements.md b/specs/2026-06-03-default-context-search/requirements.md new file mode 100644 index 0000000..824b0b0 --- /dev/null +++ b/specs/2026-06-03-default-context-search/requirements.md @@ -0,0 +1,116 @@ +# Requirements Document + +## Introduction + +`src --find` currently returns the **full contents** of every matching file. For +a typical codebase search like `src -f "process_file" -g "*.rs"`, this produces +~3,700 lines of output when only ~140 lines of focused context are actually +useful. The context-window flag (`-C `) already exists but is opt-in, meaning +the default experience is noisy and wasteful — especially for agent and LLM +pipelines where token budgets matter. + +This feature changes the default `--find` behavior to return context-windowed +output (a small number of lines around each match) and adds a `--full` flag for +users who explicitly want the old full-file behavior. The `--expand` concept is +promoted as the deliberate "give me more" escalation path in documentation and +skill guidance. + +## Requirements + +### Requirement 1 — Default context window for `--find` + +**User Story:** As a developer or agent using `src -f`, I want search results to +show only the lines around each match by default, so that output stays focused +and I don't waste time or tokens on irrelevant code. + +#### Acceptance Criteria + +1. WHEN `--find` is used WITHOUT an explicit `-C` value THEN the system SHALL + behave as if `-C 3` was passed (3 lines of context above and below each + match). +2. WHEN `--find` is used WITH an explicit `-C ` value THEN the system SHALL + use the user-provided context size, overriding the default. +3. WHEN `-C 0` is passed THEN the system SHALL show only matching lines with no + surrounding context. + +### Requirement 2 — `--full` flag for full-file output + +**User Story:** As a power user, I want a way to retrieve the full contents of +matching files when I explicitly need them, so that the old behavior remains +accessible. + +#### Acceptance Criteria + +1. WHEN `--full` is passed with `--find` THEN the system SHALL return the + complete contents of every matching file (same as the old default behavior). +2. WHEN `--full` is combined with `-C ` THEN the system SHALL return an error + explaining the flags are mutually exclusive. +3. WHEN `--full` is passed without `--find` THEN the system SHALL return an + error explaining `--full` requires `--find`. + +### Requirement 3 — Backward compatibility + +**User Story:** As an existing user with scripts that depend on the current +output shape, I want clear migration guidance and predictable behavior changes, +so that I can adapt without breakage surprises. + +#### Acceptance Criteria + +1. WHEN the default context mode is active THEN output SHALL use the `chunks:` + structure (with `startLine`, `endLine`, `content` fields) instead of the flat + `contents:` field — the same shape that `-C ` produces today. +2. WHEN `--full` is active THEN output SHALL use the flat `contents:` field — + the same shape that `--find` produces today without `-C`. +3. The help text SHALL note the default context behavior and the `--full` + escape hatch. + +### Requirement 4 — Documentation updates + +**User Story:** As a developer reading the skill file or README, I want the docs +to reflect the new defaults and promote the "triage → expand" workflow, so that +I use the tool effectively from day one. + +#### Acceptance Criteria + +1. `.cursor/skills/src/SKILL.md` SHALL be updated to: + - Remove the "returns full file contents" language from the Find section. + - Show context-windowed output as the default behavior. + - Document the `--full` flag. + - Promote the workflow: `--count` → default find → `--full` when needed. +2. `README.md` SHALL be updated to: + - Update the Find mode description and examples. + - Add `--full` to the flags table. + - Remove or qualify the "full file contents" phrasing. +3. `print_help` in `cli.rs` SHALL update the `--context` description and add + the `--full` flag entry. + +### Requirement 5 — Integration test coverage + +**User Story:** As a maintainer, I want tests that verify both the new default +and the `--full` opt-in, so that regressions are caught. + +#### Acceptance Criteria + +1. WHEN the existing `search_returns_full_file_content` test runs THEN it SHALL + be updated or replaced to verify the new default produces `chunks:` output + instead of `contents:`. +2. A new test SHALL verify that `--full` produces `contents:` output. +3. A new test SHALL verify that `--full` without `--find` returns an error. +4. A new test SHALL verify that `--full` with `-C` returns an error. +5. A new test SHALL verify that `-C 0` shows only matching lines. + +### Requirement 6 — Edge cases + +**User Story:** As a user running edge-case searches, I want the tool to handle +degenerate inputs gracefully under the new defaults. + +#### Acceptance Criteria + +1. WHEN a file has only one line that matches THEN the context window SHALL + clamp to file boundaries without error. +2. WHEN every line in a file matches THEN the single merged chunk SHALL contain + the entire file (equivalent to `--full` behavior, but in `chunks:` format). +3. WHEN `-C 999` is passed on a 10-line file THEN the chunk SHALL clamp to + lines 1–10 without error. +4. WHEN `--count` / `-c` is combined with `--find` THEN the default context + SHALL NOT apply (counts do not output file content). diff --git a/specs/2026-06-03-default-context-search/tasks.md b/specs/2026-06-03-default-context-search/tasks.md new file mode 100644 index 0000000..71d7d28 --- /dev/null +++ b/specs/2026-06-03-default-context-search/tasks.md @@ -0,0 +1,61 @@ +# Implementation Plan + +- [ ] 1. Add `--full` flag to CLI parser + - Add `pub full: bool` field to `CliArgs` in `src/cli.rs` + - Parse `"--full"` in the match arm (set `full = true`) + - Add validation: `--full` requires `find.is_some()`, error otherwise + - Add validation: `--full` + `context.is_some()` is mutually exclusive, error otherwise + - Wire `full` into the `CliArgs` constructor + - (R2, R3) + +- [ ] 1.1. Add CLI unit tests for `--full` + - Test `--full` with `--find` is accepted + - Test `--full` without `--find` returns error + - Test `--full` with `-C` returns error + - (R2, R5) + +- [ ] 2. Apply default context in `execute_search` + - Define `const DEFAULT_CONTEXT: usize = 3;` in `src/main.rs` + - In `execute_search`, resolve effective context before calling `searcher::search_files`: + - If `args.full` → pass `None` (full file) + - Else if `args.context.is_some()` → pass `args.context` (user override) + - Else → pass `Some(DEFAULT_CONTEXT)` (new default) + - (R1, R2) + +- [ ] 3. Update `print_help` in `src/cli.rs` + - Add `--full` to the Options section with description + - Update `--context, -C ` description to say `(default: 3; use --full for complete files)` + - Add an example line showing `--full` usage + - Update the `--find` example comment from `(full file content returned)` to note context-windowed default + - (R4) + +- [ ] 4. Update integration tests in `tests/integration_test.rs` + - Update `search_returns_full_file_content` to assert `chunks:` output (not `contents:`) in default mode + - Add `search_full_flag` test: verify `--full` produces `contents:` output + - Add `full_without_find_error` test: verify `--full` alone returns error + - Add `full_with_context_error` test: verify `--full -C 3` returns error + - Add `context_zero_shows_only_match` test: verify `-C 0` works + - (R5, R6) + +- [ ] 5. Run `cargo test` and fix any failures + - Ensure all existing tests pass with the new default + - Particular attention to: `legacy_pad_flag_accepted`, `legacy_context_flags_accepted`, + `search_finds_pattern`, `search_case_insensitive`, `search_multi_term`, + `limit_caps_search_results` + - (R1, R3, R5, R6) + +- [ ] 6. Update `README.md` + - Update the Find mode description in the Modes table (remove "full contents") + - Add `--full` to the Flags table + - Update the `--context` description to note the default + - Update workflow example 4 to show context-windowed as default + - Add a `--full` example to the Real Workflows section + - (R4) + +- [ ] 7. Update `.cursor/skills/src/SKILL.md` + - Update Find section: change "Returns full file contents" to "Returns context windows around matches" + - Document `--full` flag for opting into full-file output + - Update the "Important Behavior Notes" section + - Promote the triage workflow: `--count` → default find → `--full` + - Update the Common Options table to include `--full` and update `--context` description + - (R4) From a74fe78ffa0be46980649302c302d9f1ccc312ee Mon Sep 17 00:00:00 2001 From: derricksimpson Date: Thu, 18 Jun 2026 10:56:09 -0400 Subject: [PATCH 2/3] feat(glob): add brace expansion support for glob patterns This commit introduces brace expansion for the `--glob` option, allowing users to specify multiple file extensions in a single argument (e.g., `-g "*.{ts,tsx}"`). The implementation includes updates to the glob resolution logic, CLI help text, and documentation in SKILL.md. Integration tests have been added to ensure the new functionality works as expected and is equivalent to using multiple `-g` flags. --- .cursor/skills/src/SKILL.md | 9 +- specs/2026-05-29-brace-glob-expansion.md | 106 +++++++++++++++++++++++ src/cli.rs | 7 +- src/glob.rs | 68 +++++++++++++++ src/main.rs | 6 +- tests/integration_test.rs | 50 +++++++++++ 6 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 specs/2026-05-29-brace-glob-expansion.md diff --git a/.cursor/skills/src/SKILL.md b/.cursor/skills/src/SKILL.md index 306152f..e96135e 100644 --- a/.cursor/skills/src/SKILL.md +++ b/.cursor/skills/src/SKILL.md @@ -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 ``` @@ -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 ``` @@ -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: @@ -145,7 +146,7 @@ Returns file counts, line counts, and byte sizes grouped by language/extension. | Flag | Short | Purpose | |---|---|---| | `--dir ` | `-d` | Set root directory (default: cwd) | -| `--glob ` | `-g` | File pattern filter (repeatable) | +| `--glob ` | `-g` | File pattern filter (repeatable; supports brace expansion: `*.{ts,tsx}`) | | `--find ` | `-f` | Content search pattern (`\|` = OR) | | `--regex` | `-E` | Treat `--find` as regex | | `--count` | `-c` | Show match counts (requires `--find`) | @@ -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. diff --git a/specs/2026-05-29-brace-glob-expansion.md b/specs/2026-05-29-brace-glob-expansion.md new file mode 100644 index 0000000..4bff9b9 --- /dev/null +++ b/specs/2026-05-29-brace-glob-expansion.md @@ -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` 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` 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) diff --git a/src/cli.rs b/src/cli.rs index ef17808..77ffe05 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -256,7 +256,7 @@ Modes: Options: --dir, -d Root directory (default: current directory) - --glob, -g File glob pattern (repeatable; -g *.ts *.tsx also works) + --glob, -g File glob pattern (repeatable; brace expansion: -g *.{{ts,tsx}}) --find, -f Search pattern (use | for OR, e.g. Payment|Invoice) --lines Line specs: file:start:end file2:start:end (repeatable) --graph Emit source dependency graph @@ -287,8 +287,9 @@ Aliases: Examples: src Show directory tree - src -g *.rs List all Rust files - src -g *.ts -f "import" Search TypeScript files for imports + src -g *.rs List all Rust files + src -g "*.{{ts,tsx}}" List TypeScript files (brace expansion) + src -g *.ts -f "import" Search TypeScript files for imports src -f "TODO|FIXME" Find TODOs (full file content returned) src -f "pub fn" --no-line-numbers Search without line number prefixes src --lines "src/main.rs:1:20 src/cli.rs:18:40" Pull exact line ranges diff --git a/src/glob.rs b/src/glob.rs index e332a4a..a98f376 100644 --- a/src/glob.rs +++ b/src/glob.rs @@ -58,6 +58,27 @@ fn eq_ci(a: u8, b: u8) -> bool { a.to_ascii_lowercase() == b.to_ascii_lowercase() } +/// Expand a single brace group in a glob pattern. +/// `*.{rs,toml}` → `["*.rs", "*.toml"]`. +/// Patterns without braces pass through unchanged. +/// Only the first `{…}` group is expanded; nested braces are not supported. +pub fn expand_braces(pattern: &str) -> Vec { + let open = match pattern.find('{') { + Some(i) => i, + None => return vec![pattern.to_owned()], + }; + let close = match pattern[open..].find('}') { + Some(i) => open + i, + None => return vec![pattern.to_owned()], + }; + let prefix = &pattern[..open]; + let suffix = &pattern[close + 1..]; + pattern[open + 1..close] + .split(',') + .map(|alt| format!("{}{}{}", prefix, alt, suffix)) + .collect() +} + #[cfg(test)] mod tests { use super::*; @@ -143,4 +164,51 @@ mod tests { assert!(matches("test_file.spec.ts", "*.spec.ts")); assert!(matches("a.b.c.d", "a.*.d")); } + + #[test] + fn expand_braces_basic() { + assert_eq!(expand_braces("*.{rs,toml}"), vec!["*.rs", "*.toml"]); + } + + #[test] + fn expand_braces_single_element() { + assert_eq!(expand_braces("*.{rs}"), vec!["*.rs"]); + } + + #[test] + fn expand_braces_no_braces() { + assert_eq!(expand_braces("*.rs"), vec!["*.rs"]); + } + + #[test] + fn expand_braces_with_path() { + assert_eq!( + expand_braces("src/**/*.{ts,tsx}"), + vec!["src/**/*.ts", "src/**/*.tsx"], + ); + } + + #[test] + fn expand_braces_many_alternatives() { + assert_eq!( + expand_braces("*.{a,b,c,d}"), + vec!["*.a", "*.b", "*.c", "*.d"], + ); + } + + #[test] + fn expand_braces_unmatched_open() { + assert_eq!(expand_braces("*.{rs"), vec!["*.{rs"]); + } + + #[test] + fn expand_braces_unmatched_close() { + assert_eq!(expand_braces("*.rs}"), vec!["*.rs}"]); + } + + #[test] + fn expand_braces_empty_alternative() { + let result = expand_braces("file.{,rs}"); + assert_eq!(result, vec!["file.", "file.rs"]); + } } diff --git a/src/main.rs b/src/main.rs index bafef97..8e2e6c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -132,7 +132,8 @@ fn finish( } fn resolve_globs(args: &cli::CliArgs) -> Vec { - if args.globs.is_empty() { vec!["*.*".to_owned()] } else { args.globs.clone() } + let raw = if args.globs.is_empty() { vec!["*.*".to_owned()] } else { args.globs.clone() }; + raw.iter().flat_map(|g| glob::expand_braces(g)).collect() } fn find_or_bail( @@ -224,7 +225,8 @@ fn execute_file_listing( start: Instant, format: OutputFormat, ) -> i32 { - let files = scanner::find_files_filtered(root, &args.globs, filter, cancelled, args.with_tests); + let globs = resolve_globs(args); + let files = scanner::find_files_filtered(root, &globs, filter, cancelled, args.with_tests); let elapsed = start.elapsed().as_millis(); let timed_out = cancelled.load(Ordering::Relaxed); diff --git a/tests/integration_test.rs b/tests/integration_test.rs index bd33d53..befde51 100644 --- a/tests/integration_test.rs +++ b/tests/integration_test.rs @@ -1075,3 +1075,53 @@ fn graph_no_tsconfig_still_works() { assert_eq!(code, 0); assert!(stdout.contains("graph:")); } + +// ── Brace Glob Expansion ── + +#[test] +fn brace_glob_matches_multiple_extensions() { + let (stdout, _, code) = run_src_in(&fixture(), &["-g", "*.{ts,rs}"]); + assert_eq!(code, 0); + assert!(stdout.contains("config.ts")); + assert!(stdout.contains("utils.ts")); + assert!(stdout.contains("main.rs")); +} + +#[test] +fn brace_glob_equivalent_to_repeated_flags() { + let (brace_out, _, c1) = run_src_in(&fixture(), &["-g", "*.{ts,py}"]); + let (repeat_out, _, c2) = run_src_in(&fixture(), &["-g", "*.ts", "-g", "*.py"]); + assert_eq!(c1, 0); + assert_eq!(c2, 0); + let strip_elapsed = |s: &str| { + s.lines() + .filter(|l| !l.trim_start().starts_with("elapsedMs:")) + .collect::>() + .join("\n") + }; + assert_eq!(strip_elapsed(&brace_out), strip_elapsed(&repeat_out)); +} + +#[test] +fn brace_glob_single_element() { + let (brace_out, _, c1) = run_src_in(&fixture(), &["-g", "*.{rs}"]); + assert_eq!(c1, 0); + assert!(brace_out.contains("main.rs")); + assert!(brace_out.contains("mod.rs")); + assert!(brace_out.contains("documented.rs")); + assert!(brace_out.contains("filesMatched: 3")); +} + +#[test] +fn brace_glob_no_braces_unchanged() { + let (stdout, _, code) = run_src_in(&fixture(), &["-g", "*.rs"]); + assert_eq!(code, 0); + assert!(stdout.contains("main.rs")); +} + +#[test] +fn brace_glob_with_find() { + let (stdout, _, code) = run_src_in(&fixture(), &["-g", "*.{ts,py}", "-f", "import", "-c"]); + assert_eq!(code, 0); + assert!(stdout.contains("files:") || stdout.contains("filesMatched: 0")); +} From 37bc2a572c98457a564b1d78cbf6f2e8efcb2735 Mon Sep 17 00:00:00 2001 From: derricksimpson Date: Thu, 18 Jun 2026 10:56:16 -0400 Subject: [PATCH 3/3] docs(README): update examples and formatting for glob and find commands This commit refines the README documentation by consolidating glob patterns using brace expansion (e.g., `*.{ts,tsx}`) and clarifying the usage of the `--find` command with proper syntax for logical OR (`|`). The changes enhance readability and ensure consistency across examples, improving user guidance for command usage. --- README.md | 54 +++++++++++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 142a55d..b3fb072 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 ``` @@ -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 | @@ -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 ` | Scan another repo without changing directories | -| `--glob`, `-g ` | Restrict by file pattern; repeatable | -| `--find`, `-f ` | Search contents; ` | ` works as a literal OR | -| `--regex`, `-E` | Treat `--find` as regex | -| `--count`, `-c` | Return counts instead of file contents | -| `--context`, `-C ` | Return match windows instead of full files | -| `--lines ""` | 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 ` | Find declaration(s) and call sites for a symbol | -| `--limit`, `-L ` | Cap result size | -| `--json` | Emit JSON instead of YAML | -| `--output`, `-o ` | Save results as an artifact | +| Flag | Meaning | +| ------------------------ | ----------------------------------------------------------------------------- | +| `--dir`, `-d ` | Scan another repo without changing directories | +| `--glob`, `-g ` | Restrict by file pattern; repeatable; supports brace expansion (`*.{ts,tsx}`) | +| `--find`, `-f ` | Search contents; `\|` works as a literal OR | +| `--regex`, `-E` | Treat `--find` as regex | +| `--count`, `-c` | Return counts instead of file contents | +| `--context`, `-C ` | Return match windows instead of full files | +| `--lines ""` | 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 ` | Find declaration(s) and call sites for a symbol | +| `--limit`, `-L ` | Cap result size | +| `--json` | Emit JSON instead of YAML | +| `--output`, `-o ` | Save results as an artifact | ## Output Shape @@ -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`