nabla-cli 0.6.4__tar.gz → 0.7.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.
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/PKG-INFO +18 -1
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/README.md +17 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/__init__.py +1 -1
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/cli.py +81 -23
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla_cli.egg-info/PKG-INFO +18 -1
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/pyproject.toml +1 -1
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_cli_ux.py +69 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/__main__.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/branding.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/config.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/contract.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/induce.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/profile.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/prompt_recon.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/recorder.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/samplegen.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/scanner.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/schema_resolver.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/schemas/site_profile.schema.json +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/term.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla/ui.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla_cli.egg-info/SOURCES.txt +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla_cli.egg-info/dependency_links.txt +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla_cli.egg-info/entry_points.txt +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla_cli.egg-info/requires.txt +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/nabla_cli.egg-info/top_level.txt +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/setup.cfg +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_config.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_e2e_profile.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_induce.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_prompt_recon.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_recorder.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_samplegen.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_scanner.py +0 -0
- {nabla_cli-0.6.4 → nabla_cli-0.7.0}/tests/test_schema_resolver.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nabla-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Call-site harvester: scan OpenAI call sites, record real traffic, emit site profiles
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -162,6 +162,23 @@ returns a goal description, implicit constraints, and difficulty
|
|
|
162
162
|
dimensions with cited evidence. That's the goal line printed above, and
|
|
163
163
|
it's what lands in the profile's `goal` block.
|
|
164
164
|
|
|
165
|
+
### `nabla jeremy`
|
|
166
|
+
|
|
167
|
+
An alternative to `build` with the same inputs and site handling: instead
|
|
168
|
+
of the full site profile, it emits `<site>.jeremy.json` — a seed spec for
|
|
169
|
+
a problem generator:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"goal": "You are generating new problems to test and eventually fine-tune a small student model on the following task: ...",
|
|
174
|
+
"system_prompt": "<the site's own system prompt>",
|
|
175
|
+
"examples": [ {"problem": "...", "solution": "..."}, ... ]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The goal text is composed deterministically from the same inferred goal
|
|
180
|
+
and constraints that `build` uses; the examples are the captured pairs.
|
|
181
|
+
|
|
165
182
|
## Providers
|
|
166
183
|
|
|
167
184
|
`nabla init` supports three providers. Pick whichever you have a key
|
|
@@ -148,6 +148,23 @@ returns a goal description, implicit constraints, and difficulty
|
|
|
148
148
|
dimensions with cited evidence. That's the goal line printed above, and
|
|
149
149
|
it's what lands in the profile's `goal` block.
|
|
150
150
|
|
|
151
|
+
### `nabla jeremy`
|
|
152
|
+
|
|
153
|
+
An alternative to `build` with the same inputs and site handling: instead
|
|
154
|
+
of the full site profile, it emits `<site>.jeremy.json` — a seed spec for
|
|
155
|
+
a problem generator:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"goal": "You are generating new problems to test and eventually fine-tune a small student model on the following task: ...",
|
|
160
|
+
"system_prompt": "<the site's own system prompt>",
|
|
161
|
+
"examples": [ {"problem": "...", "solution": "..."}, ... ]
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The goal text is composed deterministically from the same inferred goal
|
|
166
|
+
and constraints that `build` uses; the examples are the captured pairs.
|
|
167
|
+
|
|
151
168
|
## Providers
|
|
152
169
|
|
|
153
170
|
`nabla init` supports three providers. Pick whichever you have a key
|
|
@@ -132,6 +132,29 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
132
132
|
p_profile.add_argument("--out", default=None,
|
|
133
133
|
help="output path (default: <site>.profile.json)")
|
|
134
134
|
|
|
135
|
+
p_jeremy = sub.add_parser(
|
|
136
|
+
"jeremy",
|
|
137
|
+
help="assemble a seed spec (goal + system_prompt + problem/solution examples)",
|
|
138
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
139
|
+
epilog=(
|
|
140
|
+
"an alternative to `build`: same inputs, but emits <site>.jeremy.json —\n"
|
|
141
|
+
"a generator-framed goal, the site's system prompt, and the captured\n"
|
|
142
|
+
"pairs as {problem, solution} reference examples.\n\n"
|
|
143
|
+
"examples:\n"
|
|
144
|
+
" nabla jeremy auto: pick site, find <site>.captured.jsonl\n"
|
|
145
|
+
" nabla jeremy path/to/repo --site extract_invoice --captured captured.jsonl\n"
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
p_jeremy.add_argument("repo", nargs="?", default=".", help="path to target repo (default: current directory)")
|
|
149
|
+
p_jeremy.add_argument(
|
|
150
|
+
"--site",
|
|
151
|
+
help="site symbol (auto-picked when omitted and the repo has exactly one supported call site)",
|
|
152
|
+
)
|
|
153
|
+
p_jeremy.add_argument("--captured", "--examples", dest="examples",
|
|
154
|
+
help="captured JSONL from `nabla capture` (default: <site>.captured.jsonl)")
|
|
155
|
+
p_jeremy.add_argument("--out", default=None,
|
|
156
|
+
help="output path (default: <site>.jeremy.json)")
|
|
157
|
+
|
|
135
158
|
return parser
|
|
136
159
|
|
|
137
160
|
|
|
@@ -193,6 +216,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
193
216
|
"record": _cmd_record, # legacy alias
|
|
194
217
|
"build": _cmd_profile,
|
|
195
218
|
"profile": _cmd_profile, # legacy alias
|
|
219
|
+
"jeremy": lambda a: _cmd_profile(a, mode="jeremy"),
|
|
196
220
|
}
|
|
197
221
|
try:
|
|
198
222
|
return commands[args.command](args)
|
|
@@ -778,21 +802,23 @@ def _find_captured_file(site) -> Path | None:
|
|
|
778
802
|
)
|
|
779
803
|
|
|
780
804
|
|
|
781
|
-
def _cmd_profile(args) -> int:
|
|
805
|
+
def _cmd_profile(args, mode: str = "profile") -> int:
|
|
782
806
|
from .ui import rail, rail_end, rail_start
|
|
783
807
|
|
|
784
|
-
|
|
808
|
+
command = "jeremy" if mode == "jeremy" else "build"
|
|
809
|
+
noun = "seed spec(s)" if mode == "jeremy" else "profile(s)"
|
|
810
|
+
rail_start(_out, f"nabla {command}")
|
|
785
811
|
sites = scan(args.repo)
|
|
786
812
|
|
|
787
|
-
# Multi-site: no --site/--captured, several supported sites — build
|
|
788
|
-
#
|
|
813
|
+
# Multi-site: no --site/--captured, several supported sites — build the
|
|
814
|
+
# artifact for every site that has captured pairs on disk.
|
|
789
815
|
if args.site is None and args.examples is None:
|
|
790
816
|
supported = [s for s in sites
|
|
791
817
|
if is_supported(resolve_schema(s, args.repo)[1], s.flags)]
|
|
792
818
|
if len(supported) > 1:
|
|
793
819
|
with_captures = [s for s in supported if _find_captured_file(s) is not None]
|
|
794
820
|
if not with_captures:
|
|
795
|
-
print(f"nabla
|
|
821
|
+
print(f"nabla {command}: no captured pairs found for any of the "
|
|
796
822
|
f"{len(supported)} supported sites. Run first: nabla capture {args.repo}",
|
|
797
823
|
file=sys.stderr)
|
|
798
824
|
return 2
|
|
@@ -801,11 +827,11 @@ def _cmd_profile(args) -> int:
|
|
|
801
827
|
for i, site in enumerate(with_captures):
|
|
802
828
|
if i:
|
|
803
829
|
rail(_out)
|
|
804
|
-
rc = _build_one(args, site) or rc
|
|
830
|
+
rc = _build_one(args, site, mode=mode) or rc
|
|
805
831
|
if skipped:
|
|
806
832
|
rail(_out, f"skipped (no captured pairs): {', '.join(skipped)}", style="yellow")
|
|
807
833
|
rail(_out)
|
|
808
|
-
rail_end(_out, f"done — built {len(with_captures)}
|
|
834
|
+
rail_end(_out, f"done — built {len(with_captures)} {noun}", style="green")
|
|
809
835
|
return rc
|
|
810
836
|
|
|
811
837
|
site, err = _resolve_site(args, sites)
|
|
@@ -813,16 +839,32 @@ def _cmd_profile(args) -> int:
|
|
|
813
839
|
return err
|
|
814
840
|
if args.site:
|
|
815
841
|
rail(_out, f"site {site.symbol} · {site.file}")
|
|
816
|
-
rc = _build_one(args, site, final_rail=True)
|
|
842
|
+
rc = _build_one(args, site, final_rail=True, mode=mode)
|
|
817
843
|
return rc
|
|
818
844
|
|
|
819
845
|
|
|
820
|
-
def
|
|
846
|
+
def _compose_seed_goal(goal) -> str:
|
|
847
|
+
"""Generator-framed goal text for the seed spec, composed in pure Python
|
|
848
|
+
from the induced GoalBlock — no extra LLM call, no invented facts."""
|
|
849
|
+
parts = [
|
|
850
|
+
"You are generating new problems to test and eventually fine-tune a "
|
|
851
|
+
"small student model on the following task: " + goal.description.rstrip(".") + "."
|
|
852
|
+
]
|
|
853
|
+
if goal.constraints:
|
|
854
|
+
parts.append("Every generated problem's solution must satisfy: "
|
|
855
|
+
+ " ".join(c.rstrip(".") + "." for c in goal.constraints))
|
|
856
|
+
parts.append("Match the style, difficulty range, and output format of the "
|
|
857
|
+
"reference examples below.")
|
|
858
|
+
return " ".join(parts)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _build_one(args, site, final_rail: bool = False, mode: str = "profile") -> int:
|
|
821
862
|
from .induce import induce
|
|
822
863
|
from .prompt_recon import reconstruct
|
|
823
864
|
from .schema_resolver import resolve_schema as _resolve
|
|
824
865
|
from .ui import rail, rail_end, rail_ok
|
|
825
866
|
|
|
867
|
+
command = "jeremy" if mode == "jeremy" else "build"
|
|
826
868
|
examples_path = args.examples
|
|
827
869
|
if examples_path is None:
|
|
828
870
|
found = _find_captured_file(site) or (
|
|
@@ -831,7 +873,7 @@ def _build_one(args, site, final_rail: bool = False) -> int:
|
|
|
831
873
|
capture_cmd = f"nabla capture {args.repo}"
|
|
832
874
|
if args.site:
|
|
833
875
|
capture_cmd += f" --site {site.symbol}"
|
|
834
|
-
print(f"nabla
|
|
876
|
+
print(f"nabla {command}: no captured pairs found ({site.symbol}.captured.jsonl). "
|
|
835
877
|
f"Run first: {capture_cmd}", file=sys.stderr)
|
|
836
878
|
return 2
|
|
837
879
|
examples_path = str(found)
|
|
@@ -857,21 +899,37 @@ def _build_one(args, site, final_rail: bool = False) -> int:
|
|
|
857
899
|
else:
|
|
858
900
|
goal = induce(template, schema, examples)
|
|
859
901
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
902
|
+
if mode == "jeremy":
|
|
903
|
+
seed = {
|
|
904
|
+
"goal": _compose_seed_goal(goal),
|
|
905
|
+
"system_prompt": template.system_prompt or "",
|
|
906
|
+
"examples": [
|
|
907
|
+
{"problem": e.prompt, "solution": e.completion} for e in examples
|
|
908
|
+
],
|
|
909
|
+
}
|
|
910
|
+
out = (args.out if final_rail else None) or f"{site.symbol}.jeremy.json"
|
|
911
|
+
Path(out).write_text(json.dumps(seed, indent=2), encoding="utf-8")
|
|
912
|
+
rail(_out)
|
|
913
|
+
rail_ok(_out, f"wrote {out}")
|
|
914
|
+
rail(_out, f"site {site.symbol}")
|
|
915
|
+
rail(_out, f"examples {len(examples)} ({recorded_from})")
|
|
916
|
+
rail(_out, f"goal {seed['goal'][:90]}")
|
|
917
|
+
else:
|
|
918
|
+
profile = build_profile(site, args.repo, examples, goal, recorded_from=recorded_from)
|
|
919
|
+
out = (args.out if final_rail else None) or f"{site.symbol}.profile.json"
|
|
920
|
+
Path(out).write_text(json.dumps(profile, indent=2), encoding="utf-8")
|
|
921
|
+
supported = profile["classification"]["supported"]
|
|
922
|
+
rail(_out)
|
|
923
|
+
rail_ok(_out, f"wrote {out}")
|
|
924
|
+
rail(_out, f"site {site.symbol} · {profile['site_id'][:16]}…")
|
|
925
|
+
rail(_out, f"supported {supported}")
|
|
926
|
+
rail(_out, f"examples {len(examples)} ({recorded_from})")
|
|
927
|
+
rail(_out, f"goal {goal.description[:90]}")
|
|
928
|
+
dims = ", ".join(d["name"] for d in goal.difficulty_dimensions[:4])
|
|
929
|
+
rail(_out, f"difficulty {dims}")
|
|
872
930
|
if len(examples) < 20:
|
|
873
931
|
rail(_out, f"only {len(examples)} examples (< 20) — enough for a look, but a "
|
|
874
|
-
"handoff-quality
|
|
932
|
+
"handoff-quality artifact wants 20+ real-traffic pairs", style="yellow")
|
|
875
933
|
if final_rail:
|
|
876
934
|
rail(_out)
|
|
877
935
|
rail_end(_out, f"done — {out} is the handoff artifact", style="green")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nabla-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Call-site harvester: scan OpenAI call sites, record real traffic, emit site profiles
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -162,6 +162,23 @@ returns a goal description, implicit constraints, and difficulty
|
|
|
162
162
|
dimensions with cited evidence. That's the goal line printed above, and
|
|
163
163
|
it's what lands in the profile's `goal` block.
|
|
164
164
|
|
|
165
|
+
### `nabla jeremy`
|
|
166
|
+
|
|
167
|
+
An alternative to `build` with the same inputs and site handling: instead
|
|
168
|
+
of the full site profile, it emits `<site>.jeremy.json` — a seed spec for
|
|
169
|
+
a problem generator:
|
|
170
|
+
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"goal": "You are generating new problems to test and eventually fine-tune a small student model on the following task: ...",
|
|
174
|
+
"system_prompt": "<the site's own system prompt>",
|
|
175
|
+
"examples": [ {"problem": "...", "solution": "..."}, ... ]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The goal text is composed deterministically from the same inferred goal
|
|
180
|
+
and constraints that `build` uses; the examples are the captured pairs.
|
|
181
|
+
|
|
165
182
|
## Providers
|
|
166
183
|
|
|
167
184
|
`nabla init` supports three providers. Pick whichever you have a key
|
|
@@ -517,3 +517,72 @@ def test_scan_skips_build_and_dist_dirs(tmp_path, monkeypatch, capsys):
|
|
|
517
517
|
out = capsys.readouterr().out
|
|
518
518
|
assert "1 call site found" in out
|
|
519
519
|
assert "copy_of_site" not in out
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
# ---------------------------------------------------------------------------
|
|
523
|
+
# nabla jeremy — seed-spec sibling of build
|
|
524
|
+
# ---------------------------------------------------------------------------
|
|
525
|
+
|
|
526
|
+
def _fake_goal(*a, **k):
|
|
527
|
+
from nabla.contract import GoalBlock
|
|
528
|
+
|
|
529
|
+
return GoalBlock(
|
|
530
|
+
description="Solve a grade-school word problem step by step.",
|
|
531
|
+
constraints=["final answer is a bare number", "no invented constants"],
|
|
532
|
+
difficulty_dimensions=[{"name": "d", "levels": ["a", "b"], "evidence": "e"}],
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_jeremy_single_site_writes_seed_spec(tmp_path, monkeypatch, capsys):
|
|
537
|
+
repo = _make_single_site_repo(tmp_path)
|
|
538
|
+
monkeypatch.chdir(tmp_path)
|
|
539
|
+
pair = {"input_slots": {"ocr_text": "r"}, "prompt": "A farmer has 6 crates...",
|
|
540
|
+
"completion": "The answer is 57.",
|
|
541
|
+
"tokens": {"in": 5, "out": 5}, "latency_ms": 10, "_source_kind": "samples"}
|
|
542
|
+
(tmp_path / "extract_receipt.captured.jsonl").write_text(
|
|
543
|
+
json.dumps(pair) + "\n", encoding="utf-8")
|
|
544
|
+
monkeypatch.setattr("nabla.induce.induce", _fake_goal)
|
|
545
|
+
|
|
546
|
+
rc = main(["jeremy", str(repo)])
|
|
547
|
+
|
|
548
|
+
assert rc == 0
|
|
549
|
+
seed = json.loads((tmp_path / "extract_receipt.jeremy.json").read_text(encoding="utf-8"))
|
|
550
|
+
assert set(seed) == {"goal", "system_prompt", "examples"}
|
|
551
|
+
assert seed["goal"].startswith("You are generating new problems")
|
|
552
|
+
assert "grade-school word problem" in seed["goal"]
|
|
553
|
+
assert "final answer is a bare number" in seed["goal"]
|
|
554
|
+
assert "Extract the receipt fields" in seed["system_prompt"]
|
|
555
|
+
assert seed["examples"] == [
|
|
556
|
+
{"problem": "A farmer has 6 crates...", "solution": "The answer is 57."}
|
|
557
|
+
]
|
|
558
|
+
assert "extract_receipt.jeremy.json" in capsys.readouterr().out
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def test_jeremy_multi_site(tmp_path, monkeypatch, capsys):
|
|
562
|
+
repo = _make_two_site_repo(tmp_path)
|
|
563
|
+
monkeypatch.chdir(tmp_path)
|
|
564
|
+
pair = {"input_slots": {}, "prompt": "p", "completion": "s",
|
|
565
|
+
"tokens": {"in": 1, "out": 1}, "latency_ms": 1, "_source_kind": "samples"}
|
|
566
|
+
for symbol in ("extract_receipt", "extract_menu"):
|
|
567
|
+
(tmp_path / f"{symbol}.captured.jsonl").write_text(
|
|
568
|
+
json.dumps(pair) + "\n", encoding="utf-8")
|
|
569
|
+
monkeypatch.setattr("nabla.induce.induce", _fake_goal)
|
|
570
|
+
|
|
571
|
+
rc = main(["jeremy", str(repo)])
|
|
572
|
+
|
|
573
|
+
assert rc == 0
|
|
574
|
+
assert (tmp_path / "extract_receipt.jeremy.json").exists()
|
|
575
|
+
assert (tmp_path / "extract_menu.jeremy.json").exists()
|
|
576
|
+
assert "built 2 seed spec(s)" in capsys.readouterr().out
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def test_jeremy_without_captures_points_at_capture(tmp_path, monkeypatch, capsys):
|
|
580
|
+
repo = _make_single_site_repo(tmp_path)
|
|
581
|
+
monkeypatch.chdir(tmp_path)
|
|
582
|
+
|
|
583
|
+
rc = main(["jeremy", str(repo)])
|
|
584
|
+
|
|
585
|
+
assert rc == 2
|
|
586
|
+
err = capsys.readouterr().err
|
|
587
|
+
assert "nabla jeremy" in err
|
|
588
|
+
assert "nabla capture" in err
|
|
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
|