crapkit 0.7.2__tar.gz → 0.7.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. {crapkit-0.7.2/src/crapkit.egg-info → crapkit-0.7.3}/PKG-INFO +5 -5
  2. {crapkit-0.7.2 → crapkit-0.7.3}/README.md +4 -4
  3. {crapkit-0.7.2 → crapkit-0.7.3}/pyproject.toml +1 -1
  4. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/__init__.py +1 -1
  5. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/scoring.py +13 -0
  6. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/verifying.py +17 -1
  7. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/mcp_server.py +22 -18
  8. {crapkit-0.7.2 → crapkit-0.7.3/src/crapkit.egg-info}/PKG-INFO +5 -5
  9. {crapkit-0.7.2 → crapkit-0.7.3}/LICENSE +0 -0
  10. {crapkit-0.7.2 → crapkit-0.7.3}/setup.cfg +0 -0
  11. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/__main__.py +0 -0
  12. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/_analysis_pool.py +0 -0
  13. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/_mcp_stdio.py +0 -0
  14. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/_process_family.py +0 -0
  15. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/_process_owner.py +0 -0
  16. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/_pygdefer.py +0 -0
  17. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/_windows_job.py +0 -0
  18. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/analyze.py +0 -0
  19. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cache.py +0 -0
  20. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/churn.py +0 -0
  21. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/churn_cache.py +0 -0
  22. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/churn_log.py +0 -0
  23. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/__init__.py +0 -0
  24. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/_shared.py +0 -0
  25. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/admin.py +0 -0
  26. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/analyses.py +0 -0
  27. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/claude_hook.py +0 -0
  28. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/maintenance.py +0 -0
  29. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/parser.py +0 -0
  30. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/queue.py +0 -0
  31. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/ratchet_cmds.py +0 -0
  32. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/cli/reports.py +0 -0
  33. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/config.py +0 -0
  34. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/config_contract.py +0 -0
  35. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/coupling.py +0 -0
  36. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/coupling_cache.py +0 -0
  37. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/coverage_istanbul.py +0 -0
  38. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/coverage_py.py +0 -0
  39. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/covstream.py +0 -0
  40. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/diffparse.py +0 -0
  41. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/digest.py +0 -0
  42. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/doctor.py +0 -0
  43. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/dup.py +0 -0
  44. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/errors.py +0 -0
  45. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/gitio.py +0 -0
  46. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/gitpaths.py +0 -0
  47. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/hook.py +0 -0
  48. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/invocation.py +0 -0
  49. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/junitparse.py +0 -0
  50. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/keys.py +0 -0
  51. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/lanes.py +0 -0
  52. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/lizardcognitive.py +0 -0
  53. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/lizardpowershell.py +0 -0
  54. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/lizardrust.py +0 -0
  55. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/lizardshell.py +0 -0
  56. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/lizardtypescript.py +0 -0
  57. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/locks.py +0 -0
  58. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/logs.py +0 -0
  59. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/merge.py +0 -0
  60. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/mutate.py +0 -0
  61. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/mutate_pool.py +0 -0
  62. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/override.py +0 -0
  63. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/packet.py +0 -0
  64. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/procs.py +0 -0
  65. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/ratchet.py +0 -0
  66. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/ratchet_report.py +0 -0
  67. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/ratchetfile.py +0 -0
  68. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/records.py +0 -0
  69. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/report.py +0 -0
  70. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/repotext.py +0 -0
  71. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/resources.py +0 -0
  72. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/retention.py +0 -0
  73. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/rootfind.py +0 -0
  74. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/sarif.py +0 -0
  75. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/sarifio.py +0 -0
  76. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/scaffold.py +0 -0
  77. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/score.py +0 -0
  78. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/snapshot.py +0 -0
  79. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/store.py +0 -0
  80. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/uncovered.py +0 -0
  81. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/universe.py +0 -0
  82. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/verify.py +0 -0
  83. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/watch.py +0 -0
  84. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit/worklist.py +0 -0
  85. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit.egg-info/SOURCES.txt +0 -0
  86. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit.egg-info/dependency_links.txt +0 -0
  87. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit.egg-info/entry_points.txt +0 -0
  88. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit.egg-info/requires.txt +0 -0
  89. {crapkit-0.7.2 → crapkit-0.7.3}/src/crapkit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crapkit
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more.
5
5
  Author: Jean-Francois Gagne
6
6
  License: MIT
