codebase-brief 0.1.2__tar.gz → 0.1.3__tar.gz

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.
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ - Find task-matching snippets deeper inside selected source files and show their line ranges instead of always starting at the file's beginning.
6
+ - Keep excerpt reads bounded and apply the existing secret redaction before including source snippets.
7
+
3
8
  ## 0.1.2
4
9
 
5
10
  - Add `brief --demo`, which generates a real example brief from temporary sample files without reading or modifying the user's project.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-brief
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Local-first CLI that builds task-focused context briefs from code repositories
5
5
  Author: Mohamed Maggy
6
6
  License-Expression: MIT
@@ -53,7 +53,7 @@ The first image is a terminal-style rendering of the app's actual numbered menu.
53
53
 
54
54
  You need Python 3.10 or newer. Git is optional, but required for recent-history and base-diff details.
55
55
 
56
- The current PyPI release is `0.1.2` and includes the review-feedback summary, importer-aware `--base` selection, and a sample demo. Install it with `pipx`:
56
+ The current PyPI release is `0.1.3` and includes the review-feedback summary, importer-aware `--base` selection, and a sample demo. Install it with `pipx`:
57
57
 
58
58
  ```powershell
59
59
  pipx install codebase-brief
@@ -90,6 +90,8 @@ When the menu opens, it uses the folder you launched it from as the starting pro
90
90
 
91
91
  In plain terms, Codebase Brief makes a small guide to a project so an AI coding assistant can get oriented before helping with a task. It does not write or fix code. It reads project information locally and prepares a guide for you to review and share.
92
92
 
93
+ For selected source files, it looks for code near words in your task and labels each excerpt with its source line range. This is a bounded, best-effort search: if it finds no clear match, it falls back to a general excerpt. Check the cited lines in your editor before relying on them.
94
+
93
95
  Before generating the brief, the tool shows suggested files and why they were selected. Press Enter to keep the checked files, type file numbers to toggle selections, or use `add path/to/file.py` to include another safe text file from the repository. Type `none` for an overview without source excerpts. After the brief is shown, saved, or copied, Codebase Brief prints a copyable summary of files kept, removed, added from suggestions, and manually added. To help improve file selection, paste that summary into the [tester feedback form](https://github.com/mohamed1111111m/codebase-brief/issues/new?template=tester-feedback.yml) along with your task and whether the brief helped. Do not include private source code or secrets. The menu's save-here choice writes `context.md` in the folder where you launched the menu; if that is the target repository, the file will be created there. Choose the show or copy option to avoid writing a file into that folder.
94
96
 
95
97
  For your first try, choose a project you know and a task you understand. Check whether the suggested files are the ones you would have shown a coding assistant yourself.
@@ -28,7 +28,7 @@ The first image is a terminal-style rendering of the app's actual numbered menu.
28
28
 
29
29
  You need Python 3.10 or newer. Git is optional, but required for recent-history and base-diff details.
30
30
 
31
- The current PyPI release is `0.1.2` and includes the review-feedback summary, importer-aware `--base` selection, and a sample demo. Install it with `pipx`:
31
+ The current PyPI release is `0.1.3` and includes the review-feedback summary, importer-aware `--base` selection, and a sample demo. Install it with `pipx`:
32
32
 
33
33
  ```powershell
34
34
  pipx install codebase-brief
@@ -65,6 +65,8 @@ When the menu opens, it uses the folder you launched it from as the starting pro
65
65
 
66
66
  In plain terms, Codebase Brief makes a small guide to a project so an AI coding assistant can get oriented before helping with a task. It does not write or fix code. It reads project information locally and prepares a guide for you to review and share.
67
67
 
