gwc-pybundle 1.4.5__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.
Potentially problematic release.
This version of gwc-pybundle might be problematic. Click here for more details.
- gwc_pybundle-1.4.5.dist-info/METADATA +876 -0
- gwc_pybundle-1.4.5.dist-info/RECORD +55 -0
- gwc_pybundle-1.4.5.dist-info/WHEEL +5 -0
- gwc_pybundle-1.4.5.dist-info/entry_points.txt +2 -0
- gwc_pybundle-1.4.5.dist-info/licenses/LICENSE.md +25 -0
- gwc_pybundle-1.4.5.dist-info/top_level.txt +1 -0
- pybundle/__init__.py +0 -0
- pybundle/__main__.py +4 -0
- pybundle/cli.py +365 -0
- pybundle/context.py +362 -0
- pybundle/doctor.py +148 -0
- pybundle/filters.py +178 -0
- pybundle/manifest.py +77 -0
- pybundle/packaging.py +45 -0
- pybundle/policy.py +132 -0
- pybundle/profiles.py +340 -0
- pybundle/roadmap_model.py +42 -0
- pybundle/roadmap_scan.py +295 -0
- pybundle/root_detect.py +14 -0
- pybundle/runner.py +163 -0
- pybundle/steps/__init__.py +26 -0
- pybundle/steps/bandit.py +72 -0
- pybundle/steps/base.py +20 -0
- pybundle/steps/compileall.py +76 -0
- pybundle/steps/context_expand.py +272 -0
- pybundle/steps/copy_pack.py +293 -0
- pybundle/steps/coverage.py +101 -0
- pybundle/steps/cprofile_step.py +155 -0
- pybundle/steps/dependency_sizes.py +120 -0
- pybundle/steps/duplication.py +94 -0
- pybundle/steps/error_refs.py +204 -0
- pybundle/steps/handoff_md.py +167 -0
- pybundle/steps/import_time.py +165 -0
- pybundle/steps/interrogate.py +84 -0
- pybundle/steps/license_scan.py +96 -0
- pybundle/steps/line_profiler.py +108 -0
- pybundle/steps/memory_profile.py +173 -0
- pybundle/steps/mutation_testing.py +136 -0
- pybundle/steps/mypy.py +60 -0
- pybundle/steps/pip_audit.py +45 -0
- pybundle/steps/pipdeptree.py +61 -0
- pybundle/steps/pylance.py +562 -0
- pybundle/steps/pytest.py +66 -0
- pybundle/steps/radon.py +121 -0
- pybundle/steps/repro_md.py +161 -0
- pybundle/steps/rg_scans.py +78 -0
- pybundle/steps/roadmap.py +153 -0
- pybundle/steps/ruff.py +111 -0
- pybundle/steps/shell.py +74 -0
- pybundle/steps/slow_tests.py +170 -0
- pybundle/steps/test_flakiness.py +172 -0
- pybundle/steps/tree.py +116 -0
- pybundle/steps/unused_deps.py +112 -0
- pybundle/steps/vulture.py +83 -0
- pybundle/tools.py +63 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
gwc_pybundle-1.4.5.dist-info/licenses/LICENSE.md,sha256=ZmD484KG9hysmSMFT824y7aIc8lhFBnjkN-3DJNjXCc,1108
|
|
2
|
+
pybundle/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pybundle/__main__.py,sha256=MHKZ_ae3fSLGTLUUMOx15fWdeOnJSHhq-zslRP5F5Lc,79
|
|
4
|
+
pybundle/cli.py,sha256=Fg8Jwv7PWNHnsdA46x2OVWNO7kz6SZlQ5xfU5XRTGfw,14544
|
|
5
|
+
pybundle/context.py,sha256=j2m3v1n8SCIs6vw69fqzUuSbT0iSf805KXolqpVccac,12013
|
|
6
|
+
pybundle/doctor.py,sha256=6tpEH5_UVKA8Wbi5kKkUoxXMjTGOrEZr3leBd4pU3Lw,4669
|
|
7
|
+
pybundle/filters.py,sha256=gKluTcoi6g0vt40m8sMbqmT-W2XBo_X7slW0CCAQI_o,3040
|
|
8
|
+
pybundle/manifest.py,sha256=LJB1JXg5kH6nKjgzif-sDWoaaqmIEkF3ctlbHqE0EHQ,2289
|
|
9
|
+
pybundle/packaging.py,sha256=CQ394kUmn6sudpy7ExGpmeanfRVEf_xxwrX_swaotFg,1407
|
|
10
|
+
pybundle/policy.py,sha256=PLk2Fs1hBxfn0jAWE3Jcj1jhlUnwfD5ivOFrUScI8Io,3929
|
|
11
|
+
pybundle/profiles.py,sha256=D_kOG0z477l6h6n5YifWZpuQIP6u4z2JrPKqn_CaHs0,10673
|
|
12
|
+
pybundle/roadmap_model.py,sha256=rcMwphd3uNiwzAVx_tGTE20GYYFRzyvbCoFcYGRS9aY,991
|
|
13
|
+
pybundle/roadmap_scan.py,sha256=ogS7j9pVIhqk_1u7eTBTUDjUGupZdU2RYWy1KcUPrgE,9613
|
|
14
|
+
pybundle/root_detect.py,sha256=d_HdA_usPxK1orc9cnNTY_fZrvdScQnI_f1XvvlHSFA,361
|
|
15
|
+
pybundle/runner.py,sha256=pw0T3QhbIl7cR7xyNcrm48IToE2b8NB8ZVFvtOEjyzw,5220
|
|
16
|
+
pybundle/tools.py,sha256=dLg6m7tW11QC6SoJUzoayYEI-lq_kKuOm2eN2uzqnQo,1926
|
|
17
|
+
pybundle/steps/__init__.py,sha256=3NL5A0Oj4MlTPW7LvdzesrLHMJG98nV-qrcaJuICE4o,394
|
|
18
|
+
pybundle/steps/bandit.py,sha256=4gT9JJerhT9W8O6lF_Vwk6iNjYvJqQcihVcF816NzS4,2439
|
|
19
|
+
pybundle/steps/base.py,sha256=9LZwPYlGAWfnIe5DRbfP89ZmMLhbmOm9SQ-vWmQANHA,356
|
|
20
|
+
pybundle/steps/compileall.py,sha256=uB_eFVq1FSSrdzZvx9hgs4IbNJnrXzPOCjyTfDw0gsc,2446
|
|
21
|
+
pybundle/steps/context_expand.py,sha256=pG1-59RqQFIdTlMj5CJ65b8Or9qNk8LgudntdT10m7M,7986
|
|
22
|
+
pybundle/steps/copy_pack.py,sha256=_I_hDBLiG9f9ETFzpG59lOFhe7nOasGPRvQb4CunBlE,9778
|
|
23
|
+
pybundle/steps/coverage.py,sha256=HIMuQH8PFPeoo_bPsIhn3U2UHzes5qiQIyJRqRnz0yI,3600
|
|
24
|
+
pybundle/steps/cprofile_step.py,sha256=z8KjKb2tydA0z5fnq6eZSJolpiWEld7NnlNQOSfj7AY,5755
|
|
25
|
+
pybundle/steps/dependency_sizes.py,sha256=oAzqUCmD0f7eOJfxE_S_ARgTszKEOJHSP5i-Z6aP0MI,5207
|
|
26
|
+
pybundle/steps/duplication.py,sha256=NmeNXXcEUYnlJWSCcilwb4K9NhTsGBvWJf4EoZZ3tBI,3304
|
|
27
|
+
pybundle/steps/error_refs.py,sha256=mq1DGXREQRudycyPb7H0obkHmv3HvO-QPPbf2zoZDu0,5640
|
|
28
|
+
pybundle/steps/handoff_md.py,sha256=TOv9IEqrss60ZKHlCx7XEzq_FgSZ38jr2sT3Nr7AwNM,6038
|
|
29
|
+
pybundle/steps/import_time.py,sha256=tEoybXsLYAYZ2iQjhsg6dN-VH37utsOThvHP-xwZ16k,6394
|
|
30
|
+
pybundle/steps/interrogate.py,sha256=la_3zy1gulqg382cNI3zFq4obH0OQyMZJQ9_FbCXIzU,2872
|
|
31
|
+
pybundle/steps/license_scan.py,sha256=rEigwhFC80btqAfecL2mFKyBxQkYP19FVESRXmaJaBY,3640
|
|
32
|
+
pybundle/steps/line_profiler.py,sha256=8RDjv8AE-xmnlPZzGXYEaOaLoJ2KlL8V8d_rrUJ7V94,4141
|
|
33
|
+
pybundle/steps/memory_profile.py,sha256=a8Iq3zdDstDrwWY4JmsIeIQV1JVKm5Pbx-D7OZw6AX4,5359
|
|
34
|
+
pybundle/steps/mutation_testing.py,sha256=MIHRrX6PrIfg9Aeal81Qe-G5pKMCPn0kGSbWuykVZUY,5417
|
|
35
|
+
pybundle/steps/mypy.py,sha256=QykLCT5XkQOJHHRxj4VAaPuCycoQOFymj-Xhz8cuc1c,1991
|
|
36
|
+
pybundle/steps/pip_audit.py,sha256=qNkHeyjzUFJ9SSKU4jTKOsUnWnNYAk_jWcmPlUckkAA,1582
|
|
37
|
+
pybundle/steps/pipdeptree.py,sha256=ZIBAC_qEBE_iUh8bX6ogYQVRCSB0blfiCmsGh4gFUbU,2150
|
|
38
|
+
pybundle/steps/pylance.py,sha256=PiIyyYm8gTVv-dihzRjvD4waZnz31qNOEAjYIlscvSY,15715
|
|
39
|
+
pybundle/steps/pytest.py,sha256=vfr2DI6aYCTit_j9QdV3WtKlOZtLMCCSm2E__3tsZt4,2177
|
|
40
|
+
pybundle/steps/radon.py,sha256=2gi_MQibkHhE1y9Bm3kpirlJc5SPQpHgI0yHieQ6VFo,4072
|
|
41
|
+
pybundle/steps/repro_md.py,sha256=EvoNVKaHFelfrv_jfBLpbkORTiDSjah3GgWXW3NvEXY,4856
|
|
42
|
+
pybundle/steps/rg_scans.py,sha256=hcHhzPAJ_MdPthRLcfIzxlkXoQJkDF2bIX2wU1By2gU,2511
|
|
43
|
+
pybundle/steps/roadmap.py,sha256=gFS2Mo9CR1A8wg6xNVP_FnJuT_dm8yWjgRJbtlyN2wk,5213
|
|
44
|
+
pybundle/steps/ruff.py,sha256=xrKSn5QBQ5sLBKW5TQCORANlvlh1eHdikOhxJP2gO54,3815
|
|
45
|
+
pybundle/steps/shell.py,sha256=vYGu9fZ5gDdZnKSfok95bOTIWu5HNvS23atsY5FwtJE,2529
|
|
46
|
+
pybundle/steps/slow_tests.py,sha256=3Gc1h4JB_41yjWthwVaIVMP3flm5xaCyQ94Nw86IB1o,7101
|
|
47
|
+
pybundle/steps/test_flakiness.py,sha256=5W8OGCJT1VsLu3jVcv55-CdtgdCYhk20jBCA9xvTpxc,7283
|
|
48
|
+
pybundle/steps/tree.py,sha256=tz5Z7clOEuKWMW6wjj2xtEu3pNjEto8dYgHYaoDdM0E,3674
|
|
49
|
+
pybundle/steps/unused_deps.py,sha256=L6n0fyu-p2lQkWjMKe8I5Ad5JMkou3muNU2qtqPOgOg,4870
|
|
50
|
+
pybundle/steps/vulture.py,sha256=-3964YRxqoQ-zRrAN6pS0HNlVeUzw-z75JZdbELavEw,2942
|
|
51
|
+
gwc_pybundle-1.4.5.dist-info/METADATA,sha256=iKF1bpXHiM9G1vvPyP2exuQcikA0cZjKR8W5glbF81g,25610
|
|
52
|
+
gwc_pybundle-1.4.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
53
|
+
gwc_pybundle-1.4.5.dist-info/entry_points.txt,sha256=xgXfIvyx9ZI6jd9MscFWNMzQrcX4scTVJ9L7WwksUHg,47
|
|
54
|
+
gwc_pybundle-1.4.5.dist-info/top_level.txt,sha256=N5x3QutDUtHQn3HwkFmH5PeM9uPY-E5wOKUJSE8PBKM,9
|
|
55
|
+
gwc_pybundle-1.4.5.dist-info/RECORD,,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Copyright © 2025 Jessica Brown
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
|
7
|
+
obtaining a copy of this software and associated documentation
|
|
8
|
+
files (the “Software”), to deal in the Software without
|
|
9
|
+
restriction, including without limitation the rights to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the
|
|
12
|
+
Software is furnished to do so, subject to the following
|
|
13
|
+
conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be
|
|
16
|
+
included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pybundle
|
pybundle/__init__.py
ADDED
|
File without changes
|
pybundle/__main__.py
ADDED
pybundle/cli.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import shlex
|
|
6
|
+
|
|
7
|
+
from .context import BundleContext, RunOptions
|
|
8
|
+
from .profiles import get_profile
|
|
9
|
+
from .root_detect import detect_project_root
|
|
10
|
+
from importlib.metadata import PackageNotFoundError, version as pkg_version
|
|
11
|
+
from .runner import run_profile
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_version() -> str:
|
|
15
|
+
# 1) Canonical for installed distributions (including editable)
|
|
16
|
+
try:
|
|
17
|
+
return pkg_version("gwc-pybundle")
|
|
18
|
+
except PackageNotFoundError:
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
# 2) Dev fallback: locate pyproject.toml by walking up from this file
|
|
22
|
+
try:
|
|
23
|
+
import tomllib # py3.11+
|
|
24
|
+
except Exception:
|
|
25
|
+
return "unknown"
|
|
26
|
+
|
|
27
|
+
here = Path(__file__).resolve()
|
|
28
|
+
for parent in [here.parent] + list(here.parents):
|
|
29
|
+
pp = parent / "pyproject.toml"
|
|
30
|
+
if pp.is_file():
|
|
31
|
+
try:
|
|
32
|
+
data = tomllib.loads(pp.read_text(encoding="utf-8"))
|
|
33
|
+
return data.get("project", {}).get("version", "unknown")
|
|
34
|
+
except Exception:
|
|
35
|
+
return "unknown"
|
|
36
|
+
|
|
37
|
+
return "unknown"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def add_common_args(sp: argparse.ArgumentParser) -> None:
|
|
41
|
+
sp.add_argument(
|
|
42
|
+
"--project-root",
|
|
43
|
+
type=Path,
|
|
44
|
+
default=None,
|
|
45
|
+
help="Explicit project root (skip auto-detect)",
|
|
46
|
+
)
|
|
47
|
+
sp.add_argument(
|
|
48
|
+
"--outdir",
|
|
49
|
+
type=Path,
|
|
50
|
+
default=None,
|
|
51
|
+
help="Output directory (default: <root>/artifacts)",
|
|
52
|
+
)
|
|
53
|
+
sp.add_argument("--name", default=None, help="Override archive name prefix")
|
|
54
|
+
sp.add_argument(
|
|
55
|
+
"--strict", action="store_true", help="Fail non-zero if any step fails"
|
|
56
|
+
)
|
|
57
|
+
sp.add_argument(
|
|
58
|
+
"--redact",
|
|
59
|
+
action=argparse.BooleanOptionalAction,
|
|
60
|
+
default=True,
|
|
61
|
+
help="Redact secrets in logs/text",
|
|
62
|
+
)
|
|
63
|
+
sp.add_argument(
|
|
64
|
+
"--json",
|
|
65
|
+
action="store_true",
|
|
66
|
+
help="Emit machine-readable JSON to stdout.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _resolve_profile_defaults(profile: str, o: RunOptions) -> RunOptions:
|
|
71
|
+
if profile == "ai":
|
|
72
|
+
# AI defaults: skip slow/flake-prone tools unless explicitly enabled
|
|
73
|
+
return RunOptions(
|
|
74
|
+
**{
|
|
75
|
+
**o.__dict__,
|
|
76
|
+
"no_ruff": o.no_ruff if o.no_ruff is not None else True,
|
|
77
|
+
"no_mypy": o.no_mypy if o.no_mypy is not None else True,
|
|
78
|
+
"no_pytest": o.no_pytest if o.no_pytest is not None else True,
|
|
79
|
+
"no_rg": o.no_rg if o.no_rg is not None else True,
|
|
80
|
+
"no_error_refs": o.no_error_refs
|
|
81
|
+
if o.no_error_refs is not None
|
|
82
|
+
else True,
|
|
83
|
+
"no_context": o.no_context if o.no_context is not None else True,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
return o
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def add_run_only_args(sp: argparse.ArgumentParser) -> None:
|
|
90
|
+
sp.add_argument(
|
|
91
|
+
"--format",
|
|
92
|
+
choices=["auto", "zip", "tar.gz"],
|
|
93
|
+
default="auto",
|
|
94
|
+
help="Archive format",
|
|
95
|
+
)
|
|
96
|
+
sp.add_argument(
|
|
97
|
+
"--clean-workdir",
|
|
98
|
+
action="store_true",
|
|
99
|
+
help="Delete expanded workdir after packaging",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def add_knobs(sp: argparse.ArgumentParser) -> None:
|
|
104
|
+
# selective skips
|
|
105
|
+
sp.add_argument("--ruff", dest="no_ruff", action="store_false", default=None)
|
|
106
|
+
sp.add_argument("--no-ruff", dest="no_ruff", action="store_true", default=None)
|
|
107
|
+
sp.add_argument("--mypy", dest="no_mypy", action="store_false", default=None)
|
|
108
|
+
sp.add_argument("--no-mypy", dest="no_mypy", action="store_true", default=None)
|
|
109
|
+
sp.add_argument("--pylance", dest="no_pylance", action="store_false", default=None)
|
|
110
|
+
sp.add_argument(
|
|
111
|
+
"--no-pylance", dest="no_pylance", action="store_true", default=None
|
|
112
|
+
)
|
|
113
|
+
sp.add_argument("--pytest", dest="no_pytest", action="store_false", default=None)
|
|
114
|
+
sp.add_argument("--no-pytest", dest="no_pytest", action="store_true", default=None)
|
|
115
|
+
sp.add_argument("--bandit", dest="no_bandit", action="store_false", default=None)
|
|
116
|
+
sp.add_argument("--no-bandit", dest="no_bandit", action="store_true", default=None)
|
|
117
|
+
sp.add_argument(
|
|
118
|
+
"--pip-audit", dest="no_pip_audit", action="store_false", default=None
|
|
119
|
+
)
|
|
120
|
+
sp.add_argument(
|
|
121
|
+
"--no-pip-audit", dest="no_pip_audit", action="store_true", default=None
|
|
122
|
+
)
|
|
123
|
+
sp.add_argument(
|
|
124
|
+
"--coverage", dest="no_coverage", action="store_false", default=None
|
|
125
|
+
)
|
|
126
|
+
sp.add_argument(
|
|
127
|
+
"--no-coverage", dest="no_coverage", action="store_true", default=None
|
|
128
|
+
)
|
|
129
|
+
sp.add_argument("--rg", dest="no_rg", action="store_false", default=None)
|
|
130
|
+
sp.add_argument("--no-rg", dest="no_rg", action="store_true", default=None)
|
|
131
|
+
sp.add_argument(
|
|
132
|
+
"--error-refs", dest="no_error_refs", action="store_false", default=None
|
|
133
|
+
)
|
|
134
|
+
sp.add_argument(
|
|
135
|
+
"--no-error-refs", dest="no_error_refs", action="store_true", default=None
|
|
136
|
+
)
|
|
137
|
+
sp.add_argument("--context", dest="no_context", action="store_false", default=None)
|
|
138
|
+
sp.add_argument(
|
|
139
|
+
"--no-context", dest="no_context", action="store_true", default=None
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# code quality tools (v1.3.0)
|
|
143
|
+
sp.add_argument("--vulture", dest="no_vulture", action="store_false", default=None)
|
|
144
|
+
sp.add_argument("--no-vulture", dest="no_vulture", action="store_true", default=None)
|
|
145
|
+
sp.add_argument("--radon", dest="no_radon", action="store_false", default=None)
|
|
146
|
+
sp.add_argument("--no-radon", dest="no_radon", action="store_true", default=None)
|
|
147
|
+
sp.add_argument("--interrogate", dest="no_interrogate", action="store_false", default=None)
|
|
148
|
+
sp.add_argument("--no-interrogate", dest="no_interrogate", action="store_true", default=None)
|
|
149
|
+
sp.add_argument("--duplication", dest="no_duplication", action="store_false", default=None)
|
|
150
|
+
sp.add_argument("--no-duplication", dest="no_duplication", action="store_true", default=None)
|
|
151
|
+
|
|
152
|
+
# dependency analysis tools (v1.3.1)
|
|
153
|
+
sp.add_argument("--pipdeptree", dest="no_pipdeptree", action="store_false", default=None)
|
|
154
|
+
sp.add_argument("--no-pipdeptree", dest="no_pipdeptree", action="store_true", default=None)
|
|
155
|
+
sp.add_argument("--unused-deps", dest="no_unused_deps", action="store_false", default=None)
|
|
156
|
+
sp.add_argument("--no-unused-deps", dest="no_unused_deps", action="store_true", default=None)
|
|
157
|
+
sp.add_argument("--license-scan", dest="no_license_scan", action="store_false", default=None)
|
|
158
|
+
sp.add_argument("--no-license-scan", dest="no_license_scan", action="store_true", default=None)
|
|
159
|
+
sp.add_argument("--dependency-sizes", dest="no_dependency_sizes", action="store_false", default=None)
|
|
160
|
+
sp.add_argument("--no-dependency-sizes", dest="no_dependency_sizes", action="store_true", default=None)
|
|
161
|
+
|
|
162
|
+
# performance profiling (v1.4.0)
|
|
163
|
+
sp.add_argument("--profile", dest="no_profile", action="store_false", default=None)
|
|
164
|
+
sp.add_argument("--no-profile", dest="no_profile", action="store_true", default=None)
|
|
165
|
+
sp.add_argument("--profile-entry-point", type=str, default=None,
|
|
166
|
+
help="Entry point for profiling (e.g., main.py or tests/)")
|
|
167
|
+
sp.add_argument("--profile-memory", action="store_true", default=False,
|
|
168
|
+
help="Enable memory profiling with tracemalloc")
|
|
169
|
+
sp.add_argument("--enable-line-profiler", action="store_true", default=False,
|
|
170
|
+
help="Enable line_profiler (requires @profile decorators)")
|
|
171
|
+
|
|
172
|
+
# test quality & coverage (v1.4.1)
|
|
173
|
+
sp.add_argument("--test-flakiness-runs", type=int, default=3,
|
|
174
|
+
help="Number of times to run tests for flakiness detection (default: 3)")
|
|
175
|
+
sp.add_argument("--slow-test-threshold", type=float, default=1.0,
|
|
176
|
+
help="Threshold in seconds for identifying slow tests (default: 1.0)")
|
|
177
|
+
sp.add_argument("--mutation", dest="enable_mutation_testing", action="store_true", default=False,
|
|
178
|
+
help="Enable mutation testing with mutmut (VERY SLOW!)")
|
|
179
|
+
sp.add_argument("--no-mutation", dest="enable_mutation_testing", action="store_false", default=False,
|
|
180
|
+
help="Disable mutation testing (default)")
|
|
181
|
+
|
|
182
|
+
# Security options
|
|
183
|
+
sp.add_argument(
|
|
184
|
+
"--strict-paths",
|
|
185
|
+
action="store_true",
|
|
186
|
+
help="Only use tools from trusted system directories (enhanced security)",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# targets / args
|
|
190
|
+
sp.add_argument("--ruff-target", default=".")
|
|
191
|
+
sp.add_argument("--mypy-target", default=".")
|
|
192
|
+
sp.add_argument(
|
|
193
|
+
"--pytest-args",
|
|
194
|
+
default="-q",
|
|
195
|
+
help='Pytest args as a single string, e.g. "--maxfail=1 -q"',
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# caps
|
|
199
|
+
sp.add_argument("--error-max-files", type=int, default=250)
|
|
200
|
+
sp.add_argument("--context-depth", type=int, default=2)
|
|
201
|
+
sp.add_argument("--context-max-files", type=int, default=600)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
205
|
+
p = argparse.ArgumentParser(
|
|
206
|
+
prog="pybundle", description="Build portable diagnostic bundles for projects."
|
|
207
|
+
)
|
|
208
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
209
|
+
sub.add_parser("version", help="Show version")
|
|
210
|
+
sub.add_parser("list-profiles", help="List available profiles")
|
|
211
|
+
|
|
212
|
+
runp = sub.add_parser("run", help="Run a profile and build an archive")
|
|
213
|
+
runp.add_argument("profile", choices=["analysis", "debug", "backup", "ai"])
|
|
214
|
+
add_common_args(runp)
|
|
215
|
+
add_run_only_args(runp)
|
|
216
|
+
add_knobs(runp)
|
|
217
|
+
|
|
218
|
+
docp = sub.add_parser("doctor", help="Show tool availability and what would run")
|
|
219
|
+
docp.add_argument(
|
|
220
|
+
"profile",
|
|
221
|
+
choices=["analysis", "debug", "backup", "ai"],
|
|
222
|
+
nargs="?",
|
|
223
|
+
default="analysis",
|
|
224
|
+
)
|
|
225
|
+
add_common_args(docp)
|
|
226
|
+
add_knobs(docp)
|
|
227
|
+
|
|
228
|
+
return p
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def _build_options(args) -> RunOptions:
|
|
232
|
+
pytest_args = (
|
|
233
|
+
shlex.split(args.pytest_args) if getattr(args, "pytest_args", None) else ["-q"]
|
|
234
|
+
)
|
|
235
|
+
return RunOptions(
|
|
236
|
+
no_ruff=getattr(args, "no_ruff", None),
|
|
237
|
+
no_mypy=getattr(args, "no_mypy", None),
|
|
238
|
+
no_pylance=getattr(args, "no_pylance", None),
|
|
239
|
+
no_pytest=getattr(args, "no_pytest", None),
|
|
240
|
+
no_bandit=getattr(args, "no_bandit", None),
|
|
241
|
+
no_pip_audit=getattr(args, "no_pip_audit", None),
|
|
242
|
+
no_coverage=getattr(args, "no_coverage", None),
|
|
243
|
+
no_rg=getattr(args, "no_rg", None),
|
|
244
|
+
no_error_refs=getattr(args, "no_error_refs", None),
|
|
245
|
+
no_context=getattr(args, "no_context", None),
|
|
246
|
+
no_compileall=getattr(args, "no_compileall", None),
|
|
247
|
+
no_vulture=getattr(args, "no_vulture", None),
|
|
248
|
+
no_radon=getattr(args, "no_radon", None),
|
|
249
|
+
no_interrogate=getattr(args, "no_interrogate", None),
|
|
250
|
+
no_duplication=getattr(args, "no_duplication", None),
|
|
251
|
+
no_pipdeptree=getattr(args, "no_pipdeptree", None),
|
|
252
|
+
no_unused_deps=getattr(args, "no_unused_deps", None),
|
|
253
|
+
no_license_scan=getattr(args, "no_license_scan", None),
|
|
254
|
+
no_dependency_sizes=getattr(args, "no_dependency_sizes", None),
|
|
255
|
+
no_profile=getattr(args, "no_profile", None),
|
|
256
|
+
profile_entry_point=getattr(args, "profile_entry_point", None),
|
|
257
|
+
profile_memory=getattr(args, "profile_memory", False),
|
|
258
|
+
enable_line_profiler=getattr(args, "enable_line_profiler", False),
|
|
259
|
+
test_flakiness_runs=getattr(args, "test_flakiness_runs", 3),
|
|
260
|
+
slow_test_threshold=getattr(args, "slow_test_threshold", 1.0),
|
|
261
|
+
enable_mutation_testing=getattr(args, "enable_mutation_testing", False),
|
|
262
|
+
strict_paths=getattr(args, "strict_paths", False),
|
|
263
|
+
ruff_target=getattr(args, "ruff_target", "."),
|
|
264
|
+
mypy_target=getattr(args, "mypy_target", "."),
|
|
265
|
+
pytest_args=pytest_args,
|
|
266
|
+
error_max_files=getattr(args, "error_max_files", 250),
|
|
267
|
+
context_depth=getattr(args, "context_depth", 2),
|
|
268
|
+
context_max_files=getattr(args, "context_max_files", 600),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def main(argv: list[str] | None = None) -> int:
|
|
273
|
+
args = build_parser().parse_args(argv)
|
|
274
|
+
|
|
275
|
+
if args.cmd == "version":
|
|
276
|
+
print(f"pybundle {get_version()}")
|
|
277
|
+
return 0
|
|
278
|
+
|
|
279
|
+
if args.cmd == "list-profiles":
|
|
280
|
+
print("ai - AI-friendly context bundle (fast, low-flake defaults)")
|
|
281
|
+
print("backup - portable snapshot (scaffold)")
|
|
282
|
+
print("analysis - neutral diagnostic bundle (humans, tools, assistants)")
|
|
283
|
+
print("debug - deeper diagnostics for developers")
|
|
284
|
+
return 0
|
|
285
|
+
|
|
286
|
+
# run + doctor need a root
|
|
287
|
+
root = args.project_root or detect_project_root(Path.cwd())
|
|
288
|
+
if root is None:
|
|
289
|
+
print("❌ Could not detect project root. Use --project-root PATH.")
|
|
290
|
+
return 20
|
|
291
|
+
|
|
292
|
+
outdir = args.outdir or (root / "artifacts")
|
|
293
|
+
|
|
294
|
+
options = _resolve_profile_defaults(args.profile, _build_options(args))
|
|
295
|
+
profile = get_profile(args.profile, options)
|
|
296
|
+
|
|
297
|
+
if args.cmd == "doctor":
|
|
298
|
+
ctx = BundleContext.create(
|
|
299
|
+
root=root,
|
|
300
|
+
outdir=outdir,
|
|
301
|
+
profile_name=args.profile,
|
|
302
|
+
archive_format="auto",
|
|
303
|
+
name_prefix=args.name,
|
|
304
|
+
strict=args.strict,
|
|
305
|
+
redact=args.redact,
|
|
306
|
+
json_mode=args.json,
|
|
307
|
+
keep_workdir=True,
|
|
308
|
+
options=options,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if args.json:
|
|
312
|
+
ctx.emit_json(ctx.doctor_report(profile))
|
|
313
|
+
else:
|
|
314
|
+
ctx.print_doctor(profile)
|
|
315
|
+
return 0
|
|
316
|
+
|
|
317
|
+
# cmd == run
|
|
318
|
+
keep_workdir = not args.clean_workdir
|
|
319
|
+
|
|
320
|
+
ctx = BundleContext.create(
|
|
321
|
+
root=root,
|
|
322
|
+
outdir=outdir,
|
|
323
|
+
profile_name=args.profile,
|
|
324
|
+
archive_format=args.format,
|
|
325
|
+
name_prefix=args.name,
|
|
326
|
+
strict=args.strict,
|
|
327
|
+
redact=args.redact,
|
|
328
|
+
json_mode=args.json,
|
|
329
|
+
keep_workdir=keep_workdir,
|
|
330
|
+
options=options,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
rc = run_profile(ctx, profile)
|
|
334
|
+
|
|
335
|
+
if args.json:
|
|
336
|
+
copied = None
|
|
337
|
+
excluded = None
|
|
338
|
+
|
|
339
|
+
mf = ctx.metadir / "50_copy_manifest.txt"
|
|
340
|
+
if mf.exists():
|
|
341
|
+
data: dict[str, str] = {}
|
|
342
|
+
for line in mf.read_text(encoding="utf-8").splitlines():
|
|
343
|
+
if "=" in line:
|
|
344
|
+
k, v = line.split("=", 1)
|
|
345
|
+
data[k.strip()] = v.strip()
|
|
346
|
+
|
|
347
|
+
copied = int(data.get("copied_files", "0"))
|
|
348
|
+
excluded = int(data.get("excluded_files", "0"))
|
|
349
|
+
|
|
350
|
+
payload = {
|
|
351
|
+
"status": "ok" if rc == 0 else "fail",
|
|
352
|
+
"command": "run",
|
|
353
|
+
"profile": profile.name,
|
|
354
|
+
"files_included": copied if copied is not None else 0,
|
|
355
|
+
"files_excluded": excluded if excluded is not None else 0,
|
|
356
|
+
"duration_ms": ctx.duration_ms or 0,
|
|
357
|
+
"bundle_path": str(ctx.archive_path) if ctx.archive_path else None,
|
|
358
|
+
}
|
|
359
|
+
ctx.emit_json(payload)
|
|
360
|
+
|
|
361
|
+
return rc
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
if __name__ == "__main__":
|
|
365
|
+
raise SystemExit(main())
|