@@ -199,7 +199,7 @@ files. Review those commands before running Crapkit in a repository you do not t
199
199
 
200
200
  ```
201
201
  $ crapkit --version
202
- crapkit 0.7.2
202
+ crapkit 0.7.3
203
203
  ```
204
204
 
205
205
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -455,7 +455,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
455
455
  repos:
456
456
  - repo: https://github.com/JeanFrancoisGagne/crapkit
457
457
  # crapkit's release step rewrites this line to the tag it just cut
458
- rev: v0.7.2
458
+ rev: v0.7.3
459
459
  hooks:
460
460
  - id: crapkit-gate
461
461
  ```
@@ -563,7 +563,7 @@ to a workflow, and every input has a default:
563
563
  - uses: actions/checkout@v4
564
564
  with:
565
565
  fetch-depth: 0
566
- - uses: JeanFrancoisGagne/crapkit@v0.7.2
566
+ - uses: JeanFrancoisGagne/crapkit@v0.7.3
567
567
  ```
568
568
 
569
569
  The whole job those four lines sit in:
@@ -583,7 +583,7 @@ jobs:
583
583
  with:
584
584
  python-version: "3.12" # the interpreter the install below lands in
585
585
  - run: pip install -e ".[dev]" # whatever your lanes need to run
586
- - uses: JeanFrancoisGagne/crapkit@v0.7.2
586
+ - uses: JeanFrancoisGagne/crapkit@v0.7.3
587
587
  with:
588
588
  gate: "false"
589
589
  ```
@@ -158,7 +158,7 @@ files. Review those commands before running Crapkit in a repository you do not t
158
158
 
159
159
  ```
160
160
  $ crapkit --version
161
- crapkit 0.7.2
161
+ crapkit 0.7.3
162
162
  ```
163
163
 
164
164
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -414,7 +414,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
414
414
  repos:
415
415
  - repo: https://github.com/JeanFrancoisGagne/crapkit
416
416
  # crapkit's release step rewrites this line to the tag it just cut
417
- rev: v0.7.2
417
+ rev: v0.7.3
418
418
  hooks:
419
419
  - id: crapkit-gate
420
420
  ```
@@ -522,7 +522,7 @@ to a workflow, and every input has a default:
522
522
  - uses: actions/checkout@v4
523
523
  with:
524
524
  fetch-depth: 0