68
+ For selected source files, it looks for code near words in your task and labels each excerpt with its source line range. This is a bounded, best-effort search: if it finds no clear match, it falls back to a general excerpt. Check the cited lines in your editor before relying on them.
69
+
68
70
  Before generating the brief, the tool shows suggested files and why they were selected. Press Enter to keep the checked files, type file numbers to toggle selections, or use `add path/to/file.py` to include another safe text file from the repository. Type `none` for an overview without source excerpts. After the brief is shown, saved, or copied, Codebase Brief prints a copyable summary of files kept, removed, added from suggestions, and manually added. To help improve file selection, paste that summary into the [tester feedback form](https://github.com/mohamed1111111m/codebase-brief/issues/new?template=tester-feedback.yml) along with your task and whether the brief helped. Do not include private source code or secrets. The menu's save-here choice writes `context.md` in the folder where you launched the menu; if that is the target repository, the file will be created there. Choose the show or copy option to avoid writing a file into that folder.
69
71
 
70
72
  For your first try, choose a project you know and a task you understand. Check whether the suggested files are the ones you would have shown a coding assistant yourself.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codebase-brief
3
- Version: 0.1.2
3
+ Version: 0.1.3
4
4
  Summary: Local-first CLI that builds task-focused context briefs from code repositories
5
5
  Author: Mohamed Maggy
6
6
  License-Expression: MIT
@@ -53,7 +53,7 @@ The first image is a terminal-style rendering of the app's actual numbered menu.
53
53
 
54
54
  You need Python 3.10 or newer. Git is optional, but required for recent-history and base-diff details.
55
55
 
56
- The current PyPI release is `0.1.2` and includes the review-feedback summary, importer-aware `--base` selection, and a sample demo. Install it with `pipx`:
56
+ The current PyPI release is `0.1.3` and includes the review-feedback summary, importer-aware `--base` selection, and a sample demo. Install it with `pipx`:
57
57
 
58
58
  ```powershell
59
59
  pipx install codebase-brief
@@ -90,6 +90,8 @@ When the menu opens, it uses the folder you launched it from as the starting pro
90
90
 
91
91
  In plain terms, Codebase Brief makes a small guide to a project so an AI coding assistant can get oriented before helping with a task. It does not write or fix code. It reads project information locally and prepares a guide for you to review and share.
92
92
 
93
+ For selected source files, it looks for code near words in your task and labels each excerpt with its source line range. This is a bounded, best-effort search: if it finds no clear match, it falls back to a general excerpt. Check the cited lines in your editor before relying on them.
94
+
93
95
  Before generating the brief, the tool shows suggested files and why they were selected. Press Enter to keep the checked files, type file numbers to toggle selections, or use `add path/to/file.py` to include another safe text file from the repository. Type `none` for an overview without source excerpts. After the brief is shown, saved, or copied, Codebase Brief prints a copyable summary of files kept, removed, added from suggestions, and manually added. To help improve file selection, paste that summary into the [tester feedback form](https://github.com/mohamed1111111m/codebase-brief/issues/new?template=tester-feedback.yml) along with your task and whether the brief helped. Do not include private source code or secrets. The menu's save-here choice writes `context.md` in the folder where you launched the menu; if that is the target repository, the file will be created there. Choose the show or copy option to avoid writing a file into that folder.
94
96
 
95
97
  For your first try, choose a project you know and a task you understand. Check whether the suggested files are the ones you would have shown a coding assistant yourself.
@@ -24,7 +24,7 @@ MAX_KEY_FILES = 12
24
24
  MAX_SELECTION_CHOICES = 30
25
25
  MAX_DISCOVERED_FILES = 20_000
26
26
  IMPORT_SUFFIXES = {".py", ".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"}
27
- VERSION = "0.1.2"
27
+ VERSION = "0.1.3"
28
28
  MANIFESTS = (
29
29
  "package.json", "pyproject.toml", "requirements.txt", "Cargo.toml",
30
30
  "go.mod", "Gemfile", "composer.json", "pom.xml",
@@ -203,6 +203,103 @@ def task_words(task: str) -> set[str]:
203
203
  return {word.lower() for word in re.findall(r"[A-Za-z][A-Za-z0-9_-]{2,}", task)}
204
204
 
205
205
 
206
+ _TASK_STOP_WORDS = {
207
+ "about", "after", "also", "and", "are", "does", "from", "give", "help",
208
+ "how", "into", "its", "make", "show", "that", "the", "this", "what", "with",
209
+ }
210
+
211
+
212
+ def read_task_excerpt(
213
+ root: Path, relative: str, task: str, limit: int = 1050,
214
+ max_bytes: int = 96_000,
215
+ ) -> str | None:
216
+ """Find compact, task-related source windows, including later definitions.
217
+
218
+ File ranking alone is not enough: returning only the first lines of a large
219
+ file can omit the function that made it relevant. Reads remain bounded and
220
+ pass through the same path and secret safeguards as ordinary excerpts.
221
+ """
222
+ path = safe_path(root, relative)
223
+ if path is None:
224
+ return None
225
+ try:
226
+ with path.open("rb") as source:
227
+ raw = source.read(max_bytes + 1)
228
+ except OSError:
229
+ return None
230
+ if b"\0" in raw[:4096]:
231
+ return None
232
+
233
+ truncated = len(raw) > max_bytes
234
+ text = raw[:max_bytes].decode("utf-8", errors="replace")
235
+ all_lines = text.splitlines()
236
+ if not all_lines:
237
+ return None
238
+ lines = [line[:500] for line in all_lines[:1500]]
239
+ truncated = truncated or len(all_lines) > len(lines) or any(len(line) > 500 for line in all_lines[:1500])
240
+ terms = {term for term in task_words(task) if term not in _TASK_STOP_WORDS}
241
+ # Split snake_case and hyphenated identifiers so "demo" can match
242
+ # generate_demo_brief without requiring an exact function-name query.
243
+ tokenized_lines = [
244
+ {part.lower() for part in re.findall(r"[A-Za-z][A-Za-z0-9]*", line)}
245
+ for line in lines
246
+ ]
247
+ frequencies = {
248
+ term: sum(term in line_terms for line_terms in tokenized_lines)
249
+ for term in terms
250
+ }
251
+ scores: list[float] = []
252
+ for line_terms in tokenized_lines:
253
+ scores.append(sum(
254
+ 1 + math.log((len(lines) + 1) / (frequencies[term] + 1))
255
+ for term in terms if term in line_terms
256
+ ))
257
+ for index, line in enumerate(lines):
258
+ if scores[index] and re.match(r"\s*(?:async\s+)?(?:def|class)\s+", line):
259
+ # In source files, a matching function/class declaration is a
260
+ # stronger anchor than a help string that merely mentions the task.
261
+ scores[index] += 8
262
+
263
+ if not any(scores):
264
+ return compact(redact("\n".join(lines)), limit)
265
+
266
+ # Make short windows around the strongest matching lines. Merge overlaps,
267
+ # then take the best windows until the excerpt budget is used.
268
+ candidates = sorted(range(len(lines)), key=lambda index: (-scores[index], index))
269
+ windows: list[tuple[int, int]] = []
270
+ for index in candidates:
271
+ if scores[index] == 0:
272
+ break
273
+ start, end = max(0, index - 4), min(len(lines), index + 11)
274
+ if any(start <= old_end and end >= old_start for old_start, old_end in windows):
275
+ continue
276
+ windows.append((start, end))
277
+ selected: list[tuple[int, int, str]] = []
278
+ used = 0
279
+ for start, end in windows:
280
+ body = "\n".join(lines[start:end]).strip()
281
+ snippet = f"Lines {start + 1}–{end}:\n{body}"
282
+ if not body:
283
+ continue
284
+ remaining = limit - used
285
+ if remaining <= 0:
286
+ break
287
+ if len(snippet) > remaining:
288
+ break
289
+ selected.append((start, end, snippet))
290
+ used += len(snippet) + 2
291
+ if not selected:
292
+ return compact("\n".join(lines), limit)
293
+
294
+ selected.sort(key=lambda item: item[0])
295
+ result = "\n…\n".join(snippet for _start, _end, snippet in selected)
296
+ if truncated:
297
+ result += "\nMore of this file was left out to keep the brief short."
298
+ elif len(selected) < len(windows):
299
+ result += "\nOther matching sections were left out to keep the brief short."
300
+ return redact(result)
301
+
302
+
206
303
  def discover_files(root: Path) -> list[str]:
207
304
  found: list[str] = []
208
305
  visited_dirs = 0
@@ -816,12 +913,14 @@ def gather(
816
913
  if label and label != "additional project file":
817
914
  key.append(f"{relative} is already included above; it was also {label}.")
818
915
  continue
819
- excerpt = read_excerpt(root, relative)
916
+ excerpt = read_task_excerpt(root, relative, task)
917
+ if excerpt is None:
918
+ fallback = read_excerpt(root, relative)
919
+ excerpt = fallback[0] if fallback else None
820
920
  if excerpt:
821
- content, truncated = excerpt
822
- content = compact(content, 1050)
921
+ content = compact(excerpt, 1050)
823
922
  label = reasons.get(relative, "selected by project signals")
824
- shortened_note = "" if "Some lines were left out" in content else ("\nMore of this file was left out to keep the brief short." if truncated else "")
923
+ shortened_note = "" if "Some lines were left out" in content or "More of this file was left out" in content else ""
825
924
  key.append(f"{relative} — {label}\n{excerpt_for_brief(relative, content)}" + shortened_note)
826
925
  if not key:
827
926
  if selected_files is not None and not key_files:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "codebase-brief"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Local-first CLI that builds task-focused context briefs from code repositories"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -6,7 +6,7 @@ import unittest
6
6
  from pathlib import Path
7
7
  from unittest.mock import patch
8
8
 
9
- from context_inject.cli import DEFAULT_BUDGET_TOKENS, MAX_CHARS, MAX_FILE_BYTES, MAX_KEY_FILES, choose_files, compact, copy_to_clipboard, gather, interactive, local_imports, main, read_excerpt, redact, review_feedback, safe_path, suggest_file_details, suggest_files
9
+ from context_inject.cli import DEFAULT_BUDGET_TOKENS, MAX_CHARS, MAX_FILE_BYTES, MAX_KEY_FILES, choose_files, compact, copy_to_clipboard, gather, interactive, local_imports, main, read_excerpt, read_task_excerpt, redact, review_feedback, safe_path, suggest_file_details, suggest_files
10
10
 
11
11
 
12
12
  class ContextInjectTests(unittest.TestCase):
@@ -75,6 +75,21 @@ class ContextInjectTests(unittest.TestCase):
75
75
  self.assertLess(output.index("Files that may help"), output.index("Technology and commands"))
76
76
  self.assertLessEqual(len(output), 1000 * 4)
77
77
 
78
+ def test_task_excerpt_finds_relevant_function_beyond_file_prefix(self):
79
+ filler = "\n".join(f"def unrelated_{index}(): return {index}" for index in range(180))
80
+ source = filler + "\n\ndef build_demo_brief():\n return gather(sample_files, task)\n"
81
+ self.put("src/app.py", source)
82
+
83
+ excerpt = read_task_excerpt(
84
+ self.root, "src/app.py",
85
+ "Explain how the built-in demo creates a sample context brief",
86
+ )
87
+
88
+ self.assertIsNotNone(excerpt)
89
+ self.assertIn("def build_demo_brief", excerpt)
90
+ self.assertIn("return gather(sample_files, task)", excerpt)
91
+ self.assertNotIn("def unrelated_0", excerpt)
92
+
78
93
  def test_excerpt_shortening_stops_on_a_word_boundary(self):
79
94
  excerpt = compact("This is a long sentence that should end on a complete word. " * 3, 90)
80
95
  self.assertTrue(excerpt.startswith("This is a long sentence that should\nSome lines were left out"))
@@ -486,7 +501,7 @@ class ContextInjectTests(unittest.TestCase):
486
501
  plain = __import__("re").sub(r"\x1b\[[0-9;]*m", "", output.getvalue())
487
502
  self.assertIn("CODEBASE", plain)
488
503
  self.assertIn("BRIEF", plain)
489
- self.assertIn("v0.1.2", plain)
504
+ self.assertIn("v0.1.3", plain)
490
505
  self.assertIn("Meet Briefy - your repo guide", plain)
491
506
  self.assertIn("WHAT THIS DOES", plain)
492
507
  self.assertIn("Makes a short guide to your project for an AI coding assistant", plain)
File without changes
File without changes