contextzip 0.3.0__tar.gz → 0.3.2__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.
Files changed (37) hide show
  1. {contextzip-0.3.0 → contextzip-0.3.2}/PKG-INFO +38 -2
  2. {contextzip-0.3.0 → contextzip-0.3.2}/README.md +37 -1
  3. contextzip-0.3.2/contextzip/__init__.py +34 -0
  4. contextzip-0.3.2/contextzip/api.py +400 -0
  5. contextzip-0.3.2/contextzip/cli.py +680 -0
  6. contextzip-0.3.2/contextzip/cli_ai.py +160 -0
  7. contextzip-0.3.2/contextzip/cli_display.py +331 -0
  8. contextzip-0.3.2/contextzip/cli_onboard.py +124 -0
  9. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/clipboard.py +15 -10
  10. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/config.py +3 -0
  11. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/detector.py +12 -13
  12. contextzip-0.3.2/contextzip/error_parser.py +506 -0
  13. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/filters.py +24 -20
  14. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/git.py +21 -14
  15. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/packager.py +111 -19
  16. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/rules/base.py +1 -9
  17. contextzip-0.3.2/contextzip/rules/errors/__init__.py +0 -0
  18. contextzip-0.3.2/contextzip/rules/errors/node.py +506 -0
  19. contextzip-0.3.2/contextzip/rules/errors/python.py +103 -0
  20. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/rules/node.py +1 -1
  21. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/rules/python.py +4 -4
  22. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/rules/ruby.py +6 -13
  23. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/rules/rust.py +1 -1
  24. contextzip-0.3.2/contextzip/watcher.py +662 -0
  25. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip.egg-info/PKG-INFO +38 -2
  26. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip.egg-info/SOURCES.txt +10 -1
  27. {contextzip-0.3.0 → contextzip-0.3.2}/pyproject.toml +2 -2
  28. contextzip-0.3.0/contextzip/__init__.py +0 -3
  29. contextzip-0.3.0/contextzip/cli.py +0 -1044
  30. {contextzip-0.3.0 → contextzip-0.3.2}/LICENSE +0 -0
  31. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/rules/__init__.py +0 -0
  32. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip/rules/go.py +0 -0
  33. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip.egg-info/dependency_links.txt +0 -0
  34. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip.egg-info/entry_points.txt +0 -0
  35. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip.egg-info/requires.txt +0 -0
  36. {contextzip-0.3.0 → contextzip-0.3.2}/contextzip.egg-info/top_level.txt +0 -0
  37. {contextzip-0.3.0 → contextzip-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextzip
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Intelligently package your codebase for AI tools
5
5
  Author-email: Deepesh <akadeepesh@gmail.com>
6
6
  License-Expression: MIT
@@ -51,6 +51,7 @@ contextzip eliminates that entirely. Run it from your project root — it detect
51
51
  - **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
52
52
  - **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
53
53
  - **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
54
+ - **Terminal error watcher** — wrap any dev server with `contextzip watch` to auto-detect errors and package a ready-to-upload debug context in one keypress
54
55
  - **Persistent workspace** — all generated ZIPs land in `.contextzip/` at your project root, discoverable, reusable, and git-ignored automatically
55
56
  - **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
56
57
  - **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
@@ -116,7 +117,7 @@ contextzip [OPTIONS]
116
117
  | `--no-clipboard` | Skip the clipboard / folder-open step |
117
118
  | `--no-gitignore` | Ignore the project's `.gitignore` |
118
119
 
119
- **Subcommands:** `exclude`, `include`, `config` — run `contextzip --help` for full details.
120
+ **Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
120
121
 
121
122
  ---
122
123
 
@@ -173,6 +174,41 @@ contextzip config --reset-key # clear and re-run setup
173
174
 
174
175
  ---
175
176
 
177
+ ## Terminal error watcher
178
+
179
+ The `watch` command wraps your dev server, buffers its output, and packages a debug-ready ZIP the moment you spot an error — no manual file hunting, no copy-pasting stack traces.
180
+
181
+ ```bash
182
+ contextzip watch -- npm run dev
183
+ contextzip watch -- python manage.py runserver
184
+ ```
185
+
186
+ contextzip starts your process normally. You see output exactly as you would without it. In the background, it watches the stream for errors. When one is detected, a prompt appears directly beneath the error output:
187
+
188
+ ```
189
+ ╭─ contextzip · error detected ─────────────────────╮
190
+ │ Press [D] to package debug context [S] to skip │
191
+ ╰───────────────────────────────────────────────────╯
192
+ ```
193
+
194
+ Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Your server keeps running — no restart, no interruption.
195
+
196
+ **What's in the ZIP:**
197
+
198
+ | File | Contents |
199
+ |---|---|
200
+ | `prompt.txt` | Auto-generated: detected framework, error type, and task description — ready to paste into any AI tool |
201
+ | `terminal-error.txt` | The cleaned, noise-stripped error block and stack trace |
202
+ | `source-files.zip` | Source files referenced in the stack trace, paths preserved |
203
+
204
+ **On Ctrl+C:** If no errors were packaged during the session, contextzip offers one final prompt to capture the full session output — useful when something looked wrong but didn't match a known error pattern.
205
+
206
+ **Supported frameworks:** Python, Django, FastAPI, Node.js, Next.js, React. Each has its own error detection patterns and noise filters so the output stays clean across stacks.
207
+
208
+ > **Note:** `watch` works best with dev servers that don't read stdin interactively (`npm run dev`, `manage.py runserver`, etc.). PTY emulation is not used — on Windows, color passthrough may be limited.
209
+
210
+ ---
211
+
176
212
  ## What gets excluded
177
213
 
178
214
  contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
@@ -22,6 +22,7 @@ contextzip eliminates that entirely. Run it from your project root — it detect
22
22
  - **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
23
23
  - **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
24
24
  - **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
25
+ - **Terminal error watcher** — wrap any dev server with `contextzip watch` to auto-detect errors and package a ready-to-upload debug context in one keypress
25
26
  - **Persistent workspace** — all generated ZIPs land in `.contextzip/` at your project root, discoverable, reusable, and git-ignored automatically
26
27
  - **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
27
28
  - **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
@@ -87,7 +88,7 @@ contextzip [OPTIONS]
87
88
  | `--no-clipboard` | Skip the clipboard / folder-open step |
88
89
  | `--no-gitignore` | Ignore the project's `.gitignore` |
89
90
 
90
- **Subcommands:** `exclude`, `include`, `config` — run `contextzip --help` for full details.
91
+ **Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
91
92
 
92
93
  ---
93
94
 
@@ -144,6 +145,41 @@ contextzip config --reset-key # clear and re-run setup
144
145
 
145
146
  ---
146
147
 
148
+ ## Terminal error watcher
149
+
150
+ The `watch` command wraps your dev server, buffers its output, and packages a debug-ready ZIP the moment you spot an error — no manual file hunting, no copy-pasting stack traces.
151
+
152
+ ```bash
153
+ contextzip watch -- npm run dev
154
+ contextzip watch -- python manage.py runserver
155
+ ```
156
+
157
+ contextzip starts your process normally. You see output exactly as you would without it. In the background, it watches the stream for errors. When one is detected, a prompt appears directly beneath the error output:
158
+
159
+ ```
160
+ ╭─ contextzip · error detected ─────────────────────╮
161
+ │ Press [D] to package debug context [S] to skip │
162
+ ╰───────────────────────────────────────────────────╯
163
+ ```
164
+
165
+ Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Your server keeps running — no restart, no interruption.
166
+
167
+ **What's in the ZIP:**
168
+
169
+ | File | Contents |
170
+ |---|---|
171
+ | `prompt.txt` | Auto-generated: detected framework, error type, and task description — ready to paste into any AI tool |
172
+ | `terminal-error.txt` | The cleaned, noise-stripped error block and stack trace |
173
+ | `source-files.zip` | Source files referenced in the stack trace, paths preserved |
174
+
175
+ **On Ctrl+C:** If no errors were packaged during the session, contextzip offers one final prompt to capture the full session output — useful when something looked wrong but didn't match a known error pattern.
176
+
177
+ **Supported frameworks:** Python, Django, FastAPI, Node.js, Next.js, React. Each has its own error detection patterns and noise filters so the output stays clean across stacks.
178
+
179
+ > **Note:** `watch` works best with dev servers that don't read stdin interactively (`npm run dev`, `manage.py runserver`, etc.). PTY emulation is not used — on Windows, color passthrough may be limited.
180
+
181
+ ---
182
+
147
183
  ## What gets excluded
148
184
 
149
185
  contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
@@ -0,0 +1,34 @@
1
+ """contextzip — intelligent codebase packager for AI tools."""
2
+
3
+ __version__ = "0.3.2"
4
+
5
+ from contextzip.api import (
6
+ FileCollection,
7
+ ContextzipError,
8
+ NotARepositoryError,
9
+ GitNotFoundError,
10
+ GitCommandError,
11
+ NoFilesError,
12
+ get_git_changes,
13
+ get_files,
14
+ create_zip,
15
+ detect_ecosystem,
16
+ )
17
+ from contextzip.packager import PackageResult
18
+
19
+ __all__ = [
20
+ # Functions
21
+ "get_git_changes",
22
+ "get_files",
23
+ "create_zip",
24
+ "detect_ecosystem",
25
+ # Data types
26
+ "FileCollection",
27
+ "PackageResult",
28
+ # Exceptions
29
+ "ContextzipError",
30
+ "NotARepositoryError",
31
+ "GitNotFoundError",
32
+ "GitCommandError",
33
+ "NoFilesError",
34
+ ]
@@ -0,0 +1,400 @@
1
+ """
2
+ api.py — Public Python API for contextzip.
3
+
4
+ Exposes the same capabilities as the CLI but as plain Python functions:
5
+ no Click, no Rich output, no SystemExit. All functions raise exceptions
6
+ on failure so callers can handle errors in their own way.
7
+
8
+ Quickstart
9
+ ──────────
10
+ from contextzip import get_git_changes, get_files, create_zip
11
+
12
+ # Get git-changed files and use them directly (no zip needed)
13
+ collection = get_git_changes()
14
+ for path in collection.files:
15
+ upload(path) # plain pathlib.Path objects
16
+
17
+ # Or zip them
18
+ pkg = create_zip(collection, output="/tmp/changes.zip")
19
+ with open(pkg.zip_path, "rb") as f:
20
+ upload_to_s3(f)
21
+
22
+ # Get all project files (respecting .gitignore and built-in rules)
23
+ collection = get_files()
24
+ for path in collection.files:
25
+ print(path)
26
+
27
+ # Narrow it down
28
+ collection = get_files(
29
+ include=["src/", "app/"],
30
+ exclude=["tests/", "*.log"],
31
+ )
32
+ pkg = create_zip(collection, output="/tmp/upload.zip")
33
+ print(f"Packed {pkg.file_count} files → {pkg.zip_path}")
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import os
39
+ from dataclasses import dataclass, field
40
+ from pathlib import Path
41
+
42
+ from contextzip.detector import DetectionResult, detect
43
+ from contextzip.filters import (
44
+ ResolveResult,
45
+ build_spec,
46
+ resolve_files,
47
+ resolve_files_from_git,
48
+ )
49
+ from contextzip.git import GitChanges, GitError, GitErrorKind, get_changed_files
50
+ from contextzip.packager import PackageResult, create_zip_silent
51
+
52
+
53
+ # ---------------------------------------------------------------------------
54
+ # Public result type
55
+ # ---------------------------------------------------------------------------
56
+
57
+
58
+ @dataclass
59
+ class FileCollection:
60
+ """
61
+ A resolved set of files returned by :func:`get_git_changes` or
62
+ :func:`get_files`.
63
+
64
+ ``files`` is always a plain list of absolute :class:`pathlib.Path` objects
65
+ — use them directly for uploads, processing, or anything else.
66
+ Zipping is optional: pass this object to :func:`create_zip` if needed.
67
+
68
+ Attributes
69
+ ----------
70
+ files:
71
+ Absolute paths of all files in this collection. These are the
72
+ files that would be (or were) included in a ZIP.
73
+ skipped:
74
+ Files that were silently skipped during resolution, as
75
+ ``(path, reason)`` tuples (e.g. dangling symlinks, unreadable files).
76
+ large_files:
77
+ Files exceeding 1 MB, as ``(path, size_in_bytes)`` tuples.
78
+ They are still included in ``files`` — this is informational only.
79
+ binary_files:
80
+ Files that appear to be binary (contain null bytes). Still included
81
+ in ``files`` — informational only.
82
+ project_dir:
83
+ The project root used when resolving paths.
84
+ ecosystem:
85
+ Detected ecosystem string, e.g. ``"Next.js + Node.js"``.
86
+ """
87
+
88
+ files: list[Path] = field(default_factory=list)
89
+ skipped: list[tuple[Path, str]] = field(default_factory=list)
90
+ large_files: list[tuple[Path, int]] = field(default_factory=list)
91
+ binary_files: list[Path] = field(default_factory=list)
92
+ project_dir: Path = field(default_factory=Path.cwd)
93
+ ecosystem: str = "Unknown"
94
+
95
+ def __len__(self) -> int:
96
+ return len(self.files)
97
+
98
+ def __iter__(self):
99
+ return iter(self.files)
100
+
101
+ def __bool__(self) -> bool:
102
+ return bool(self.files)
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # Exceptions
107
+ # ---------------------------------------------------------------------------
108
+
109
+
110
+ class ContextzipError(Exception):
111
+ """Base class for all contextzip API errors."""
112
+
113
+
114
+ class NotARepositoryError(ContextzipError):
115
+ """Raised when the project directory is not inside a git repository."""
116
+
117
+
118
+ class GitNotFoundError(ContextzipError):
119
+ """Raised when git is not installed or not on PATH."""
120
+
121
+
122
+ class GitCommandError(ContextzipError):
123
+ """Raised when a git command fails unexpectedly."""
124
+
125
+
126
+ class NoFilesError(ContextzipError):
127
+ """Raised when file resolution produces an empty result."""
128
+
129
+
130
+ # ---------------------------------------------------------------------------
131
+ # Public API
132
+ # ---------------------------------------------------------------------------
133
+
134
+
135
+ def get_git_changes(
136
+ path: str | Path | None = None,
137
+ ) -> FileCollection:
138
+ """
139
+ Return the files that git reports as modified, added, or untracked.
140
+
141
+ The git root is found automatically by walking up from *path* (or the
142
+ current working directory). Files are filtered through contextzip's base
143
+ safety rules so secrets, binaries, and similar files are always excluded.
144
+
145
+ Parameters
146
+ ----------
147
+ path:
148
+ Directory to start from. Defaults to ``Path.cwd()``. The actual
149
+ git root may be a parent of this directory.
150
+
151
+ Returns
152
+ -------
153
+ FileCollection
154
+ ``collection.files`` contains absolute :class:`~pathlib.Path` objects
155
+ for every changed file. Iterate over it or pass it to
156
+ :func:`create_zip`.
157
+
158
+ Raises
159
+ ------
160
+ GitNotFoundError
161
+ If git is not installed or not on PATH.
162
+ NotARepositoryError
163
+ If *path* is not inside a git repository.
164
+ GitCommandError
165
+ If ``git status`` fails for any other reason.
166
+
167
+ Example
168
+ -------
169
+ ::
170
+
171
+ from contextzip import get_git_changes
172
+
173
+ collection = get_git_changes()
174
+ for f in collection.files:
175
+ print(f) # plain pathlib.Path — use however you like
176
+
177
+ # Only staged files
178
+ for rel in collection._git_changes.staged:
179
+ print(rel)
180
+ """
181
+ project_dir = _resolve_dir(path)
182
+ git_result = get_changed_files(project_dir)
183
+
184
+ if isinstance(git_result, GitError):
185
+ _raise_git_error(git_result)
186
+
187
+ if git_result.is_empty:
188
+ return FileCollection(project_dir=project_dir)
189
+
190
+ resolved = resolve_files_from_git(
191
+ git_files=git_result.files,
192
+ project_dir=project_dir,
193
+ )
194
+
195
+ detection = detect(project_dir)
196
+
197
+ collection = _resolve_result_to_collection(resolved, project_dir, detection)
198
+ # Stash the raw GitChanges on the collection for callers who want
199
+ # staged/unstaged/untracked breakdowns without accessing internals
200
+ collection._git_changes = git_result # type: ignore[attr-defined]
201
+ return collection
202
+
203
+
204
+ def get_files(
205
+ path: str | Path | None = None,
206
+ *,
207
+ include: list[str] | None = None,
208
+ exclude: list[str] | None = None,
209
+ use_gitignore: bool = True,
210
+ ) -> FileCollection:
211
+ """
212
+ Return all project files after applying contextzip's standard exclusion rules.
213
+
214
+ Parameters
215
+ ----------
216
+ path:
217
+ Project root to scan. Defaults to ``Path.cwd()``.
218
+ include:
219
+ If given, only files under these paths are returned (e.g.
220
+ ``["src/", "app/"]``). Matched as exact path prefixes.
221
+ exclude:
222
+ Extra exclusion patterns on top of the auto-detected rules
223
+ (gitignore syntax, e.g. ``["tests/", "*.log"]``).
224
+ use_gitignore:
225
+ Whether to apply the project's ``.gitignore`` file.
226
+ Defaults to ``True``.
227
+
228
+ Returns
229
+ -------
230
+ FileCollection
231
+ ``collection.files`` contains absolute :class:`~pathlib.Path` objects
232
+ for every included file.
233
+
234
+ Example
235
+ -------
236
+ ::
237
+
238
+ from contextzip import get_files
239
+
240
+ # All project files
241
+ collection = get_files()
242
+
243
+ # Only src/, excluding tests
244
+ collection = get_files(include=["src/"], exclude=["tests/"])
245
+
246
+ for f in collection.files:
247
+ process(f)
248
+ """
249
+ project_dir = _resolve_dir(path)
250
+ detection = detect(project_dir)
251
+
252
+ gitignore_path = (project_dir / ".gitignore") if use_gitignore else None
253
+
254
+ spec = build_spec(
255
+ rule_modules=detection.rule_modules,
256
+ extra_exclude=exclude or None,
257
+ gitignore_path=gitignore_path,
258
+ )
259
+
260
+ resolved = resolve_files(
261
+ project_dir=project_dir,
262
+ spec=spec,
263
+ include_only=include or None,
264
+ )
265
+
266
+ return _resolve_result_to_collection(resolved, project_dir, detection)
267
+
268
+
269
+ def create_zip(
270
+ collection: FileCollection,
271
+ output: str | Path | None = None,
272
+ ) -> PackageResult:
273
+ """
274
+ Write *collection* into a ZIP archive and return the result.
275
+
276
+ Parameters
277
+ ----------
278
+ collection:
279
+ A :class:`FileCollection` returned by :func:`get_git_changes` or
280
+ :func:`get_files`.
281
+ output:
282
+ Where to write the ZIP. If omitted, the archive is written to the
283
+ ``.contextzip/`` workspace at the project root (same as the CLI).
284
+ Pass an explicit path to control where it lands — useful when you
285
+ want to write to a temp directory before uploading.
286
+
287
+ Returns
288
+ -------
289
+ PackageResult
290
+ Contains ``zip_path`` (a :class:`~pathlib.Path`), ``file_count``,
291
+ ``compressed_bytes``, ``uncompressed_bytes``, and ``skipped_in_zip``.
292
+
293
+ Raises
294
+ ------
295
+ NoFilesError
296
+ If *collection* is empty (nothing to zip).
297
+ OSError
298
+ If the ZIP file cannot be written.
299
+
300
+ Example
301
+ -------
302
+ ::
303
+
304
+ import tempfile
305
+ from contextzip import get_git_changes, create_zip
306
+
307
+ collection = get_git_changes()
308
+ with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp:
309
+ pkg = create_zip(collection, output=tmp.name)
310
+
311
+ with open(pkg.zip_path, "rb") as f:
312
+ upload_to_s3(f.read())
313
+
314
+ print(f"Packed {pkg.file_count} files, {pkg.compressed_bytes} bytes compressed")
315
+ """
316
+ if not collection.files:
317
+ raise NoFilesError(
318
+ "The FileCollection is empty — nothing to zip. "
319
+ "Check get_git_changes() or get_files() returned files."
320
+ )
321
+
322
+ resolve_result = ResolveResult(
323
+ included=collection.files,
324
+ skipped=collection.skipped,
325
+ large_files=collection.large_files,
326
+ binary_files=collection.binary_files,
327
+ )
328
+
329
+ output_path = Path(output).resolve() if output is not None else None
330
+
331
+ return create_zip_silent(
332
+ resolve_result=resolve_result,
333
+ project_dir=collection.project_dir,
334
+ output_path=output_path,
335
+ )
336
+
337
+
338
+ def detect_ecosystem(
339
+ path: str | Path | None = None,
340
+ ) -> DetectionResult:
341
+ """
342
+ Detect the ecosystem(s) present in a project directory.
343
+
344
+ Parameters
345
+ ----------
346
+ path:
347
+ Directory to inspect. Defaults to ``Path.cwd()``.
348
+
349
+ Returns
350
+ -------
351
+ DetectionResult
352
+ Has ``.ecosystems`` (list of strings like ``["Next.js", "Node.js"]``),
353
+ ``.display_name`` (e.g. ``"Next.js + Node.js"``), and
354
+ ``.confidence`` (``"low"`` / ``"medium"`` / ``"high"``).
355
+
356
+ Example
357
+ -------
358
+ ::
359
+
360
+ from contextzip import detect_ecosystem
361
+
362
+ result = detect_ecosystem("/path/to/project")
363
+ print(result.display_name) # "Django + Python"
364
+ print(result.confidence) # "high"
365
+ """
366
+ return detect(_resolve_dir(path))
367
+
368
+
369
+ # ---------------------------------------------------------------------------
370
+ # Internal helpers
371
+ # ---------------------------------------------------------------------------
372
+
373
+
374
+ def _resolve_dir(path: str | Path | None) -> Path:
375
+ if path is None:
376
+ return Path(os.getcwd()).resolve()
377
+ return Path(path).resolve()
378
+
379
+
380
+ def _resolve_result_to_collection(
381
+ resolved: ResolveResult,
382
+ project_dir: Path,
383
+ detection: DetectionResult,
384
+ ) -> FileCollection:
385
+ return FileCollection(
386
+ files=resolved.included,
387
+ skipped=resolved.skipped,
388
+ large_files=resolved.large_files,
389
+ binary_files=resolved.binary_files,
390
+ project_dir=project_dir,
391
+ ecosystem=detection.display_name,
392
+ )
393
+
394
+
395
+ def _raise_git_error(error: GitError) -> None:
396
+ if error.kind == GitErrorKind.GIT_NOT_FOUND:
397
+ raise GitNotFoundError(error.message)
398
+ if error.kind == GitErrorKind.NOT_A_REPO:
399
+ raise NotARepositoryError(error.message)
400
+ raise GitCommandError(error.message)