525
- - uses: JeanFrancoisGagne/crapkit@v0.7.2
525
+ - uses: JeanFrancoisGagne/crapkit@v0.7.3
526
526
  ```
527
527
 
528
528
  The whole job those four lines sit in:
@@ -542,7 +542,7 @@ jobs:
542
542
  with:
543
543
  python-version: "3.12" # the interpreter the install below lands in
544
544
  - run: pip install -e ".[dev]" # whatever your lanes need to run
545
- - uses: JeanFrancoisGagne/crapkit@v0.7.2
545
+ - uses: JeanFrancoisGagne/crapkit@v0.7.3
546
546
  with:
547
547
  gate: "false"
548
548
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "crapkit"
7
- version = "0.7.2"
7
+ version = "0.7.3"
8
8
  description = "Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  license = { text = "MIT" }
@@ -1,2 +1,2 @@
1
1
  """crapkit: deterministic CRAP-score framework."""
2
- __version__ = "0.7.2"
2
+ __version__ = "0.7.3"
@@ -540,12 +540,25 @@ def _rescored_records(root: Path, cache_path: Path, flat: list,
540
540
  return records_by_path
541
541
 
542
542
 
543
+ def _refuse_missing(root: Path, rel_paths: list) -> None:
544
+ """A path crapkit cannot open is refused the way one it cannot place is.
545
+
546
+ Unchecked, a typo reached the analyzer and came back as a FileNotFoundError
547
+ traceback with exit 1; through the MCP server that traceback was the whole
548
+ answer. A config error says which path, and exits 3 like every other
549
+ argument the command cannot act on."""
550
+ missing = [rel for rel in rel_paths if not (root / rel).exists()]
551
+ if missing:
552
+ raise ConfigError(f"{', '.join(missing)} does not exist under {root}")
553
+
554
+
543
555
  def _rescore_analyze(root: Path, cfg, files, cwd: Path | None = None) -> tuple[list, list, dict]:
544
556
  """Fresh complexity for the named files, said from `cwd` where the user
545
557
  stands; the shared cache is merged, never truncated."""
546
558
  from ..hook import file_ceilings
547
559
 
548
560
  rel_paths = sorted({_repo_relative(p, root, cwd) for p in files})
561
+ _refuse_missing(root, rel_paths)
549
562
  files_by_scope = assign_files(rel_paths, cfg, size_of=_file_sizer(root))
550
563
  flat = sorted(set().union(*files_by_scope.values())) if files_by_scope else []
551
564
  records_by_path = _rescored_records(root, root / ".crapkit" / "cache.json", flat,
@@ -558,6 +558,20 @@ def _ratchet_suffix(changes: dict | None, overridden: list, ratchet_file: str) -
558
558
  return f" ratchet: {_marks_moved(changes)} -> git add {ratchet_file}"
559
559
 
560
560
 
561
+ def _forgiven_suffix(out: dict) -> str:
562
+ """Failures this verdict forgives because the baseline carries them too.
563
+
564
+ A regression verdict is about change, so an unchanged failure is not a
565
+ regression. Saying nothing about it made `verify OK` read as a clean suite
566
+ beside three failing tests, and the release guard downstream, which does not
567
+ forgive them, then refused evidence the operator had just watched pass."""
568
+ forgiven = out.get("forgiven_failures") or ()
569
+ if not forgiven:
570
+ return ""
571
+ plural = "" if len(forgiven) == 1 else "s"
572
+ return f" ({len(forgiven)} unchanged failure{plural} forgiven, first {forgiven[0]})"
573
+
574
+
561
575
  def _report_verify(as_json: bool, out: dict, verdict, overridden, ratchet_file: str) -> None:
562
576
  if as_json:
563
577
  _print_json(out)
@@ -565,6 +579,7 @@ def _report_verify(as_json: bool, out: dict, verdict, overridden, ratchet_file:
565
579
  state = "OK" if verdict.ok else "FAILED"
566
580
  print(f"verify {state} @ {out['commit'][:11]} vs baseline {out['baseline_commit'][:11]} "
567
581
  f"({out['changed_files']} changed files)"
582
+ f"{_forgiven_suffix(out)}"
568
583
  f"{_ratchet_suffix(out['ratchet_changes'], overridden, ratchet_file)}")
569
584
  _print_verify_findings(verdict, overridden)
570
585
  _print_finding_split(verdict)
@@ -649,7 +664,8 @@ def cmd_verify(args: argparse.Namespace) -> int:
649
664
  _report_verify(args.json,
650
665
  {**_verify_result(verdict, overridden, run_id, baseline, commit, ranges,
651
666
  uncovered, cfg.diff_uncovered_max, len(unmarked)),
652
- **_receipt(tool_versions, saved.sha256, changes)},
667
+ **_receipt(tool_versions, saved.sha256, changes),
668
+ "forgiven_failures": sorted(set(fresh_failures) - set(verdict.new_failures))},
653
669
  verdict, overridden, cfg.ratchet_file)
654
670
  _refuse_override(verdict, args.override)
655
671
  return _verify_exit_code(verdict)
@@ -294,11 +294,13 @@ TOOLS: tuple[dict, ...] = (
294
294
  "positional": (),
295
295
  "flags": {},
296
296
  "description": ("Lists every run in the store, oldest first by id. Use it to date the store "
297
- "or to see which commit the other tools answer from, get_trend for per-run "
298
- "totals, and get_function_history for one function's scores per run. It reads "
299
- "the store only and spawns no git, and baseline true is not always the newest "
300
- "run. repo can be any directory under a measured checkout, and an unmeasured "
301
- "one answers isError true with the setup pointer."),
297
+ "or to see which commit the other tools answer from. Use get_trend for "
298
+ "per-run totals and get_function_history for one function's scores per run. "
299
+ "It reads the store only and spawns no git. repo may be any directory under "
300
+ "the checkout, because the server walks up to the nearest crapkit.toml, and a "
301
+ "relative path resolves from the server's start directory. No crapkit.toml "
302
+ "above it answers an init pointer, and a checkout never scored answers a "
303
+ "coverage pointer, both as isError true."),
302
304
  "properties": {},
303
305
  "output": {
304
306
  "schema": {
@@ -1278,13 +1280,14 @@ TOOLS: tuple[dict, ...] = (
1278
1280
  "positional": ("path",),
1279
1281
  "flags": {},
1280
1282
  "verdict_exits": (6,),
1281
- "description": ("Checks whether an edited file clears the commit gate as the hook will: fresh "
1282
- "ccn per changed function against its scope's ceiling, less pardoned debt. "
1283
- "Call it after an edit, once get_function_brief has stated the rule, and "
1284
- "leave the repo-wide verdict to CLI verify. It re-measures ccn off git diff, "
1285
- "runs no tests, and answers a breach as gate.ok false, not a tool error. path "
1286
- "is repo-relative and repo must hold it: a tracked file is judged on its diff "
1287
- "from HEAD, an untracked one in full, an unchanged one judges 0."),
1283
+ "description": ("Checks whether an edited file clears the hook's commit gate: fresh ccn per "
1284
+ "changed function against its scope's ceiling less pardoned ratchet debt. "
1285
+ "Call it after an edit once get_function_brief states the rule. CLI verify "
1286
+ "gives the repo-wide verdict. It runs no tests, and a breach reads gate.ok "
1287
+ "false, not an error. path is repo-relative or absolute inside repo, outside "
1288
+ "or missing is a config error. A tracked file is judged on its diff from "
1289
+ "HEAD, an untracked one in full, an unchanged or unscoped one judges 0. repo "
1290
+ "may be any directory under the checkout."),
1288
1291
  "properties": {
1289
1292
  "path": {
1290
1293
  "type": "string",
@@ -1415,12 +1418,13 @@ TOOLS: tuple[dict, ...] = (
1415
1418
  "positional": (),
1416
1419
  "flags": {},
1417
1420
  "description": ("Lists open claims on queue items, oldest first. Use it when get_next_item "
1418
- "answers empty or skipped_claimed above 0, and get_function_brief's attempts "
1419
- "for one function. No tool here writes a claim by design, so a stale one is "
1420
- "released in the CLI with crapkit claims release PATH NAME, and one closes "
1421
- "when verify finds the function at its ceiling. repo may be any directory "
1422
- "under a checkout where crapkit init and crapkit coverage have run, and an "
1423
- "unmeasured one answers isError true with the setup pointer."),
1421
+ "answers empty or skipped_claimed above 0, and use get_function_brief to see "
1422
+ "one function's own attempts. No tool here writes a claim: the CLI releases a "
1423
+ "stale one with crapkit claims release PATH NAME, and verify closes one at "
1424
+ "the ceiling. repo may be any directory under the checkout, since the server "
1425
+ "walks up to the nearest crapkit.toml. No crapkit.toml above it answers an "
1426
+ "init pointer, and a checkout never scored answers a coverage pointer, both "
1427
+ "as isError true."),
1424
1428
  "properties": {},
1425
1429
  "output": {
1426
1430
  "schema": {
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crapkit
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: Scores every function on complexity times uncovered risk, ranks the worst, and blocks commits that add more.
5
5
  Author: Jean-Francois Gagne
6
6
  License: MIT
@@ -199,7 +199,7 @@ files. Review those commands before running Crapkit in a repository you do not t
199
199
 
200
200
  ```
