rlmgrep 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

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.
rlmgrep/cli.py CHANGED
@@ -8,7 +8,7 @@ from pathlib import Path
8
8
  import dspy
9
9
  from .config import ensure_default_config, load_config
10
10
  from .file_map import build_file_map
11
- from .ingest import FileRecord, load_files, resolve_type_exts
11
+ from .ingest import FileRecord, collect_candidates, load_files, resolve_type_exts
12
12
  from .rlm import Match, build_lm, run_rlm
13
13
  from .render import render_matches
14
14
 
@@ -17,6 +17,23 @@ def _warn(msg: str) -> None:
17
17
  print(f"rlmgrep: {msg}", file=sys.stderr)
18
18
 
19
19
 
20
+ def _confirm_over_limit(count: int, threshold: int) -> bool:
21
+ prompt = (
22
+ f"rlmgrep: {count} files to load (over {threshold}). Continue? [y/N] "
23
+ )
24
+ try:
25
+ with open("/dev/tty", "r+") as tty:
26
+ print(prompt, file=tty, end="", flush=True)
27
+ response = tty.readline()
28
+ except Exception:
29
+ if not sys.stdin.isatty():
30
+ _warn("refusing to prompt for confirmation; use --yes to proceed")
31
+ return False
32
+ print(prompt, file=sys.stderr, end="", flush=True)
33
+ response = sys.stdin.readline()
34
+ return response.strip().lower() in {"y", "yes"}
35
+
36
+
20
37
  def verify_matches(
21
38
  matches: list[Match],
22
39
  files: dict[str, FileRecord],
@@ -65,6 +82,7 @@ def _parse_args(argv: list[str]) -> argparse.Namespace:
65
82
  parser.add_argument("-m", dest="max_count", type=int, default=None, help="Max matching lines per file")
66
83
  parser.add_argument("-a", "--text", dest="binary_as_text", action="store_true", help="Search binary files as text")
67
84
  parser.add_argument("--answer", action="store_true", help="Print a narrative answer before grep output")
85
+ parser.add_argument("-y", "--yes", action="store_true", help="Skip file count confirmation")
68
86
 
69
87
  parser.add_argument("-g", "--glob", dest="globs", action="append", default=[], help="Include files matching glob (may repeat)")
70
88
  parser.add_argument("--type", dest="types", action="append", default=[], help="Include file types (py, js, md, etc.). May repeat")
@@ -328,12 +346,39 @@ def main(argv: list[str] | None = None) -> int:
328
346
  }
329
347
  warnings: list[str] = []
330
348
  else:
