codecrate 0.1.1__tar.gz → 0.1.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.
Potentially problematic release.
This version of codecrate might be problematic. Click here for more details.
- {codecrate-0.1.1/codecrate.egg-info → codecrate-0.1.2}/PKG-INFO +1 -1
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/_version.py +3 -3
- codecrate-0.1.2/codecrate/cli.py +433 -0
- {codecrate-0.1.1 → codecrate-0.1.2/codecrate.egg-info}/PKG-INFO +1 -1
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate.egg-info/SOURCES.txt +1 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/cli.rst +15 -6
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/quickstart.rst +6 -0
- codecrate-0.1.2/tests/test_cli_pack_multi.py +39 -0
- codecrate-0.1.1/codecrate/cli.py +0 -251
- {codecrate-0.1.1 → codecrate-0.1.2}/.github/pytest.ini +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.github/workflows/codecov.yml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.github/workflows/pre-commit.yml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.github/workflows/python-publish.yml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.github/workflows/tests.yml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.gitignore +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.pre-commit-config.yaml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.readthedocs.yaml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/.ruff.toml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/AGENTS.md +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/LICENSE +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/README.md +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/__init__.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/config.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/diffgen.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/discover.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/ids.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/manifest.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/markdown.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/mdparse.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/model.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/packer.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/parse.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/stubber.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/token_budget.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/udiff.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/unpacker.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate/validate.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate.egg-info/dependency_links.txt +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate.egg-info/entry_points.txt +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate.egg-info/requires.txt +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate.egg-info/top_level.txt +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/codecrate.toml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/api.rst +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/conf.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/format.rst +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/index.rst +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/make.bat +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/make.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/docs/requirements.txt +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/pyproject.toml +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/requirements-test.txt +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/setup.cfg +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/setup.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/__init__.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_config.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_discover.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_ids.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_markdown_line_numbers.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_model.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_pack_unpack_roundtrip.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_parse.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_patch_apply_roundtrip.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_smoke.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_split_codecrate_pack.py +0 -0
- {codecrate-0.1.1 → codecrate-0.1.2}/tests/test_token_budget.py +0 -0
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 2)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g3ede7bcd7'
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .config import Config, load_config
|
|
8
|
+
from .diffgen import generate_patch_markdown
|
|
9
|
+
from .discover import discover_files
|
|
10
|
+
from .markdown import render_markdown
|
|
11
|
+
from .packer import pack_repo
|
|
12
|
+
from .token_budget import split_by_max_chars
|
|
13
|
+
from .udiff import apply_file_diffs, parse_unified_diff
|
|
14
|
+
from .unpacker import unpack_to_dir
|
|
15
|
+
from .validate import validate_pack_markdown
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
19
|
+
p = argparse.ArgumentParser(
|
|
20
|
+
prog="codecrate",
|
|
21
|
+
description="Pack/unpack/patch/apply for repositories (Python + text files).",
|
|
22
|
+
)
|
|
23
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
24
|
+
|
|
25
|
+
# pack
|
|
26
|
+
pack = sub.add_parser(
|
|
27
|
+
"pack", help="Pack one or more repositories/directories into Markdown."
|
|
28
|
+
)
|
|
29
|
+
pack.add_argument(
|
|
30
|
+
"root",
|
|
31
|
+
type=Path,
|
|
32
|
+
nargs="?",
|
|
33
|
+
help="Root directory to scan (omit when using --repo)",
|
|
34
|
+
)
|
|
35
|
+
pack.add_argument(
|
|
36
|
+
"--repo",
|
|
37
|
+
action="append",
|
|
38
|
+
default=None,
|
|
39
|
+
type=Path,
|
|
40
|
+
help="Additional repo root to pack (repeatable; use instead of ROOT)",
|
|
41
|
+
)
|
|
42
|
+
pack.add_argument(
|
|
43
|
+
"-o",
|
|
44
|
+
"--output",
|
|
45
|
+
type=Path,
|
|
46
|
+
default=None,
|
|
47
|
+
help="Output markdown path (default: config 'output' or context.md)",
|
|
48
|
+
)
|
|
49
|
+
pack.add_argument(
|
|
50
|
+
"--dedupe", action="store_true", help="Deduplicate identical function bodies"
|
|
51
|
+
)
|
|
52
|
+
pack.add_argument(
|
|
53
|
+
"--layout",
|
|
54
|
+
choices=["auto", "stubs", "full"],
|
|
55
|
+
default=None,
|
|
56
|
+
help="Output layout: auto|stubs|full (default: auto via config)",
|
|
57
|
+
)
|
|
58
|
+
pack.add_argument(
|
|
59
|
+
"--keep-docstrings",
|
|
60
|
+
action=argparse.BooleanOptionalAction,
|
|
61
|
+
default=None,
|
|
62
|
+
help="Keep docstrings in stubbed file view (default: true via config)",
|
|
63
|
+
)
|
|
64
|
+
pack.add_argument(
|
|
65
|
+
"--respect-gitignore",
|
|
66
|
+
action=argparse.BooleanOptionalAction,
|
|
67
|
+
default=None,
|
|
68
|
+
help="Respect .gitignore (default: true via config)",
|
|
69
|
+
)
|
|
70
|
+
pack.add_argument(
|
|
71
|
+
"--manifest",
|
|
72
|
+
action=argparse.BooleanOptionalAction,
|
|
73
|
+
default=None,
|
|
74
|
+
help="Include Manifest section (default: true via config)",
|
|
75
|
+
)
|
|
76
|
+
pack.add_argument(
|
|
77
|
+
"--include", action="append", default=None, help="Include glob (repeatable)"
|
|
78
|
+
)
|
|
79
|
+
pack.add_argument(
|
|
80
|
+
"--exclude", action="append", default=None, help="Exclude glob (repeatable)"
|
|
81
|
+
)
|
|
82
|
+
pack.add_argument(
|
|
83
|
+
"--split-max-chars",
|
|
84
|
+
type=int,
|
|
85
|
+
default=None,
|
|
86
|
+
help="Split output into .partN.md files",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
# unpack
|
|
90
|
+
unpack = sub.add_parser(
|
|
91
|
+
"unpack", help="Reconstruct files from a packed context Markdown."
|
|
92
|
+
)
|
|
93
|
+
unpack.add_argument("markdown", type=Path, help="Packed Markdown file from `pack`")
|
|
94
|
+
unpack.add_argument(
|
|
95
|
+
"-o",
|
|
96
|
+
"--out-dir",
|
|
97
|
+
type=Path,
|
|
98
|
+
required=True,
|
|
99
|
+
help="Output directory for reconstructed files",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# patch
|
|
103
|
+
patch = sub.add_parser(
|
|
104
|
+
"patch",
|
|
105
|
+
help="Generate a diff-only patch Markdown from old pack + current repo.",
|
|
106
|
+
)
|
|
107
|
+
patch.add_argument(
|
|
108
|
+
"old_markdown", type=Path, help="Older packed Markdown (baseline)"
|
|
109
|
+
)
|
|
110
|
+
patch.add_argument("root", type=Path, help="Current repo root to compare against")
|
|
111
|
+
patch.add_argument(
|
|
112
|
+
"-o",
|
|
113
|
+
"--output",
|
|
114
|
+
type=Path,
|
|
115
|
+
default=Path("patch.md"),
|
|
116
|
+
help="Output patch markdown",
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# apply
|
|
120
|
+
apply = sub.add_parser("apply", help="Apply a diff-only patch Markdown to a repo.")
|
|
121
|
+
apply.add_argument(
|
|
122
|
+
"patch_markdown", type=Path, help="Patch Markdown containing ```diff blocks"
|
|
123
|
+
)
|
|
124
|
+
apply.add_argument("root", type=Path, help="Repo root to apply patch to")
|
|
125
|
+
# validate-pack
|
|
126
|
+
vpack = sub.add_parser(
|
|
127
|
+
"validate-pack",
|
|
128
|
+
help="Validate a packed context Markdown (sha/markers/canonical consistency).",
|
|
129
|
+
)
|
|
130
|
+
vpack.add_argument("markdown", type=Path, help="Packed Markdown to validate")
|
|
131
|
+
vpack.add_argument(
|
|
132
|
+
"--root",
|
|
133
|
+
type=Path,
|
|
134
|
+
default=None,
|
|
135
|
+
help="Optional repo root to compare reconstructed files against",
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
return p
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True)
|
|
142
|
+
class PackOptions:
|
|
143
|
+
include: list[str] | None
|
|
144
|
+
exclude: list[str] | None
|
|
145
|
+
keep_docstrings: bool
|
|
146
|
+
include_manifest: bool
|
|
147
|
+
respect_gitignore: bool
|
|
148
|
+
dedupe: bool
|
|
149
|
+
split_max_chars: int
|
|
150
|
+
layout: str
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass(frozen=True)
|
|
154
|
+
class PackRun:
|
|
155
|
+
root: Path
|
|
156
|
+
label: str
|
|
157
|
+
slug: str
|
|
158
|
+
markdown: str
|
|
159
|
+
options: PackOptions
|
|
160
|
+
default_output: Path
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _resolve_pack_options(cfg: Config, args: argparse.Namespace) -> PackOptions:
|
|
164
|
+
include = args.include if args.include is not None else cfg.include
|
|
165
|
+
exclude = args.exclude if args.exclude is not None else cfg.exclude
|
|
166
|
+
keep_docstrings = (
|
|
167
|
+
cfg.keep_docstrings
|
|
168
|
+
if args.keep_docstrings is None
|
|
169
|
+
else bool(args.keep_docstrings)
|
|
170
|
+
)
|
|
171
|
+
include_manifest = cfg.manifest if args.manifest is None else bool(args.manifest)
|
|
172
|
+
respect_gitignore = (
|
|
173
|
+
cfg.respect_gitignore
|
|
174
|
+
if args.respect_gitignore is None
|
|
175
|
+
else bool(args.respect_gitignore)
|
|
176
|
+
)
|
|
177
|
+
dedupe = bool(args.dedupe) or bool(cfg.dedupe)
|
|
178
|
+
split_max_chars = (
|
|
179
|
+
cfg.split_max_chars
|
|
180
|
+
if args.split_max_chars is None
|
|
181
|
+
else int(args.split_max_chars or 0)
|
|
182
|
+
)
|
|
183
|
+
layout = (
|
|
184
|
+
str(args.layout).strip().lower()
|
|
185
|
+
if args.layout is not None
|
|
186
|
+
else str(getattr(cfg, "layout", "auto")).strip().lower()
|
|
187
|
+
)
|
|
188
|
+
return PackOptions(
|
|
189
|
+
include=include,
|
|
190
|
+
exclude=exclude,
|
|
191
|
+
keep_docstrings=keep_docstrings,
|
|
192
|
+
include_manifest=include_manifest,
|
|
193
|
+
respect_gitignore=respect_gitignore,
|
|
194
|
+
dedupe=dedupe,
|
|
195
|
+
split_max_chars=split_max_chars,
|
|
196
|
+
layout=layout,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _resolve_output_path(cfg: Config, args: argparse.Namespace, root: Path) -> Path:
|
|
201
|
+
if args.output is not None:
|
|
202
|
+
return args.output
|
|
203
|
+
out_path = Path(getattr(cfg, "output", "context.md"))
|
|
204
|
+
if not out_path.is_absolute():
|
|
205
|
+
out_path = root / out_path
|
|
206
|
+
return out_path
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _default_repo_label(root: Path) -> str:
|
|
210
|
+
cwd = Path.cwd().resolve()
|
|
211
|
+
resolved = root.resolve()
|
|
212
|
+
try:
|
|
213
|
+
rel = resolved.relative_to(cwd).as_posix()
|
|
214
|
+
return rel or resolved.name or resolved.as_posix()
|
|
215
|
+
except ValueError:
|
|
216
|
+
return root.name or resolved.name or resolved.as_posix()
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _unique_label(root: Path, used: set[str]) -> str:
|
|
220
|
+
base = _default_repo_label(root)
|
|
221
|
+
label = base
|
|
222
|
+
idx = 2
|
|
223
|
+
while label in used:
|
|
224
|
+
label = f"{base}-{idx}"
|
|
225
|
+
idx += 1
|
|
226
|
+
used.add(label)
|
|
227
|
+
return label
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _slugify(label: str) -> str:
|
|
231
|
+
safe: list[str] = []
|
|
232
|
+
for ch in label:
|
|
233
|
+
if ch.isalnum() or ch in {"-", "_"}:
|
|
234
|
+
safe.append(ch)
|
|
235
|
+
else:
|
|
236
|
+
safe.append("-")
|
|
237
|
+
slug = "".join(safe).strip("-")
|
|
238
|
+
while "--" in slug:
|
|
239
|
+
slug = slug.replace("--", "-")
|
|
240
|
+
return slug or "repo"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _unique_slug(label: str, used: set[str]) -> str:
|
|
244
|
+
base = _slugify(label)
|
|
245
|
+
slug = base
|
|
246
|
+
idx = 2
|
|
247
|
+
while slug in used:
|
|
248
|
+
slug = f"{base}-{idx}"
|
|
249
|
+
idx += 1
|
|
250
|
+
used.add(slug)
|
|
251
|
+
return slug
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _prefix_repo_header(text: str, label: str) -> str:
|
|
255
|
+
header = f"# Repository: {label}\n\n"
|
|
256
|
+
if text.startswith(header):
|
|
257
|
+
return text
|
|
258
|
+
return header + text
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _combine_pack_markdown(packs: list[PackRun]) -> str:
|
|
262
|
+
out: list[str] = []
|
|
263
|
+
for i, pack in enumerate(packs):
|
|
264
|
+
if i:
|
|
265
|
+
out.append("\n\n")
|
|
266
|
+
out.append(_prefix_repo_header(pack.markdown.rstrip() + "\n", pack.label))
|
|
267
|
+
return "".join(out).rstrip() + "\n"
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _extract_diff_blocks(md_text: str) -> str:
|
|
271
|
+
"""
|
|
272
|
+
Extract only diff fences from markdown and concatenate to a unified diff string.
|
|
273
|
+
"""
|
|
274
|
+
lines = md_text.splitlines()
|
|
275
|
+
out: list[str] = []
|
|
276
|
+
i = 0
|
|
277
|
+
while i < len(lines):
|
|
278
|
+
if lines[i].strip() == "```diff":
|
|
279
|
+
i += 1
|
|
280
|
+
while i < len(lines) and lines[i].strip() != "```":
|
|
281
|
+
out.append(lines[i])
|
|
282
|
+
i += 1
|
|
283
|
+
i += 1
|
|
284
|
+
return "\n".join(out) + "\n"
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def main(argv: list[str] | None = None) -> None: # noqa: C901
|
|
288
|
+
parser = build_parser()
|
|
289
|
+
args = parser.parse_args(argv)
|
|
290
|
+
|
|
291
|
+
if args.cmd == "pack":
|
|
292
|
+
if args.repo:
|
|
293
|
+
if args.root is not None:
|
|
294
|
+
parser.error(
|
|
295
|
+
"pack: specify either ROOT or --repo (repeatable), not both"
|
|
296
|
+
)
|
|
297
|
+
roots = [r.resolve() for r in args.repo]
|
|
298
|
+
else:
|
|
299
|
+
if args.root is None:
|
|
300
|
+
parser.error("pack: ROOT is required when --repo is not used")
|
|
301
|
+
roots = [args.root.resolve()]
|
|
302
|
+
|
|
303
|
+
used_labels: set[str] = set()
|
|
304
|
+
used_slugs: set[str] = set()
|
|
305
|
+
pack_runs: list[PackRun] = []
|
|
306
|
+
|
|
307
|
+
for root in roots:
|
|
308
|
+
cfg = load_config(root)
|
|
309
|
+
options = _resolve_pack_options(cfg, args)
|
|
310
|
+
label = _unique_label(root, used_labels)
|
|
311
|
+
slug = _unique_slug(label, used_slugs)
|
|
312
|
+
|
|
313
|
+
disc = discover_files(
|
|
314
|
+
root=root,
|
|
315
|
+
include=options.include,
|
|
316
|
+
exclude=options.exclude,
|
|
317
|
+
respect_gitignore=options.respect_gitignore,
|
|
318
|
+
)
|
|
319
|
+
pack, canonical = pack_repo(
|
|
320
|
+
disc.root,
|
|
321
|
+
disc.files,
|
|
322
|
+
keep_docstrings=options.keep_docstrings,
|
|
323
|
+
dedupe=options.dedupe,
|
|
324
|
+
)
|
|
325
|
+
md = render_markdown(
|
|
326
|
+
pack,
|
|
327
|
+
canonical,
|
|
328
|
+
layout=options.layout,
|
|
329
|
+
include_manifest=options.include_manifest,
|
|
330
|
+
)
|
|
331
|
+
default_output = _resolve_output_path(cfg, args, root)
|
|
332
|
+
pack_runs.append(
|
|
333
|
+
PackRun(
|
|
334
|
+
root=root,
|
|
335
|
+
label=label,
|
|
336
|
+
slug=slug,
|
|
337
|
+
markdown=md,
|
|
338
|
+
options=options,
|
|
339
|
+
default_output=default_output,
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
out_path = (
|
|
344
|
+
args.output if args.output is not None else pack_runs[0].default_output
|
|
345
|
+
)
|
|
346
|
+
if len(pack_runs) == 1:
|
|
347
|
+
md = pack_runs[0].markdown
|
|
348
|
+
else:
|
|
349
|
+
md = _combine_pack_markdown(pack_runs)
|
|
350
|
+
|
|
351
|
+
# Always write the canonical, unsplit pack
|
|
352
|
+
# for machine parsing (unpack/validate).
|
|
353
|
+
out_path.write_text(md, encoding="utf-8")
|
|
354
|
+
|
|
355
|
+
extra_count = 0
|
|
356
|
+
if len(pack_runs) == 1:
|
|
357
|
+
split_max_chars = pack_runs[0].options.split_max_chars
|
|
358
|
+
parts = split_by_max_chars(md, out_path, split_max_chars)
|
|
359
|
+
extra = [p for p in parts if p.path != out_path]
|
|
360
|
+
for part in extra:
|
|
361
|
+
part.path.write_text(part.content, encoding="utf-8")
|
|
362
|
+
extra_count += len(extra)
|
|
363
|
+
else:
|
|
364
|
+
for pack in pack_runs:
|
|
365
|
+
if pack.options.split_max_chars <= 0:
|
|
366
|
+
continue
|
|
367
|
+
repo_base = out_path.with_name(
|
|
368
|
+
f"{out_path.stem}.{pack.slug}{out_path.suffix}"
|
|
369
|
+
)
|
|
370
|
+
parts = split_by_max_chars(
|
|
371
|
+
pack.markdown, repo_base, pack.options.split_max_chars
|
|
372
|
+
)
|
|
373
|
+
extra = [p for p in parts if p.path != repo_base]
|
|
374
|
+
for part in extra:
|
|
375
|
+
content = _prefix_repo_header(part.content, pack.label)
|
|
376
|
+
part.path.write_text(content, encoding="utf-8")
|
|
377
|
+
extra_count += len(extra)
|
|
378
|
+
|
|
379
|
+
if extra_count:
|
|
380
|
+
if len(pack_runs) == 1:
|
|
381
|
+
print(f"Wrote {out_path} and {extra_count} split part file(s).")
|
|
382
|
+
else:
|
|
383
|
+
print(
|
|
384
|
+
f"Wrote {out_path} and {extra_count} split part file(s) for "
|
|
385
|
+
f"{len(pack_runs)} repos."
|
|
386
|
+
)
|
|
387
|
+
else:
|
|
388
|
+
if len(pack_runs) == 1:
|
|
389
|
+
print(f"Wrote {out_path}.")
|
|
390
|
+
else:
|
|
391
|
+
print(f"Wrote {out_path} for {len(pack_runs)} repos.")
|
|
392
|
+
elif args.cmd == "unpack":
|
|
393
|
+
md_text = args.markdown.read_text(encoding="utf-8", errors="replace")
|
|
394
|
+
unpack_to_dir(md_text, args.out_dir)
|
|
395
|
+
print(f"Unpacked into {args.out_dir}")
|
|
396
|
+
|
|
397
|
+
elif args.cmd == "patch":
|
|
398
|
+
old_md = args.old_markdown.read_text(encoding="utf-8", errors="replace")
|
|
399
|
+
cfg = load_config(args.root)
|
|
400
|
+
patch_md = generate_patch_markdown(
|
|
401
|
+
old_md,
|
|
402
|
+
args.root,
|
|
403
|
+
include=cfg.include,
|
|
404
|
+
exclude=cfg.exclude,
|
|
405
|
+
respect_gitignore=cfg.respect_gitignore,
|
|
406
|
+
)
|
|
407
|
+
args.output.write_text(patch_md, encoding="utf-8")
|
|
408
|
+
print(f"Wrote {args.output}")
|
|
409
|
+
|
|
410
|
+
elif args.cmd == "validate-pack":
|
|
411
|
+
md_text = args.markdown.read_text(encoding="utf-8", errors="replace")
|
|
412
|
+
report = validate_pack_markdown(md_text, root=args.root)
|
|
413
|
+
if report.warnings:
|
|
414
|
+
print("Warnings:")
|
|
415
|
+
for w in report.warnings:
|
|
416
|
+
print(f"- {w}")
|
|
417
|
+
if report.errors:
|
|
418
|
+
print("Errors:")
|
|
419
|
+
for e in report.errors:
|
|
420
|
+
print(f"- {e}")
|
|
421
|
+
raise SystemExit(1)
|
|
422
|
+
print("OK: pack is internally consistent.")
|
|
423
|
+
|
|
424
|
+
elif args.cmd == "apply":
|
|
425
|
+
md_text = args.patch_markdown.read_text(encoding="utf-8", errors="replace")
|
|
426
|
+
diff_text = _extract_diff_blocks(md_text)
|
|
427
|
+
diffs = parse_unified_diff(diff_text)
|
|
428
|
+
changed = apply_file_diffs(diffs, args.root)
|
|
429
|
+
print(f"Applied patch to {len(changed)} file(s).")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
if __name__ == "__main__":
|
|
433
|
+
main()
|
|
@@ -26,7 +26,7 @@ Overview
|
|
|
26
26
|
|
|
27
27
|
.. code-block:: console
|
|
28
28
|
|
|
29
|
-
codecrate pack ROOT [options]
|
|
29
|
+
codecrate pack [ROOT] [--repo REPO ...] [options]
|
|
30
30
|
codecrate unpack PACK.md -o OUT_DIR
|
|
31
31
|
codecrate patch OLD_PACK.md ROOT [-o patch.md]
|
|
32
32
|
codecrate apply PATCH.md ROOT
|
|
@@ -36,11 +36,14 @@ Overview
|
|
|
36
36
|
pack
|
|
37
37
|
----
|
|
38
38
|
|
|
39
|
-
Create a packed Markdown context file from
|
|
39
|
+
Create a packed Markdown context file from one or more repositories.
|
|
40
40
|
|
|
41
41
|
.. code-block:: console
|
|
42
42
|
|
|
43
43
|
codecrate pack . -o context.md
|
|
44
|
+
codecrate pack --repo /path/to/repo1 --repo /path/to/repo2 -o multi.md
|
|
45
|
+
|
|
46
|
+
When using ``--repo``, omit the positional ``ROOT``. Specifying both is an error.
|
|
44
47
|
|
|
45
48
|
Useful flags:
|
|
46
49
|
|
|
@@ -52,7 +55,8 @@ Useful flags:
|
|
|
52
55
|
* ``--include GLOB`` (repeatable): include patterns
|
|
53
56
|
* ``--exclude GLOB`` (repeatable): exclude patterns
|
|
54
57
|
* ``--split-max-chars N``: additionally emit ``.partN.md`` files for LLMs (the
|
|
55
|
-
main output stays
|
|
58
|
+
main output stays unsplit). For multi-repo packs, parts are named
|
|
59
|
+
``output.<repo>.partN.md``
|
|
56
60
|
* ``-o/--output PATH``: output path (defaults to config ``output`` or ``context.md``)
|
|
57
61
|
|
|
58
62
|
|
|
@@ -107,7 +111,7 @@ Overview
|
|
|
107
111
|
|
|
108
112
|
.. code-block:: console
|
|
109
113
|
|
|
110
|
-
codecrate pack ROOT [options]
|
|
114
|
+
codecrate pack [ROOT] [--repo REPO ...] [options]
|
|
111
115
|
codecrate unpack PACK.md -o OUT_DIR
|
|
112
116
|
codecrate patch OLD_PACK.md ROOT [-o patch.md]
|
|
113
117
|
codecrate apply PATCH.md ROOT
|
|
@@ -117,11 +121,14 @@ Overview
|
|
|
117
121
|
pack
|
|
118
122
|
----
|
|
119
123
|
|
|
120
|
-
Create a packed Markdown context file from
|
|
124
|
+
Create a packed Markdown context file from one or more repositories.
|
|
121
125
|
|
|
122
126
|
.. code-block:: console
|
|
123
127
|
|
|
124
128
|
codecrate pack . -o context.md
|
|
129
|
+
codecrate pack --repo /path/to/repo1 --repo /path/to/repo2 -o multi.md
|
|
130
|
+
|
|
131
|
+
When using ``--repo``, omit the positional ``ROOT``. Specifying both is an error.
|
|
125
132
|
|
|
126
133
|
Useful flags:
|
|
127
134
|
|
|
@@ -131,7 +138,9 @@ Useful flags:
|
|
|
131
138
|
* ``--respect-gitignore / --no-respect-gitignore``: include ignored files or not
|
|
132
139
|
* ``--include GLOB`` (repeatable): include patterns
|
|
133
140
|
* ``--exclude GLOB`` (repeatable): exclude patterns
|
|
134
|
-
* ``--split-max-chars N``:
|
|
141
|
+
* ``--split-max-chars N``: additionally emit ``.partN.md`` files for LLMs (the
|
|
142
|
+
main output stays unsplit). For multi-repo packs, parts are named
|
|
143
|
+
``output.<repo>.partN.md``
|
|
135
144
|
|
|
136
145
|
|
|
137
146
|
unpack
|
|
@@ -41,6 +41,12 @@ Pack a repository into ``context.md``:
|
|
|
41
41
|
|
|
42
42
|
codecrate pack /path/to/repo -o context.md
|
|
43
43
|
|
|
44
|
+
Pack multiple repositories into a single output:
|
|
45
|
+
|
|
46
|
+
.. code-block:: console
|
|
47
|
+
|
|
48
|
+
codecrate pack --repo /path/to/repo1 --repo /path/to/repo2 -o multi.md
|
|
49
|
+
|
|
44
50
|
Common options:
|
|
45
51
|
|
|
46
52
|
* ``--dedupe``: deduplicate identical function bodies (enables stub layout when effective)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from codecrate.cli import main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _write_repo(root: Path, filename: str, content: str) -> None:
|
|
11
|
+
root.mkdir()
|
|
12
|
+
(root / filename).write_text(content, encoding="utf-8")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_pack_multi_repos(tmp_path: Path) -> None:
|
|
16
|
+
repo1 = tmp_path / "repo1"
|
|
17
|
+
repo2 = tmp_path / "repo2"
|
|
18
|
+
_write_repo(repo1, "a.py", "def alpha():\n return 1\n")
|
|
19
|
+
_write_repo(repo2, "b.py", "def beta():\n return 2\n")
|
|
20
|
+
|
|
21
|
+
out_path = tmp_path / "multi.md"
|
|
22
|
+
main(["pack", "--repo", str(repo1), "--repo", str(repo2), "-o", str(out_path)])
|
|
23
|
+
|
|
24
|
+
text = out_path.read_text(encoding="utf-8")
|
|
25
|
+
assert "# Repository: repo1" in text
|
|
26
|
+
assert "# Repository: repo2" in text
|
|
27
|
+
assert "def alpha()" in text
|
|
28
|
+
assert "def beta()" in text
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_pack_rejects_root_and_repo(tmp_path: Path) -> None:
|
|
32
|
+
repo = tmp_path / "repo"
|
|
33
|
+
_write_repo(repo, "a.py", "def alpha():\n return 1\n")
|
|
34
|
+
|
|
35
|
+
out_path = tmp_path / "multi.md"
|
|
36
|
+
with pytest.raises(SystemExit) as excinfo:
|
|
37
|
+
main(["pack", str(repo), "--repo", str(repo), "-o", str(out_path)])
|
|
38
|
+
|
|
39
|
+
assert excinfo.value.code == 2
|
codecrate-0.1.1/codecrate/cli.py
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import argparse
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from .config import load_config
|
|
7
|
-
from .diffgen import generate_patch_markdown
|
|
8
|
-
from .discover import discover_files
|
|
9
|
-
from .markdown import render_markdown
|
|
10
|
-
from .packer import pack_repo
|
|
11
|
-
from .token_budget import split_by_max_chars
|
|
12
|
-
from .udiff import apply_file_diffs, parse_unified_diff
|
|
13
|
-
from .unpacker import unpack_to_dir
|
|
14
|
-
from .validate import validate_pack_markdown
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
18
|
-
p = argparse.ArgumentParser(
|
|
19
|
-
prog="codecrate",
|
|
20
|
-
description="Pack/unpack/patch/apply for repositories (Python + text files).",
|
|
21
|
-
)
|
|
22
|
-
sub = p.add_subparsers(dest="cmd", required=True)
|
|
23
|
-
|
|
24
|
-
# pack
|
|
25
|
-
pack = sub.add_parser("pack", help="Pack a repository/directory into Markdown.")
|
|
26
|
-
pack.add_argument("root", type=Path, help="Root directory to scan")
|
|
27
|
-
pack.add_argument(
|
|
28
|
-
"-o",
|
|
29
|
-
"--output",
|
|
30
|
-
type=Path,
|
|
31
|
-
default=None,
|
|
32
|
-
help="Output markdown path (default: config 'output' or context.md)",
|
|
33
|
-
)
|
|
34
|
-
pack.add_argument(
|
|
35
|
-
"--dedupe", action="store_true", help="Deduplicate identical function bodies"
|
|
36
|
-
)
|
|
37
|
-
pack.add_argument(
|
|
38
|
-
"--layout",
|
|
39
|
-
choices=["auto", "stubs", "full"],
|
|
40
|
-
default=None,
|
|
41
|
-
help="Output layout: auto|stubs|full (default: auto via config)",
|
|
42
|
-
)
|
|
43
|
-
pack.add_argument(
|
|
44
|
-
"--keep-docstrings",
|
|
45
|
-
action=argparse.BooleanOptionalAction,
|
|
46
|
-
default=None,
|
|
47
|
-
help="Keep docstrings in stubbed file view (default: true via config)",
|
|
48
|
-
)
|
|
49
|
-
pack.add_argument(
|
|
50
|
-
"--respect-gitignore",
|
|
51
|
-
action=argparse.BooleanOptionalAction,
|
|
52
|
-
default=None,
|
|
53
|
-
help="Respect .gitignore (default: true via config)",
|
|
54
|
-
)
|
|
55
|
-
pack.add_argument(
|
|
56
|
-
"--manifest",
|
|
57
|
-
action=argparse.BooleanOptionalAction,
|
|
58
|
-
default=None,
|
|
59
|
-
help="Include Manifest section (default: true via config)",
|
|
60
|
-
)
|
|
61
|
-
pack.add_argument(
|
|
62
|
-
"--include", action="append", default=None, help="Include glob (repeatable)"
|
|
63
|
-
)
|
|
64
|
-
pack.add_argument(
|
|
65
|
-
"--exclude", action="append", default=None, help="Exclude glob (repeatable)"
|
|
66
|
-
)
|
|
67
|
-
pack.add_argument(
|
|
68
|
-
"--split-max-chars",
|
|
69
|
-
type=int,
|
|
70
|
-
default=None,
|
|
71
|
-
help="Split output into .partN.md files",
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
# unpack
|
|
75
|
-
unpack = sub.add_parser(
|
|
76
|
-
"unpack", help="Reconstruct files from a packed context Markdown."
|
|
77
|
-
)
|
|
78
|
-
unpack.add_argument("markdown", type=Path, help="Packed Markdown file from `pack`")
|
|
79
|
-
unpack.add_argument(
|
|
80
|
-
"-o",
|
|
81
|
-
"--out-dir",
|
|
82
|
-
type=Path,
|
|
83
|
-
required=True,
|
|
84
|
-
help="Output directory for reconstructed files",
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
# patch
|
|
88
|
-
patch = sub.add_parser(
|
|
89
|
-
"patch",
|
|
90
|
-
help="Generate a diff-only patch Markdown from old pack + current repo.",
|
|
91
|
-
)
|
|
92
|
-
patch.add_argument(
|
|
93
|
-
"old_markdown", type=Path, help="Older packed Markdown (baseline)"
|
|
94
|
-
)
|
|
95
|
-
patch.add_argument("root", type=Path, help="Current repo root to compare against")
|
|
96
|
-
patch.add_argument(
|
|
97
|
-
"-o",
|
|
98
|
-
"--output",
|
|
99
|
-
type=Path,
|
|
100
|
-
default=Path("patch.md"),
|
|
101
|
-
help="Output patch markdown",
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
# apply
|
|
105
|
-
apply = sub.add_parser("apply", help="Apply a diff-only patch Markdown to a repo.")
|
|
106
|
-
apply.add_argument(
|
|
107
|
-
"patch_markdown", type=Path, help="Patch Markdown containing ```diff blocks"
|
|
108
|
-
)
|
|
109
|
-
apply.add_argument("root", type=Path, help="Repo root to apply patch to")
|
|
110
|
-
# validate-pack
|
|
111
|
-
vpack = sub.add_parser(
|
|
112
|
-
"validate-pack",
|
|
113
|
-
help="Validate a packed context Markdown (sha/markers/canonical consistency).",
|
|
114
|
-
)
|
|
115
|
-
vpack.add_argument("markdown", type=Path, help="Packed Markdown to validate")
|
|
116
|
-
vpack.add_argument(
|
|
117
|
-
"--root",
|
|
118
|
-
type=Path,
|
|
119
|
-
default=None,
|
|
120
|
-
help="Optional repo root to compare reconstructed files against",
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
return p
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def _extract_diff_blocks(md_text: str) -> str:
|
|
127
|
-
"""
|
|
128
|
-
Extract only diff fences from markdown and concatenate to a unified diff string.
|
|
129
|
-
"""
|
|
130
|
-
lines = md_text.splitlines()
|
|
131
|
-
out: list[str] = []
|
|
132
|
-
i = 0
|
|
133
|
-
while i < len(lines):
|
|
134
|
-
if lines[i].strip() == "```diff":
|
|
135
|
-
i += 1
|
|
136
|
-
while i < len(lines) and lines[i].strip() != "```":
|
|
137
|
-
out.append(lines[i])
|
|
138
|
-
i += 1
|
|
139
|
-
i += 1
|
|
140
|
-
return "\n".join(out) + "\n"
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def main(argv: list[str] | None = None) -> None:
|
|
144
|
-
parser = build_parser()
|
|
145
|
-
args = parser.parse_args(argv)
|
|
146
|
-
|
|
147
|
-
if args.cmd == "pack":
|
|
148
|
-
root: Path = args.root.resolve()
|
|
149
|
-
cfg = load_config(root)
|
|
150
|
-
|
|
151
|
-
include = args.include if args.include is not None else cfg.include
|
|
152
|
-
exclude = args.exclude if args.exclude is not None else cfg.exclude
|
|
153
|
-
|
|
154
|
-
keep_docstrings = (
|
|
155
|
-
cfg.keep_docstrings
|
|
156
|
-
if args.keep_docstrings is None
|
|
157
|
-
else bool(args.keep_docstrings)
|
|
158
|
-
)
|
|
159
|
-
include_manifest = (
|
|
160
|
-
cfg.manifest if args.manifest is None else bool(args.manifest)
|
|
161
|
-
)
|
|
162
|
-
respect_gitignore = (
|
|
163
|
-
cfg.respect_gitignore
|
|
164
|
-
if args.respect_gitignore is None
|
|
165
|
-
else bool(args.respect_gitignore)
|
|
166
|
-
)
|
|
167
|
-
dedupe = bool(args.dedupe) or bool(cfg.dedupe)
|
|
168
|
-
split_max_chars = (
|
|
169
|
-
cfg.split_max_chars
|
|
170
|
-
if args.split_max_chars is None
|
|
171
|
-
else int(args.split_max_chars or 0)
|
|
172
|
-
)
|
|
173
|
-
layout = (
|
|
174
|
-
str(args.layout).strip().lower()
|
|
175
|
-
if args.layout is not None
|
|
176
|
-
else str(getattr(cfg, "layout", "auto")).strip().lower()
|
|
177
|
-
)
|
|
178
|
-
if args.output is not None:
|
|
179
|
-
out_path = args.output
|
|
180
|
-
else:
|
|
181
|
-
out_path = Path(getattr(cfg, "output", "context.md"))
|
|
182
|
-
if not out_path.is_absolute():
|
|
183
|
-
out_path = root / out_path
|
|
184
|
-
disc = discover_files(
|
|
185
|
-
root=root,
|
|
186
|
-
include=include,
|
|
187
|
-
exclude=exclude,
|
|
188
|
-
respect_gitignore=respect_gitignore,
|
|
189
|
-
)
|
|
190
|
-
pack, canonical = pack_repo(
|
|
191
|
-
disc.root, disc.files, keep_docstrings=keep_docstrings, dedupe=dedupe
|
|
192
|
-
)
|
|
193
|
-
md = render_markdown(
|
|
194
|
-
pack, canonical, layout=layout, include_manifest=include_manifest
|
|
195
|
-
)
|
|
196
|
-
# Always write the canonical, unsplit pack
|
|
197
|
-
# for machine parsing (unpack/validate).
|
|
198
|
-
out_path.write_text(md, encoding="utf-8")
|
|
199
|
-
|
|
200
|
-
# Additionally, write split parts for LLM consumption, if requested.
|
|
201
|
-
parts = split_by_max_chars(md, out_path, split_max_chars)
|
|
202
|
-
extra = [p for p in parts if p.path != out_path]
|
|
203
|
-
for part in extra:
|
|
204
|
-
part.path.write_text(part.content, encoding="utf-8")
|
|
205
|
-
|
|
206
|
-
if extra:
|
|
207
|
-
print(f"Wrote {out_path} and {len(extra)} split part file(s).")
|
|
208
|
-
else:
|
|
209
|
-
print(f"Wrote {out_path}.")
|
|
210
|
-
elif args.cmd == "unpack":
|
|
211
|
-
md_text = args.markdown.read_text(encoding="utf-8", errors="replace")
|
|
212
|
-
unpack_to_dir(md_text, args.out_dir)
|
|
213
|
-
print(f"Unpacked into {args.out_dir}")
|
|
214
|
-
|
|
215
|
-
elif args.cmd == "patch":
|
|
216
|
-
old_md = args.old_markdown.read_text(encoding="utf-8", errors="replace")
|
|
217
|
-
cfg = load_config(args.root)
|
|
218
|
-
patch_md = generate_patch_markdown(
|
|
219
|
-
old_md,
|
|
220
|
-
args.root,
|
|
221
|
-
include=cfg.include,
|
|
222
|
-
exclude=cfg.exclude,
|
|
223
|
-
respect_gitignore=cfg.respect_gitignore,
|
|
224
|
-
)
|
|
225
|
-
args.output.write_text(patch_md, encoding="utf-8")
|
|
226
|
-
print(f"Wrote {args.output}")
|
|
227
|
-
|
|
228
|
-
elif args.cmd == "validate-pack":
|
|
229
|
-
md_text = args.markdown.read_text(encoding="utf-8", errors="replace")
|
|
230
|
-
report = validate_pack_markdown(md_text, root=args.root)
|
|
231
|
-
if report.warnings:
|
|
232
|
-
print("Warnings:")
|
|
233
|
-
for w in report.warnings:
|
|
234
|
-
print(f"- {w}")
|
|
235
|
-
if report.errors:
|
|
236
|
-
print("Errors:")
|
|
237
|
-
for e in report.errors:
|
|
238
|
-
print(f"- {e}")
|
|
239
|
-
raise SystemExit(1)
|
|
240
|
-
print("OK: pack is internally consistent.")
|
|
241
|
-
|
|
242
|
-
elif args.cmd == "apply":
|
|
243
|
-
md_text = args.patch_markdown.read_text(encoding="utf-8", errors="replace")
|
|
244
|
-
diff_text = _extract_diff_blocks(md_text)
|
|
245
|
-
diffs = parse_unified_diff(diff_text)
|
|
246
|
-
changed = apply_file_diffs(diffs, args.root)
|
|
247
|
-
print(f"Applied patch to {len(changed)} file(s).")
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if __name__ == "__main__":
|
|
251
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|