201
201
  $ crapkit --version
202
- crapkit 0.7.2
202
+ crapkit 0.7.3
203
203
  ```
204
204
 
205
205
  `python -m crapkit` works identically to the console script and is what to use from a
@@ -455,7 +455,7 @@ crapkit ships a `.pre-commit-hooks.yaml` declaring `id: crapkit-gate`. In your
455
455
  repos:
456
456
  - repo: https://github.com/JeanFrancoisGagne/crapkit
457
457
  # crapkit's release step rewrites this line to the tag it just cut
458
- rev: v0.7.2
458
+ rev: v0.7.3
459
459
  hooks:
460
460
  - id: crapkit-gate
461
461
  ```
@@ -563,7 +563,7 @@ to a workflow, and every input has a default:
563
563
  - uses: actions/checkout@v4
564
564
  with:
565
565
  fetch-depth: 0
566
- - uses: JeanFrancoisGagne/crapkit@v0.7.2
566
+ - uses: JeanFrancoisGagne/crapkit@v0.7.3
567
567
  ```
568
568
 
569
569
  The whole job those four lines sit in:
@@ -583,7 +583,7 @@ jobs:
583
583
  with:
584
584
  python-version: "3.12" # the interpreter the install below lands in
585
585
  - run: pip install -e ".[dev]" # whatever your lanes need to run
586
- - uses: JeanFrancoisGagne/crapkit@v0.7.2
586
+ - uses: JeanFrancoisGagne/crapkit@v0.7.3
587
587
  with:
588
588
  gate: "false"
589
589
  ```
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