errlore 0.1.2__tar.gz → 0.1.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.
- {errlore-0.1.2 → errlore-0.1.3}/CHANGELOG.md +17 -0
- {errlore-0.1.2 → errlore-0.1.3}/PKG-INFO +28 -2
- {errlore-0.1.2 → errlore-0.1.3}/README.md +27 -1
- {errlore-0.1.2 → errlore-0.1.3}/pyproject.toml +1 -1
- {errlore-0.1.2 → errlore-0.1.3}/site/index.html +23 -3
- {errlore-0.1.2 → errlore-0.1.3}/site/llms.txt +4 -3
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/__init__.py +1 -1
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/io/jsonl_writer.py +7 -8
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_regressions.py +56 -0
- {errlore-0.1.2 → errlore-0.1.3}/.github/workflows/ci.yml +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/.gitignore +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/LICENSE +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/SECURITY.md +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/benchmarks/bench_error_reduction.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/benchmarks/bench_retrieval.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/benchmarks/results/error_reduction/report.md +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/examples/anthropic_agent.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/examples/claude-code/README.md +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/examples/claude-code/errlore_posttooluse.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/examples/claude-code/errlore_sessionstart.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/examples/claude-code/settings.json.example +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/examples/langchain_agent.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/examples/openai_agent.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/integrations/openwebui/README.md +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/integrations/openwebui/errlore_feedback_action.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/integrations/openwebui/errlore_memory_filter.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/site/demo.gif +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/site/demo_script.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/site/og.png +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/errmem/__init__.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/errmem/classifier.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/errmem/injector.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/errmem/patterns.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/errmem/tracker.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/facade.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/io/__init__.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/io/jsonl_index.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/io/repair.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/lessons/__init__.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/lessons/models.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/lessons/store.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/py.typed +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/retrieval/__init__.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/retrieval/backend.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/retrieval/index.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/sanitize.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/trust/__init__.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/src/errlore/trust/engine.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/conftest.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_errmem.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_facade.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_io.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_lessons.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_openwebui_integration.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_retrieval.py +0 -0
- {errlore-0.1.2 → errlore-0.1.3}/tests/test_trust.py +0 -0
|
@@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.1.3] - 2026-07-06
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- Read-cache poisoning race in `JSONLWriter.read_all`: an append landing
|
|
12
|
+
mid-parse cached pre-append records under the post-append mtime/size, so
|
|
13
|
+
later reads returned stale data and `atomic_update` could silently drop
|
|
14
|
+
the concurrent record. Now caches only when the file is unchanged across
|
|
15
|
+
the parse (pre/post stat match). Found by pre-launch adversarial audit;
|
|
16
|
+
regression test added (Bug 3).
|
|
17
|
+
- Removed dead `JSONLWriter._invalidate_cache`.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
- README: added the error-reduction A/B benchmark section (96 paired tasks,
|
|
21
|
+
63 -> 20 failures, McNemar p=1.8e-09; knowledge-gap 46/48 -> 0/48,
|
|
22
|
+
capability-gap honestly worse at 17/48 -> 20/48), so the PyPI page shows
|
|
23
|
+
the headline evidence, not just the retrieval table.
|
|
24
|
+
|
|
8
25
|
## [0.1.2] - 2026-07-06
|
|
9
26
|
|
|
10
27
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: errlore
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Memory for AI agents that learns from failures: lessons, known-issues injection, and per-model trust — embedded, file-based, no server.
|
|
5
5
|
Project-URL: Homepage, https://errlore.com
|
|
6
6
|
Project-URL: Repository, https://github.com/Ma4etaSS/errlore
|
|
@@ -91,12 +91,38 @@ mem.report_outcome(inj, success=True)
|
|
|
91
91
|
print(mem.stats())
|
|
92
92
|
# {'errors_total': 1, 'errors_resolved': 1, 'errors_unresolved': 0,
|
|
93
93
|
# 'lessons_total': 1, 'lessons_applied': 1, 'pending_injections': 0,
|
|
94
|
-
# 'trust': {'gpt-5.5': 0.
|
|
94
|
+
# 'trust': {'gpt-5.5': 0.5522...}}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
No API keys needed. errlore itself never calls any LLM -- it manages local
|
|
98
98
|
JSONL files and does text matching. LLM calls are yours to make (or not).
|
|
99
99
|
|
|
100
|
+
## Does it actually reduce errors?
|
|
101
|
+
|
|
102
|
+
Yes -- for the class of errors memory can fix. Paired A/B benchmark
|
|
103
|
+
(`benchmarks/bench_error_reduction.py`): the same model (claude-haiku-4-5)
|
|
104
|
+
runs 96 tasks twice, with and without errlore injection. Deterministic
|
|
105
|
+
validators, no LLM judges; raw outputs committed in
|
|
106
|
+
[benchmarks/results/error_reduction/](benchmarks/results/error_reduction/).
|
|
107
|
+
|
|
108
|
+
| arm | failures | fail rate |
|
|
109
|
+
|-----|----------|-----------|
|
|
110
|
+
| A: plain | 63/96 | 65.6% |
|
|
111
|
+
| B: with errlore | 20/96 | 20.8% |
|
|
112
|
+
|
|
113
|
+
Exact McNemar over all 96 pairs: p = 1.8e-09 (49 pairs fixed, 6 broken).
|
|
114
|
+
Split by error class:
|
|
115
|
+
|
|
116
|
+
- **Knowledge-gap errors** (workspace conventions: date formats, ID
|
|
117
|
+
normalization, rounding rules, CSV column order): **46/48 -> 0/48, a 100%
|
|
118
|
+
reduction.** The model didn't know the convention; a lesson told it.
|
|
119
|
+
- **Capability-gap errors** (letter counting, string reversal): 17/48 ->
|
|
120
|
+
20/48 -- errlore did **not** help and slightly hurt. Memory fixes what the
|
|
121
|
+
model doesn't know, not what it can't do.
|
|
122
|
+
|
|
123
|
+
Reproduce: `python benchmarks/bench_error_reduction.py` (needs an Anthropic
|
|
124
|
+
API key; the task families and validators ship in the repo).
|
|
125
|
+
|
|
100
126
|
## How it works
|
|
101
127
|
|
|
102
128
|
errlore runs three reinforcement loops around your agent:
|
|
@@ -57,12 +57,38 @@ mem.report_outcome(inj, success=True)
|
|
|
57
57
|
print(mem.stats())
|
|
58
58
|
# {'errors_total': 1, 'errors_resolved': 1, 'errors_unresolved': 0,
|
|
59
59
|
# 'lessons_total': 1, 'lessons_applied': 1, 'pending_injections': 0,
|
|
60
|
-
# 'trust': {'gpt-5.5': 0.
|
|
60
|
+
# 'trust': {'gpt-5.5': 0.5522...}}
|
|
61
61
|
```
|
|
62
62
|
|
|
63
63
|
No API keys needed. errlore itself never calls any LLM -- it manages local
|
|
64
64
|
JSONL files and does text matching. LLM calls are yours to make (or not).
|
|
65
65
|
|
|
66
|
+
## Does it actually reduce errors?
|
|
67
|
+
|
|
68
|
+
Yes -- for the class of errors memory can fix. Paired A/B benchmark
|
|
69
|
+
(`benchmarks/bench_error_reduction.py`): the same model (claude-haiku-4-5)
|
|
70
|
+
runs 96 tasks twice, with and without errlore injection. Deterministic
|
|
71
|
+
validators, no LLM judges; raw outputs committed in
|
|
72
|
+
[benchmarks/results/error_reduction/](benchmarks/results/error_reduction/).
|
|
73
|
+
|
|
74
|
+
| arm | failures | fail rate |
|
|
75
|
+
|-----|----------|-----------|
|
|
76
|
+
| A: plain | 63/96 | 65.6% |
|
|
77
|
+
| B: with errlore | 20/96 | 20.8% |
|
|
78
|
+
|
|
79
|
+
Exact McNemar over all 96 pairs: p = 1.8e-09 (49 pairs fixed, 6 broken).
|
|
80
|
+
Split by error class:
|
|
81
|
+
|
|
82
|
+
- **Knowledge-gap errors** (workspace conventions: date formats, ID
|
|
83
|
+
normalization, rounding rules, CSV column order): **46/48 -> 0/48, a 100%
|
|
84
|
+
reduction.** The model didn't know the convention; a lesson told it.
|
|
85
|
+
- **Capability-gap errors** (letter counting, string reversal): 17/48 ->
|
|
86
|
+
20/48 -- errlore did **not** help and slightly hurt. Memory fixes what the
|
|
87
|
+
model doesn't know, not what it can't do.
|
|
88
|
+
|
|
89
|
+
Reproduce: `python benchmarks/bench_error_reduction.py` (needs an Anthropic
|
|
90
|
+
API key; the task families and validators ship in the repo).
|
|
91
|
+
|
|
66
92
|
## How it works
|
|
67
93
|
|
|
68
94
|
errlore runs three reinforcement loops around your agent:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "errlore"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "Memory for AI agents that learns from failures: lessons, known-issues injection, and per-model trust — embedded, file-based, no server."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -168,10 +168,13 @@ mem.resolve(err, <span class="c-str">"added validation"</span>,
|
|
|
168
168
|
lesson=<span class="c-str">"For date extraction, demand ISO-8601 and verify against source"</span>)
|
|
169
169
|
|
|
170
170
|
<span class="c-com"># 3. next similar task — memory speaks up</span>
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
task = <span class="c-str">"extract dates from contract"</span>
|
|
172
|
+
inj = mem.inject_for(task, model=<span class="c-str">"gpt-5.5"</span>, task_type=<span class="c-str">"extraction"</span>)
|
|
173
|
+
prompt = task + <span class="c-str">"\n\n"</span> + inj.text
|
|
173
174
|
<span class="c-out"># [LESSONS FROM PAST FAILURES]
|
|
174
|
-
# - hallucinated dates -> For date extraction,
|
|
175
|
+
# - UnclassifiedError: hallucinated dates -> For date extraction,
|
|
176
|
+
# demand ISO-8601 and verify against source
|
|
177
|
+
#
|
|
175
178
|
# KNOWN ISSUES:
|
|
176
179
|
# - Past error on similar task: hallucinated dates</span>
|
|
177
180
|
|
|
@@ -180,6 +183,23 @@ mem.report_outcome(inj, success=<span class="c-kw">True</span>)</pre>
|
|
|
180
183
|
</div>
|
|
181
184
|
</section>
|
|
182
185
|
|
|
186
|
+
<section>
|
|
187
|
+
<h2>Measured, not promised</h2>
|
|
188
|
+
<p style="color:var(--dim);margin-bottom:18px">Paired A/B: the same model
|
|
189
|
+
(claude-haiku-4-5) runs 96 tasks with and without errlore injection. Deterministic
|
|
190
|
+
validators, no LLM judges; raw outputs committed to the repo. Exact McNemar over
|
|
191
|
+
all 96 pairs: p = 1.8e-09.</p>
|
|
192
|
+
<table>
|
|
193
|
+
<tr><th>error class</th><th>plain</th><th>with errlore</th></tr>
|
|
194
|
+
<tr><td>knowledge-gap (workspace conventions)</td><td>46/48</td><td><b class="zero">0/48</b></td></tr>
|
|
195
|
+
<tr><td>capability-gap (model skill limits)</td><td>17/48</td><td>20/48</td></tr>
|
|
196
|
+
</table>
|
|
197
|
+
<p class="note">The honest half is the second row: errlore did <i>not</i> help on
|
|
198
|
+
things the model can't do (letter counting, string reversal) — and even slightly hurt.
|
|
199
|
+
Memory fixes what the model doesn't know, not what it can't do.
|
|
200
|
+
Reproduce it yourself: <span class="mono">python benchmarks/bench_error_reduction.py</span>.</p>
|
|
201
|
+
</section>
|
|
202
|
+
|
|
183
203
|
<section>
|
|
184
204
|
<h2>Finds lessons by meaning, not keywords</h2>
|
|
185
205
|
<p style="color:var(--dim);margin-bottom:18px">Optional local embeddings
|
|
@@ -12,9 +12,10 @@ closes the loop: it tracks whether an injected lesson actually helped
|
|
|
12
12
|
(reinforce/decay) and which model to trust per task domain.
|
|
13
13
|
|
|
14
14
|
Proof: paired A/B (deterministic validators, raw outputs committed):
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
63 -> 20 failures across all 96 paired tasks (exact McNemar p=1.8e-09);
|
|
16
|
+
repeated workspace-convention errors 46/48 -> 0/48 (100% reduction).
|
|
17
|
+
Capability-gap errors (e.g. letter counting) are honestly NOT improved
|
|
18
|
+
(17/48 -> 20/48). See the benchmark in the repository.
|
|
18
19
|
|
|
19
20
|
## Links
|
|
20
21
|
- [Website](https://errlore.com)
|
|
@@ -285,11 +285,6 @@ class JSONLWriter:
|
|
|
285
285
|
self.atomic_rewrite(path, new_entries)
|
|
286
286
|
return new_entries
|
|
287
287
|
|
|
288
|
-
def _invalidate_cache(self, path: Path) -> None:
|
|
289
|
-
"""Drop the read cache entry for *path*."""
|
|
290
|
-
with self._cache_lock:
|
|
291
|
-
self._read_cache.pop(str(path), None)
|
|
292
|
-
|
|
293
288
|
def read_all(self, path: Path) -> list[dict[str, object]]:
|
|
294
289
|
"""Read all valid records from a JSONL file.
|
|
295
290
|
|
|
@@ -330,13 +325,17 @@ class JSONLWriter:
|
|
|
330
325
|
except json.JSONDecodeError:
|
|
331
326
|
logger.warning("Skipping corrupted line in %s", path.name)
|
|
332
327
|
|
|
333
|
-
#
|
|
328
|
+
# Cache only if the file is unchanged since the pre-parse stat.
|
|
329
|
+
# An append landing mid-parse would otherwise store the pre-append
|
|
330
|
+
# records under the post-append mtime/size — a poisoned cache that
|
|
331
|
+
# makes atomic_update silently drop the concurrent record.
|
|
334
332
|
try:
|
|
335
333
|
st2 = path.stat()
|
|
336
334
|
except OSError:
|
|
337
335
|
return records
|
|
338
336
|
|
|
339
|
-
|
|
340
|
-
self.
|
|
337
|
+
if st2.st_mtime_ns == st.st_mtime_ns and st2.st_size == st.st_size:
|
|
338
|
+
with self._cache_lock:
|
|
339
|
+
self._read_cache[key] = (st.st_mtime_ns, st.st_size, records)
|
|
341
340
|
|
|
342
341
|
return [dict(r) for r in records]
|
|
@@ -5,6 +5,9 @@ Bug 1: TrustEngine state silently reset on process restart (facade used the
|
|
|
5
5
|
Bug 2: Lost updates under concurrency -- resolve_error/reinforce/decay_unused
|
|
6
6
|
did read -> atomic_rewrite outside a shared lock, so records appended
|
|
7
7
|
by other threads between the read and the rewrite were wiped.
|
|
8
|
+
Bug 3: read_all cached a pre-append snapshot under the post-append stat when
|
|
9
|
+
an append landed mid-parse, poisoning the cache; atomic_update then
|
|
10
|
+
silently deleted the concurrent record.
|
|
8
11
|
"""
|
|
9
12
|
|
|
10
13
|
from __future__ import annotations
|
|
@@ -112,3 +115,56 @@ class TestNoLostUpdatesUnderConcurrency:
|
|
|
112
115
|
]
|
|
113
116
|
seed_row = next(r for r in rows if r["id"] == seed_id)
|
|
114
117
|
assert seed_row["applied_count"] == n_new
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class TestReadCacheNotPoisonedByMidParseAppend:
|
|
121
|
+
"""Bug 3: read_all cached pre-append records under the post-append stat.
|
|
122
|
+
|
|
123
|
+
An append landing between the parse loop and the re-stat made read_all
|
|
124
|
+
store stale records keyed by the fresh mtime/size; every later read
|
|
125
|
+
returned stale data and atomic_update silently deleted the concurrent
|
|
126
|
+
record. Fixed by caching only when pre-parse stat == post-parse stat.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
def test_append_during_parse_does_not_poison_cache(
|
|
130
|
+
self, data_dir: Path, monkeypatch: object
|
|
131
|
+
) -> None:
|
|
132
|
+
import io as _io
|
|
133
|
+
|
|
134
|
+
import errlore.io.jsonl_writer as jw
|
|
135
|
+
|
|
136
|
+
writer = jw.JSONLWriter()
|
|
137
|
+
path = data_dir / "records.jsonl"
|
|
138
|
+
writer.append(path, {"id": 1})
|
|
139
|
+
|
|
140
|
+
real_open = open
|
|
141
|
+
raced = {"done": False}
|
|
142
|
+
|
|
143
|
+
def racing_open(file: object, mode: str = "r", *args: object, **kwargs: object): # type: ignore[no-untyped-def]
|
|
144
|
+
# Intercept only read_all's read of our file, exactly once:
|
|
145
|
+
# snapshot the current content, append a record behind the
|
|
146
|
+
# parser's back, then hand the parser the stale snapshot.
|
|
147
|
+
if str(file) == str(path) and mode == "r" and not raced["done"]:
|
|
148
|
+
raced["done"] = True
|
|
149
|
+
content = path.read_text(encoding="utf-8")
|
|
150
|
+
with real_open(path, "a", encoding="utf-8") as af:
|
|
151
|
+
af.write(json.dumps({"id": 2}) + "\n")
|
|
152
|
+
return _io.StringIO(content)
|
|
153
|
+
return real_open(file, mode, *args, **kwargs) # type: ignore[arg-type]
|
|
154
|
+
|
|
155
|
+
monkeypatch.setattr(jw, "open", racing_open, raising=False) # type: ignore[attr-defined]
|
|
156
|
+
|
|
157
|
+
# The racy read itself legitimately sees the pre-append snapshot...
|
|
158
|
+
first = writer.read_all(path)
|
|
159
|
+
assert [r["id"] for r in first] == [1]
|
|
160
|
+
|
|
161
|
+
# ...but it must NOT have been cached: the next read sees both.
|
|
162
|
+
second = writer.read_all(path)
|
|
163
|
+
assert [r["id"] for r in second] == [1, 2], (
|
|
164
|
+
"read cache was poisoned by a mid-parse append"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# And atomic_update must not delete the concurrently appended record.
|
|
168
|
+
result = writer.atomic_update(path, lambda entries: entries)
|
|
169
|
+
assert result is not None
|
|
170
|
+
assert [r["id"] for r in result] == [1, 2]
|
|
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
|