contextzip 0.4.7__tar.gz → 0.5.0__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.
- {contextzip-0.4.7 → contextzip-0.5.0}/PKG-INFO +23 -11
- {contextzip-0.4.7 → contextzip-0.5.0}/README.md +22 -10
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/__init__.py +1 -1
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/api.py +39 -20
- contextzip-0.5.0/contextzip/applier.py +1357 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/cleanup.py +16 -1
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/cli.py +387 -44
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/cli_ai.py +2 -2
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/cli_display.py +132 -49
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/config.py +28 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/detector.py +12 -1
- contextzip-0.5.0/contextzip/editor.py +124 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/error_parser.py +54 -12
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/filters.py +17 -2
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/git.py +102 -36
- contextzip-0.5.0/contextzip/guide.py +134 -0
- contextzip-0.5.0/contextzip/merge3.py +149 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/packager.py +89 -35
- contextzip-0.5.0/contextzip/pathmatch.py +265 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/project_config.py +57 -5
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/redact.py +2 -3
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/report.py +43 -3
- contextzip-0.5.0/contextzip/root.py +331 -0
- contextzip-0.5.0/contextzip/snapshots.py +259 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/watcher.py +11 -1
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/webui/assets.py +17 -2
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/webui/persist.py +3 -0
- contextzip-0.5.0/contextzip/webui/review.py +360 -0
- contextzip-0.5.0/contextzip/webui/review_assets.py +407 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/webui/server.py +7 -0
- contextzip-0.5.0/contextzip/workspaces.py +138 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip.egg-info/PKG-INFO +23 -11
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip.egg-info/SOURCES.txt +9 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/pyproject.toml +1 -1
- contextzip-0.4.7/contextzip/applier.py +0 -808
- {contextzip-0.4.7 → contextzip-0.5.0}/LICENSE +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/_x11_clip.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/ai/__init__.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/ai/gemini.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/ai/heuristic.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/ai/selector.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/cli_onboard.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/clipboard.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/__init__.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/base.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/errors/__init__.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/errors/node.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/errors/python.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/go.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/node.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/python.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/ruby.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/rules/rust.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/updater.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/webui/__init__.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip/webui/suggestions.py +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip.egg-info/dependency_links.txt +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip.egg-info/entry_points.txt +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip.egg-info/requires.txt +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/contextzip.egg-info/top_level.txt +0 -0
- {contextzip-0.4.7 → contextzip-0.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: contextzip
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Intelligently package your codebase for AI tools
|
|
5
5
|
Author-email: Deepesh <akadeepesh@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -120,6 +120,7 @@ contextzip --git-changes # only what you've changed
|
|
|
120
120
|
contextzip --prompt "fix the login redirect bug" # let AI pick the relevant files
|
|
121
121
|
contextzip include src/ app/ # only these folders
|
|
122
122
|
contextzip include src/ .env # .env too, values redacted
|
|
123
|
+
contextzip --here # only the folder you're in (monorepos)
|
|
123
124
|
contextzip exclude tests/ '*.snap' # everything except these
|
|
124
125
|
contextzip apply-zip # write the AI's changes back
|
|
125
126
|
```
|
|
@@ -163,7 +164,7 @@ Describe your task in plain language and [Gemini](https://ai.google.dev/) picks
|
|
|
163
164
|
|
|
164
165
|
- **Setup:** you need a free Gemini API key. contextzip walks you through it the first time you use `--prompt`, or you can set the `GEMINI_API_KEY` environment variable.
|
|
165
166
|
- **What is sent:** only the list of file **paths and sizes** that already passed the exclusion rules, plus your prompt. File contents never leave your machine.
|
|
166
|
-
- **Fallback:** if Gemini is rate-limited
|
|
167
|
+
- **Fallback:** if Gemini is rate-limited, a local keyword-based selector takes over, so `--prompt` keeps working. Other errors (a bad key, no network) stop with a clear message.
|
|
167
168
|
- **Model:** `gemini-2.5-flash-lite` by default.
|
|
168
169
|
|
|
169
170
|
### Applying AI changes back
|
|
@@ -174,17 +175,25 @@ contextzip apply-zip fix.zip # or point at one directly
|
|
|
174
175
|
contextzip apply-zip --dry-run # preview without writing anything
|
|
175
176
|
```
|
|
176
177
|
|
|
177
|
-
|
|
178
|
+
AI tools return ZIPs in every shape: wrapped in an extra folder, relative to a subfolder, flattened, with Windows separators. `apply-zip` matches each file to its real place in your project anyway, and new files follow their siblings.
|
|
178
179
|
|
|
179
|
-
|
|
180
|
+
Every ZIP contextzip makes gets a **local snapshot** (a hash of each file, never uploaded). `apply-zip` picks the snapshot that best matches the returned ZIP and compares three versions of each file: what you sent, what's on disk now, and what came back.
|
|
181
|
+
|
|
182
|
+
| Status | Meaning | Default |
|
|
180
183
|
|---|---|---|
|
|
181
|
-
| **New** |
|
|
182
|
-
| **
|
|
183
|
-
| **Unchanged** |
|
|
184
|
-
| **
|
|
185
|
-
| **
|
|
184
|
+
| **New** / **Modified** | New file, or only the AI changed it | Written |
|
|
185
|
+
| **Merged** | You both changed it, in different places | Written (three-way merge) |
|
|
186
|
+
| **Unchanged** | Already matches — including changes you already applied | Skipped |
|
|
187
|
+
| **Conflict** | You both changed the same lines | Needs your call |
|
|
188
|
+
| **Partial** | The AI returned a cut-down file (`// ... rest unchanged`) | Needs your call |
|
|
189
|
+
| **Moved** | The AI moved or renamed it | Written at the new path; the old file is kept unless you say otherwise |
|
|
190
|
+
| **Drifted** / **Deleted** / **Untracked** / **Ambiguous** / **Delete requested** | Can't be applied safely without you | Needs your call |
|
|
191
|
+
|
|
192
|
+
When something needs a decision you choose: apply the safe changes now, **review side by side in your browser** (`--review`), **resolve in VS Code's merge editor** (`--editor`), or apply everything. Line endings never count as a change, redacted secrets are never overwritten with placeholders, and **files are only removed with your explicit OK** — never one you've changed since.
|
|
193
|
+
|
|
194
|
+
### Snapshots and CONTEXTZIP.md
|
|
186
195
|
|
|
187
|
-
`
|
|
196
|
+
Every package is kept locally as a numbered **snapshot** (`#14`), so a reply can be merged against exactly what you sent, even after you've packaged again. Each ZIP also carries a short **`CONTEXTZIP.md`** for the AI: the snapshot ID, and how to reply so the answer applies cleanly (complete files, same paths, secrets left as `[REDACTED]`, moves and deletions listed). When the reply echoes it back, `apply-zip` knows the exact snapshot. `contextzip snapshots` lists them; turn the file off with `"zip_guide": false`.
|
|
188
197
|
|
|
189
198
|
### Terminal error watcher
|
|
190
199
|
|
|
@@ -266,6 +275,8 @@ This holds per-project settings. Edit it by hand or with `contextzip config --ui
|
|
|
266
275
|
},
|
|
267
276
|
"applied_zip_retention": 1, // past apply-zip archives to keep
|
|
268
277
|
"cleanup": { "enabled": true, "keep_recent": 1 },
|
|
278
|
+
"snapshots": { "keep": 10, "max_age_days": 14 }, // history apply-zip merges against
|
|
279
|
+
"zip_guide": true, // add CONTEXTZIP.md to every ZIP
|
|
269
280
|
"webui": { "auto_open": true, "port": null }
|
|
270
281
|
}
|
|
271
282
|
```
|
|
@@ -328,11 +339,12 @@ contextzip keeps everything in a `.contextzip/` folder at your git root (change
|
|
|
328
339
|
│ ├── git-changes/ changes.zip + manifest + report (--git-changes)
|
|
329
340
|
│ ├── prompt/ vibe.zip + manifest + report (--prompt)
|
|
330
341
|
│ └── watch/ debug-context.zip (contextzip watch)
|
|
342
|
+
├── snapshots/ numbered copies of what you sent (contextzip snapshots)
|
|
331
343
|
├── inbox/ drop AI-returned ZIPs here for apply-zip
|
|
332
344
|
└── config.json project config (optional)
|
|
333
345
|
```
|
|
334
346
|
|
|
335
|
-
Old outputs are cleaned up automatically, and
|
|
347
|
+
Old outputs are cleaned up automatically: `output/` keeps the latest ZIP per mode (`cleanup`), and `snapshots/` keeps the newest 10 or anything under 14 days old (`snapshots`) — never one a reply in the inbox is waiting on.
|
|
336
348
|
|
|
337
349
|
## Python API
|
|
338
350
|
|
|
@@ -91,6 +91,7 @@ contextzip --git-changes # only what you've changed
|
|
|
91
91
|
contextzip --prompt "fix the login redirect bug" # let AI pick the relevant files
|
|
92
92
|
contextzip include src/ app/ # only these folders
|
|
93
93
|
contextzip include src/ .env # .env too, values redacted
|
|
94
|
+
contextzip --here # only the folder you're in (monorepos)
|
|
94
95
|
contextzip exclude tests/ '*.snap' # everything except these
|
|
95
96
|
contextzip apply-zip # write the AI's changes back
|
|
96
97
|
```
|
|
@@ -134,7 +135,7 @@ Describe your task in plain language and [Gemini](https://ai.google.dev/) picks
|
|
|
134
135
|
|
|
135
136
|
- **Setup:** you need a free Gemini API key. contextzip walks you through it the first time you use `--prompt`, or you can set the `GEMINI_API_KEY` environment variable.
|
|
136
137
|
- **What is sent:** only the list of file **paths and sizes** that already passed the exclusion rules, plus your prompt. File contents never leave your machine.
|
|
137
|
-
- **Fallback:** if Gemini is rate-limited
|
|
138
|
+
- **Fallback:** if Gemini is rate-limited, a local keyword-based selector takes over, so `--prompt` keeps working. Other errors (a bad key, no network) stop with a clear message.
|
|
138
139
|
- **Model:** `gemini-2.5-flash-lite` by default.
|
|
139
140
|
|
|
140
141
|
### Applying AI changes back
|
|
@@ -145,17 +146,25 @@ contextzip apply-zip fix.zip # or point at one directly
|
|
|
145
146
|
contextzip apply-zip --dry-run # preview without writing anything
|
|
146
147
|
```
|
|
147
148
|
|
|
148
|
-
|
|
149
|
+
AI tools return ZIPs in every shape: wrapped in an extra folder, relative to a subfolder, flattened, with Windows separators. `apply-zip` matches each file to its real place in your project anyway, and new files follow their siblings.
|
|
149
150
|
|
|
150
|
-
|
|
151
|
+
Every ZIP contextzip makes gets a **local snapshot** (a hash of each file, never uploaded). `apply-zip` picks the snapshot that best matches the returned ZIP and compares three versions of each file: what you sent, what's on disk now, and what came back.
|
|
152
|
+
|
|
153
|
+
| Status | Meaning | Default |
|
|
151
154
|
|---|---|---|
|
|
152
|
-
| **New** |
|
|
153
|
-
| **
|
|
154
|
-
| **Unchanged** |
|
|
155
|
-
| **
|
|
156
|
-
| **
|
|
155
|
+
| **New** / **Modified** | New file, or only the AI changed it | Written |
|
|
156
|
+
| **Merged** | You both changed it, in different places | Written (three-way merge) |
|
|
157
|
+
| **Unchanged** | Already matches — including changes you already applied | Skipped |
|
|
158
|
+
| **Conflict** | You both changed the same lines | Needs your call |
|
|
159
|
+
| **Partial** | The AI returned a cut-down file (`// ... rest unchanged`) | Needs your call |
|
|
160
|
+
| **Moved** | The AI moved or renamed it | Written at the new path; the old file is kept unless you say otherwise |
|
|
161
|
+
| **Drifted** / **Deleted** / **Untracked** / **Ambiguous** / **Delete requested** | Can't be applied safely without you | Needs your call |
|
|
162
|
+
|
|
163
|
+
When something needs a decision you choose: apply the safe changes now, **review side by side in your browser** (`--review`), **resolve in VS Code's merge editor** (`--editor`), or apply everything. Line endings never count as a change, redacted secrets are never overwritten with placeholders, and **files are only removed with your explicit OK** — never one you've changed since.
|
|
164
|
+
|
|
165
|
+
### Snapshots and CONTEXTZIP.md
|
|
157
166
|
|
|
158
|
-
`
|
|
167
|
+
Every package is kept locally as a numbered **snapshot** (`#14`), so a reply can be merged against exactly what you sent, even after you've packaged again. Each ZIP also carries a short **`CONTEXTZIP.md`** for the AI: the snapshot ID, and how to reply so the answer applies cleanly (complete files, same paths, secrets left as `[REDACTED]`, moves and deletions listed). When the reply echoes it back, `apply-zip` knows the exact snapshot. `contextzip snapshots` lists them; turn the file off with `"zip_guide": false`.
|
|
159
168
|
|
|
160
169
|
### Terminal error watcher
|
|
161
170
|
|
|
@@ -237,6 +246,8 @@ This holds per-project settings. Edit it by hand or with `contextzip config --ui
|
|
|
237
246
|
},
|
|
238
247
|
"applied_zip_retention": 1, // past apply-zip archives to keep
|
|
239
248
|
"cleanup": { "enabled": true, "keep_recent": 1 },
|
|
249
|
+
"snapshots": { "keep": 10, "max_age_days": 14 }, // history apply-zip merges against
|
|
250
|
+
"zip_guide": true, // add CONTEXTZIP.md to every ZIP
|
|
240
251
|
"webui": { "auto_open": true, "port": null }
|
|
241
252
|
}
|
|
242
253
|
```
|
|
@@ -299,11 +310,12 @@ contextzip keeps everything in a `.contextzip/` folder at your git root (change
|
|
|
299
310
|
│ ├── git-changes/ changes.zip + manifest + report (--git-changes)
|
|
300
311
|
│ ├── prompt/ vibe.zip + manifest + report (--prompt)
|
|
301
312
|
│ └── watch/ debug-context.zip (contextzip watch)
|
|
313
|
+
├── snapshots/ numbered copies of what you sent (contextzip snapshots)
|
|
302
314
|
├── inbox/ drop AI-returned ZIPs here for apply-zip
|
|
303
315
|
└── config.json project config (optional)
|
|
304
316
|
```
|
|
305
317
|
|
|
306
|
-
Old outputs are cleaned up automatically, and
|
|
318
|
+
Old outputs are cleaned up automatically: `output/` keeps the latest ZIP per mode (`cleanup`), and `snapshots/` keeps the newest 10 or anything under 14 days old (`snapshots`) — never one a reply in the inbox is waiting on.
|
|
307
319
|
|
|
308
320
|
## Python API
|
|
309
321
|
|
|
@@ -35,7 +35,6 @@ Quickstart
|
|
|
35
35
|
|
|
36
36
|
from __future__ import annotations
|
|
37
37
|
|
|
38
|
-
import os
|
|
39
38
|
from dataclasses import dataclass, field
|
|
40
39
|
from pathlib import Path
|
|
41
40
|
|
|
@@ -158,7 +157,7 @@ def get_git_changes(
|
|
|
158
157
|
Parameters
|
|
159
158
|
----------
|
|
160
159
|
path:
|
|
161
|
-
Directory to start from. Defaults to
|
|
160
|
+
Directory to start from. Defaults to the project root found from the current folder. The actual
|
|
162
161
|
git root may be a parent of this directory.
|
|
163
162
|
use_project_config:
|
|
164
163
|
Apply the project's ``.contextzip/config.json``. Defaults to
|
|
@@ -234,7 +233,7 @@ def get_files(
|
|
|
234
233
|
Parameters
|
|
235
234
|
----------
|
|
236
235
|
path:
|
|
237
|
-
Project root to scan. Defaults to
|
|
236
|
+
Project root to scan. Defaults to the project root found from the current folder.
|
|
238
237
|
include:
|
|
239
238
|
If given, only files under these paths are returned (e.g.
|
|
240
239
|
``["src/", "app/"]``). Matched as exact path prefixes.
|
|
@@ -358,10 +357,14 @@ def create_zip(
|
|
|
358
357
|
|
|
359
358
|
output_path = Path(output).resolve() if output is not None else None
|
|
360
359
|
|
|
360
|
+
project_cfg = load_project_config(collection.project_dir)
|
|
361
361
|
return create_zip_silent(
|
|
362
362
|
resolve_result=resolve_result,
|
|
363
363
|
project_dir=collection.project_dir,
|
|
364
364
|
output_path=output_path,
|
|
365
|
+
redact_secrets_enabled=project_cfg.limits.redact_secrets,
|
|
366
|
+
ecosystem=collection.ecosystem,
|
|
367
|
+
guide=project_cfg.zip_guide,
|
|
365
368
|
)
|
|
366
369
|
|
|
367
370
|
|
|
@@ -370,20 +373,23 @@ def apply_zip(
|
|
|
370
373
|
*,
|
|
371
374
|
project_dir: str | Path | None = None,
|
|
372
375
|
manifest: str | Path | None = None,
|
|
376
|
+
include_risky: bool = False,
|
|
373
377
|
) -> ApplyResult:
|
|
374
378
|
"""
|
|
375
379
|
Apply an AI-returned ZIP back into *project_dir* (defaults to cwd).
|
|
376
380
|
|
|
377
|
-
|
|
378
|
-
to
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
381
|
+
Each file is matched to its place in the project (even when the zip is
|
|
382
|
+
wrapped, relative to a subfolder, or flattened), then compared with the
|
|
383
|
+
snapshot of what was sent and with the file on disk. New, modified and
|
|
384
|
+
cleanly merged files are written. Files that need a decision —
|
|
385
|
+
conflicts, partial files, ones you deleted or that weren't sent, and
|
|
386
|
+
ambiguous ones — are left alone unless *include_risky* is True, in which
|
|
387
|
+
case the AI's version wins. Nothing is removed — moved files keep their
|
|
388
|
+
old copy and requested deletions are skipped; use
|
|
389
|
+
``execute_plan(..., remove_moved=True, allow_delete=True)`` to opt in.
|
|
382
390
|
|
|
383
|
-
|
|
384
|
-
``
|
|
385
|
-
inspect risk before writing (see ``ApplyPlan.is_risky`` /
|
|
386
|
-
``.risky_entries``).
|
|
391
|
+
Doesn't prompt. To inspect first, use ``contextzip.applier.build_plan()``
|
|
392
|
+
and ``execute_plan(..., decisions=...)`` directly.
|
|
387
393
|
|
|
388
394
|
Parameters
|
|
389
395
|
----------
|
|
@@ -391,15 +397,20 @@ def apply_zip(
|
|
|
391
397
|
Path to the returned zip. If omitted, auto-detects from
|
|
392
398
|
``.contextzip/inbox/`` — exactly one zip must be present there.
|
|
393
399
|
project_dir:
|
|
394
|
-
Project root to apply into. Defaults to
|
|
400
|
+
Project root to apply into. Defaults to the project root found from the current folder.
|
|
395
401
|
manifest:
|
|
396
|
-
|
|
402
|
+
Snapshot (manifest) to compare against. By default the one that
|
|
403
|
+
best matches the zip is picked automatically.
|
|
404
|
+
include_risky:
|
|
405
|
+
Also write files that need a decision, with the AI's version
|
|
406
|
+
winning. Defaults to False.
|
|
397
407
|
|
|
398
408
|
Returns
|
|
399
409
|
-------
|
|
400
410
|
ApplyResult
|
|
401
|
-
``written`` (
|
|
402
|
-
``applied_zip_path`` (where the
|
|
411
|
+
``written`` (relative paths written), ``skipped`` (paths left alone)
|
|
412
|
+
and ``applied_zip_path`` (where the zip ended up — it stays in the
|
|
413
|
+
inbox while something still needs a decision).
|
|
403
414
|
|
|
404
415
|
Raises
|
|
405
416
|
------
|
|
@@ -431,9 +442,9 @@ def apply_zip(
|
|
|
431
442
|
except ApplyError as exc:
|
|
432
443
|
raise ZipNotFoundError(str(exc)) from exc
|
|
433
444
|
|
|
434
|
-
manifest_path = find_latest_manifest(pdir, manifest)
|
|
445
|
+
manifest_path = find_latest_manifest(pdir, manifest) if manifest is not None else None
|
|
435
446
|
plan = build_plan(resolved_zip, pdir, manifest_path)
|
|
436
|
-
return execute_plan(plan, pdir)
|
|
447
|
+
return execute_plan(plan, pdir, include_risky=include_risky)
|
|
437
448
|
|
|
438
449
|
|
|
439
450
|
def detect_ecosystem(
|
|
@@ -445,7 +456,7 @@ def detect_ecosystem(
|
|
|
445
456
|
Parameters
|
|
446
457
|
----------
|
|
447
458
|
path:
|
|
448
|
-
Directory to inspect. Defaults to
|
|
459
|
+
Directory to inspect. Defaults to the project root found from the current folder.
|
|
449
460
|
|
|
450
461
|
Returns
|
|
451
462
|
-------
|
|
@@ -498,8 +509,16 @@ def _config_kwargs(cfg, extra_exclude: list[str] | None = None) -> dict:
|
|
|
498
509
|
|
|
499
510
|
|
|
500
511
|
def _resolve_dir(path: str | Path | None) -> Path:
|
|
512
|
+
"""
|
|
513
|
+
An explicit *path* is used exactly as given. Without one, resolve the
|
|
514
|
+
project root from the current folder — the same root the CLI uses (see
|
|
515
|
+
root.find_project_root) — so a script run from a subfolder of a
|
|
516
|
+
monorepo still works on the whole project with root-relative paths.
|
|
517
|
+
"""
|
|
501
518
|
if path is None:
|
|
502
|
-
|
|
519
|
+
from contextzip.root import find_project_root
|
|
520
|
+
|
|
521
|
+
return find_project_root().root
|
|
503
522
|
return Path(path).resolve()
|
|
504
523
|
|
|
505
524
|
|