331
- files, warnings = load_files(
349
+ warn_threshold = _parse_num(
350
+ _pick(None, config, "file_warn_threshold", 200), int
351
+ )
352
+ hard_max = _parse_num(_pick(None, config, "file_hard_max", 1000), int)
353
+ if warn_threshold is not None and warn_threshold <= 0:
354
+ warn_threshold = None
355
+ if hard_max is not None and hard_max <= 0:
356
+ hard_max = None
357
+
358
+ candidates = collect_candidates(
332
359
  args.paths,
333
360
  cwd=cwd,
334
361
  recursive=args.recursive,
335
362
  include_globs=globs,
336
363
  type_exts=type_exts,
364
+ )
365
+ candidate_count = len(candidates)
366
+ if hard_max is not None and candidate_count > hard_max:
367
+ _warn(
368
+ f"{candidate_count} files to load (over {hard_max}); aborting"
369
+ )
370
+ return 2
371
+ if (
372
+ warn_threshold is not None
373
+ and candidate_count > warn_threshold
374
+ and not args.yes
375
+ ):
376
+ if not _confirm_over_limit(candidate_count, warn_threshold):
377
+ return 2
378
+
379
+ files, warnings = load_files(
380
+ candidates,
381
+ cwd=cwd,
337
382
  markitdown=markitdown,
338
383
  enable_images=md_enable_images,
339
384
  enable_audio=md_enable_audio,
rlmgrep/config.py CHANGED
@@ -19,6 +19,8 @@ DEFAULT_CONFIG_TEXT = "\n".join(
19
19
  "max_tokens = 64000",
20
20
  "max_iterations = 10",
21
21
  "max_llm_calls = 20",
22
+ "file_warn_threshold = 200",
23
+ "file_hard_max = 1000",
22
24
  "# markitdown_enable_images = false",
23
25
  "# markitdown_image_llm_model = \"gpt-5-mini\"",
24
26
  "# markitdown_image_llm_provider = \"openai\"",
rlmgrep/ingest.py CHANGED
@@ -237,12 +237,34 @@ def _matches_globs(path: str, globs: list[str]) -> bool:
237
237
  return False
238
238
 
239
239
 
240
- def load_files(
240
+ def collect_candidates(
241
241
  paths: Iterable[str],
242
242
  cwd: Path,
243
243
  recursive: bool = True,
244
244
  include_globs: list[str] | None = None,
245
245
  type_exts: set[str] | None = None,
246
+ ) -> list[Path]:
247
+ files = collect_files(paths, recursive=recursive)
248
+ candidates: list[Path] = []
249
+ for fp in files:
250
+ try:
251
+ key = fp.relative_to(cwd).as_posix()
252
+ except ValueError:
253
+ key = fp.as_posix()
254
+
255
+ if include_globs and not _matches_globs(key, include_globs):
256
+ continue
257
+
258
+ if type_exts and fp.suffix.lower() not in type_exts:
259
+ continue
260
+
261
+ candidates.append(fp)
262
+ return candidates
263
+
264
+
265
+ def load_files(
266
+ candidates: Iterable[Path],
267
+ cwd: Path,
246
268
  markitdown: Any | None = None,
247
269
  enable_images: bool = False,
248
270
  enable_audio: bool = False,
@@ -254,20 +276,12 @@ def load_files(
254
276
  image_convert_count = 0
255
277
  audio_convert_count = 0
256
278
 
257
- files = collect_files(paths, recursive=recursive)
258
- for fp in files:
279
+ for fp in candidates:
259
280
  try:
260
281
  key = fp.relative_to(cwd).as_posix()
261
282
  except ValueError:
262
283
  key = fp.as_posix()
263
284
 
264
- if include_globs and not _matches_globs(key, include_globs):
265
- continue
266
-
267
- if type_exts:
268
- if fp.suffix.lower() not in type_exts:
269
- continue
270
-
271
285
  suffix = fp.suffix.lower()
272
286
  if markitdown is not None and not binary_as_text:
273
287
  if enable_images and suffix in IMAGE_EXTS:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rlmgrep
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Grep-shaped CLI search powered by DSPy RLM
5
5
  Author: rlmgrep
6
6
  License: MIT
@@ -17,7 +17,7 @@ Grep-shaped search powered by DSPy RLM. It accepts a natural-language query, sca
17
17
  ## Quickstart
18
18
 
19
19
  ```sh
20
- uv tool install --python 3.11 .
20
+ uv tool install --python 3.11 rlmgrep
21
21
  # or from GitHub:
22
22
  # uv tool install --python 3.11 git+https://github.com/halfprice06/rlmgrep.git
23
23
 
@@ -71,6 +71,7 @@ Common options:
71
71
  - `--type T` include file types (repeatable, comma-separated)
72
72
  - `--no-recursive` do not recurse directories
73
73
  - `-a`, `--text` treat binary files as text
74
+ - `-y`, `--yes` skip file count confirmation
74
75
  - `--model`, `--sub-model` override model names
75
76
  - `--api-key`, `--api-base`, `--model-type` override provider settings
76
77
  - `--max-iterations`, `--max-llm-calls` cap RLM search effort
@@ -99,6 +100,7 @@ cat README.md | rlmgrep "install"
99
100
  - `-g/--glob` matches path globs against normalized paths (forward slashes).
100
101
  - Paths are printed relative to the current working directory when possible.
101
102
  - If no paths are provided, rlmgrep reads from stdin and uses the synthetic path `<stdin>`; if stdin is empty, it exits with code 2.
103
+ - rlmgrep asks for confirmation when more than 200 files would be loaded (use `-y/--yes` to skip), and aborts when more than 1000 files would be loaded.
102
104
 
103
105
  ## Output contract (stable for agents)
104
106
 
@@ -133,6 +135,8 @@ temperature = 1.0
133
135
  max_tokens = 64000
134
136
  max_iterations = 10
135
137
  max_llm_calls = 20
138
+ file_warn_threshold = 200
139
+ file_hard_max = 1000
136
140
  # markitdown_enable_images = false
137
141
  # markitdown_image_llm_model = "gpt-5-mini"
138
142
  # markitdown_image_llm_provider = "openai"
@@ -168,10 +172,8 @@ If more than one provider key is set and the model does not make the provider ob
168
172
 
169
173
  - Prefer narrow corpora (globs/types) to reduce token usage.
170
174
  - Use `--max-llm-calls` to cap costs; combine with small `--max-iterations` for safety.
171
- - Always read stderr for warnings (skipped files, config issues, ambiguous API keys).
172
175
  - For reproducible parsing, use `-n -H` and avoid context (`-C/-A/-B`).
173
- - RLM results are verified against real file lines; invalid or duplicate matches are dropped and reported.
174
-
176
+
175
177
  ## Development
176
178
 
177
179
  - Install locally: `pip install -e .` or `uv tool install .`
@@ -0,0 +1,14 @@
1
+ rlmgrep/__init__.py,sha256=tXbRXsO0NE_UV1kIHiZTTQQH0fj0U2KoxxNusu_gzrM,48
2
+ rlmgrep/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
3
+ rlmgrep/cli.py,sha256=dgv0GLL8nZ193h7b7EKum9mYDebWh-kd4drSufzmapw,17973
4
+ rlmgrep/config.py,sha256=xk8uB9M01Ih9yQDemY0BMVKTJQgBlvFGU51Zg01p3yE,2536
5
+ rlmgrep/file_map.py,sha256=x2Ri1wzK8_87GUorsAV01K_nYLZcv30yIquDeTCcdEw,876
6
+ rlmgrep/ingest.py,sha256=uCz2el9B-RIT9umFo-gFEdAsmWPP1IJOArFFQY0D_1A,9127
7
+ rlmgrep/interpreter.py,sha256=s_nMRxLlAU9C0JmUzUBW5NbVbuH67doVWF54K54STlA,2478
8
+ rlmgrep/render.py,sha256=w6KOfont2M7pQz_EEngTFMY5xJEE11N_ko8P9x5FdH8,3097
9
+ rlmgrep/rlm.py,sha256=LZfkyWxjvtf8dwo5JxetKvvpBYeGKhajwHEVpCb2eo4,4474
10
+ rlmgrep-0.1.1.dist-info/METADATA,sha256=W47e42Foa9jbgzqy-LPWpAyftVjhCZqbWJ8UeEJAW3s,5867
11
+ rlmgrep-0.1.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
+ rlmgrep-0.1.1.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
13
+ rlmgrep-0.1.1.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
14
+ rlmgrep-0.1.1.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- rlmgrep/__init__.py,sha256=tXbRXsO0NE_UV1kIHiZTTQQH0fj0U2KoxxNusu_gzrM,48
2
- rlmgrep/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
3
- rlmgrep/cli.py,sha256=D-Ze5YEPQSFQ_Hynfzk6Wvzby30fJe6s75NjsRIhux8,16291
4
- rlmgrep/config.py,sha256=A4Z6emTwpvbnwPmVrKM6OGRTpK6O4V3FIbWfi9lgI9g,2467
5
- rlmgrep/file_map.py,sha256=x2Ri1wzK8_87GUorsAV01K_nYLZcv30yIquDeTCcdEw,876
6
- rlmgrep/ingest.py,sha256=DgeyOYnSMpiQiWlhXjQi-DZFv6jlAl8v2KhXAKqfd44,8821
7
- rlmgrep/interpreter.py,sha256=s_nMRxLlAU9C0JmUzUBW5NbVbuH67doVWF54K54STlA,2478
8
- rlmgrep/render.py,sha256=w6KOfont2M7pQz_EEngTFMY5xJEE11N_ko8P9x5FdH8,3097
9
- rlmgrep/rlm.py,sha256=LZfkyWxjvtf8dwo5JxetKvvpBYeGKhajwHEVpCb2eo4,4474
10
- rlmgrep-0.1.0.dist-info/METADATA,sha256=1-ElFhF9t2VvVeVO6O90bOnIFd5xgrMkuXnp7q958A0,5807
11
- rlmgrep-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
12
- rlmgrep-0.1.0.dist-info/entry_points.txt,sha256=UV6QkEbkwBO1JJ53mm84_n35tVyOczPvOQ14ga7vrCI,45
13
- rlmgrep-0.1.0.dist-info/top_level.txt,sha256=gTujSRsO58c80eN7aRH2cfe51FHxx8LJ1w1Y2YlHti0,8
14
- rlmgrep-0.1.0.dist-info/RECORD,,