groundmemory 0.3.2__tar.gz → 0.3.4__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.
- {groundmemory-0.3.2 → groundmemory-0.3.4}/.github/workflows/pypi-publish.yml +1 -1
- {groundmemory-0.3.2 → groundmemory-0.3.4}/PKG-INFO +3 -3
- {groundmemory-0.3.2 → groundmemory-0.3.4}/README.md +2 -2
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/bootstrap/injector.py +18 -6
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/storage.py +8 -4
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/workspace.py +38 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/base.py +17 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_write.py +3 -1
- {groundmemory-0.3.2 → groundmemory-0.3.4}/pyproject.toml +1 -1
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_replace.py +6 -3
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_write.py +46 -1
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_session.py +2 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/.dockerignore +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/.github/workflows/unit-tests.yml +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/.gitignore +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/.python-version +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/DOCS.md +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/Dockerfile +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/LICENSE +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/_assets/icon.png +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/docker-compose.yml +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/examples/anthropic_agent.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/examples/openai_agent.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/__init__.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/adapters/__init__.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/adapters/anthropic.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/adapters/openai.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/bootstrap/__init__.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/bootstrap/compaction.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/config/.env.example +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/config/__init__.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/config/groundmemory.yaml.example +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/__init__.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/chunker.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/embeddings.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/index.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/relations.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/search.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/core/sync.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/mcp_server.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/session.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/__init__.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_bootstrap.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_delete.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_dispatcher.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_get.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_list.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_read.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_relate.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_replace.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/groundmemory/tools/memory_search.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/__init__.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/conftest.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_embeddings_integration.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_mcp_server.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_delete.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_get.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_list.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_read.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_relate.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_memory_search.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_relations_sync.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/tests/test_workspace_security.py +0 -0
- {groundmemory-0.3.2 → groundmemory-0.3.4}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: groundmemory
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Persistent, semantic memory for AI agents - mcp-native, local-first, framework-agnostic, production-ready
|
|
5
5
|
Project-URL: Documentation, https://github.com/huss-mo/GroundMemory/blob/master/DOCS.md
|
|
6
6
|
Project-URL: Issues, https://github.com/huss-mo/GroundMemory/issues
|
|
@@ -31,9 +31,9 @@ Description-Content-Type: text/markdown
|
|
|
31
31
|
**Persistent, semantic memory for AI agents - mcp-native, local-first, framework-agnostic, production-ready.**
|
|
32
32
|
|
|
33
33
|
[](https://www.python.org/downloads/)
|
|
34
|
-
[](https://github.com/huss-mo/GroundMemory/actions/workflows/pypi-publish.yml)
|
|
35
35
|
[](https://github.com/huss-mo/GroundMemory/actions/workflows/unit-tests.yml)
|
|
36
|
-
[](#running-the-test-suite)
|
|
37
37
|
[](LICENSE)
|
|
38
38
|

|
|
39
39
|

|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
**Persistent, semantic memory for AI agents - mcp-native, local-first, framework-agnostic, production-ready.**
|
|
6
6
|
|
|
7
7
|
[](https://www.python.org/downloads/)
|
|
8
|
-
[](https://github.com/huss-mo/GroundMemory/actions/workflows/pypi-publish.yml)
|
|
9
9
|
[](https://github.com/huss-mo/GroundMemory/actions/workflows/unit-tests.yml)
|
|
10
|
-
[](#running-the-test-suite)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|

|
|
13
13
|

|
|
@@ -44,8 +44,8 @@ def _read_capped(path: Path, max_chars: int) -> tuple[str, bool]:
|
|
|
44
44
|
def _section(title: str, body: str, truncated: bool = False, source: str = "") -> str:
|
|
45
45
|
"""Wrap *body* in a labelled Markdown block."""
|
|
46
46
|
marker = " [TRUNCATED - use memory_get to read the rest]" if truncated else ""
|
|
47
|
-
|
|
48
|
-
return f"### {title}{marker}
|
|
47
|
+
source_block = f" ({source})" if source else ""
|
|
48
|
+
return f"### {title}{marker}{source_block}\n\n{body}\n"
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
# ---------------------------------------------------------------------------
|
|
@@ -107,12 +107,20 @@ def build_bootstrap_prompt(
|
|
|
107
107
|
total_chars += len(body_text)
|
|
108
108
|
return total_chars < cfg.max_total_chars
|
|
109
109
|
|
|
110
|
-
#
|
|
111
|
-
|
|
110
|
+
# Check whether first-run onboarding is active (file exists and is non-empty).
|
|
111
|
+
# When active, skip MEMORY.md and USER.md - they are empty/default and would
|
|
112
|
+
# only confuse the model during onboarding.
|
|
113
|
+
first_run_active = (
|
|
114
|
+
workspace.first_run_file.exists()
|
|
115
|
+
and workspace.first_run_file.read_text(encoding="utf-8").strip() != ""
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# 1. Long-term memory (MEMORY.md) - skipped during first run
|
|
119
|
+
if cfg.inject_long_term_memory and not first_run_active:
|
|
112
120
|
_add("Long-Term Memory", workspace.memory_file)
|
|
113
121
|
|
|
114
|
-
# 2. User profile (USER.md)
|
|
115
|
-
if cfg.inject_user_profile:
|
|
122
|
+
# 2. User profile (USER.md) - skipped during first run
|
|
123
|
+
if cfg.inject_user_profile and not first_run_active:
|
|
116
124
|
_add("User Profile", workspace.user_file)
|
|
117
125
|
|
|
118
126
|
# 3. Agent roster (AGENTS.md)
|
|
@@ -139,6 +147,10 @@ def build_bootstrap_prompt(
|
|
|
139
147
|
if not _add(label, day_path, source=f"daily/{day_path.name}"):
|
|
140
148
|
break # budget exhausted
|
|
141
149
|
|
|
150
|
+
# 6. First-run onboarding (FIRST_RUN.md) - injected last; skipped automatically once emptied
|
|
151
|
+
if first_run_active:
|
|
152
|
+
_add("First Run", workspace.first_run_file)
|
|
153
|
+
|
|
142
154
|
if not sections:
|
|
143
155
|
return ""
|
|
144
156
|
|
|
@@ -218,8 +218,10 @@ def hard_delete_lines(path: Path, start_line: int, end_line: int) -> dict:
|
|
|
218
218
|
|
|
219
219
|
if start_line < 1 or start_line > total:
|
|
220
220
|
return {"error": f"start_line {start_line} out of range (file has {total} lines)"}
|
|
221
|
-
if end_line < start_line
|
|
222
|
-
return {"error": f"end_line {end_line}
|
|
221
|
+
if end_line < start_line:
|
|
222
|
+
return {"error": f"end_line {end_line} must be >= start_line {start_line}"}
|
|
223
|
+
# Clamp end_line silently if it exceeds the file length
|
|
224
|
+
end_line = min(end_line, total)
|
|
223
225
|
|
|
224
226
|
# Convert to 0-indexed
|
|
225
227
|
s = start_line - 1
|
|
@@ -277,8 +279,10 @@ def replace_lines(path: Path, start_line: int, end_line: int, replacement: str)
|
|
|
277
279
|
|
|
278
280
|
if start_line < 1 or start_line > total:
|
|
279
281
|
return {"error": f"start_line {start_line} out of range (file has {total} lines)"}
|
|
280
|
-
if end_line < start_line
|
|
281
|
-
return {"error": f"end_line {end_line}
|
|
282
|
+
if end_line < start_line:
|
|
283
|
+
return {"error": f"end_line {end_line} must be >= start_line {start_line}"}
|
|
284
|
+
# Clamp end_line silently if it exceeds the file length
|
|
285
|
+
end_line = min(end_line, total)
|
|
282
286
|
|
|
283
287
|
# Convert to 0-indexed
|
|
284
288
|
s = start_line - 1
|
|
@@ -144,6 +144,39 @@ Every non-blank, non-comment line must follow this exact format:
|
|
|
144
144
|
Lines that do not match will be rejected by the write tools.
|
|
145
145
|
"""
|
|
146
146
|
|
|
147
|
+
_DEFAULT_FIRST_RUN_MD = """\
|
|
148
|
+
# First Run
|
|
149
|
+
|
|
150
|
+
If you are seeing this section, then this is the user's very first session
|
|
151
|
+
with GroundMemory. Your memory is completely empty. You are meeting them for
|
|
152
|
+
the first time - make it feel like the start of something, not a setup wizard.
|
|
153
|
+
|
|
154
|
+
What to do:
|
|
155
|
+
|
|
156
|
+
1. Open warmly and explain what GroundMemory means for them. Something like:
|
|
157
|
+
|
|
158
|
+
"Hey! Welcome - this is the start of something good. I have persistent memory,
|
|
159
|
+
so anything we talk about I'll carry with me into every future session. No need
|
|
160
|
+
to repeat yourself down the line."
|
|
161
|
+
|
|
162
|
+
Use your own words. Don't copy this exactly.
|
|
163
|
+
|
|
164
|
+
2. In the same message, ask your questions - each on its own line, not crammed
|
|
165
|
+
together. Keep it light:
|
|
166
|
+
|
|
167
|
+
- What should you call them? (And invite them to give you a name too, if they want.)
|
|
168
|
+
- Is there anything they always want you to keep in mind?
|
|
169
|
+
|
|
170
|
+
End with something relaxed, like: "No rush - just getting to know you."
|
|
171
|
+
|
|
172
|
+
3. Once they have answered, save what you learned:
|
|
173
|
+
- Name, preferences, working style -> USER.md
|
|
174
|
+
- How they want you to behave -> AGENTS.md
|
|
175
|
+
- You can also use the other memory file or tools (Ex. memory_relate) as you see fit
|
|
176
|
+
|
|
177
|
+
Do not mention this file to the user. Just have the conversation.
|
|
178
|
+
"""
|
|
179
|
+
|
|
147
180
|
_DEFAULT_RELATIONS_MD = """\
|
|
148
181
|
# Relations
|
|
149
182
|
|
|
@@ -210,6 +243,10 @@ class Workspace:
|
|
|
210
243
|
def relations_file(self) -> Path:
|
|
211
244
|
return self.path / "RELATIONS.md"
|
|
212
245
|
|
|
246
|
+
@property
|
|
247
|
+
def first_run_file(self) -> Path:
|
|
248
|
+
return self.path / "FIRST_RUN.md"
|
|
249
|
+
|
|
213
250
|
def daily_file(self, day: date | None = None) -> Path:
|
|
214
251
|
"""Return the path for the daily log of *day* (defaults to today)."""
|
|
215
252
|
d = day or date.today()
|
|
@@ -229,6 +266,7 @@ class Workspace:
|
|
|
229
266
|
self._seed(self.user_file, _DEFAULT_USER_MD)
|
|
230
267
|
self._seed(self.agents_file, _DEFAULT_AGENTS_MD)
|
|
231
268
|
self._seed(self.relations_file, _DEFAULT_RELATIONS_MD)
|
|
269
|
+
self._seed(self.first_run_file, _DEFAULT_FIRST_RUN_MD)
|
|
232
270
|
|
|
233
271
|
@staticmethod
|
|
234
272
|
def _seed(path: Path, content: str) -> None:
|
|
@@ -44,6 +44,22 @@ _IMMUTABLE_MSG = (
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
|
|
47
|
+
def _auto_clear_first_run(session) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Silently empty FIRST_RUN.md after the first successful write operation.
|
|
50
|
+
|
|
51
|
+
This marks onboarding as complete without requiring the model to do it
|
|
52
|
+
explicitly. Safe to call on every write - does nothing once the file
|
|
53
|
+
is already empty.
|
|
54
|
+
"""
|
|
55
|
+
try:
|
|
56
|
+
fr = session.workspace.first_run_file
|
|
57
|
+
if fr.exists() and fr.read_text(encoding="utf-8").strip():
|
|
58
|
+
fr.write_text("", encoding="utf-8")
|
|
59
|
+
except Exception: # noqa: BLE001
|
|
60
|
+
pass # Never let this interfere with the actual write result
|
|
61
|
+
|
|
62
|
+
|
|
47
63
|
def sync_after_edit(
|
|
48
64
|
session,
|
|
49
65
|
resolved: Path,
|
|
@@ -80,4 +96,5 @@ def sync_after_edit(
|
|
|
80
96
|
base_payload["format_reminder"] = RELATIONS_FORMAT_REMINDER
|
|
81
97
|
if relation_sync_result:
|
|
82
98
|
base_payload["relations_synced"] = relation_sync_result
|
|
99
|
+
_auto_clear_first_run(session)
|
|
83
100
|
return ok(base_payload)
|
|
@@ -276,4 +276,6 @@ def run(
|
|
|
276
276
|
sync.sync_file(daily_path, session.index, session.provider, session.config.chunking)
|
|
277
277
|
|
|
278
278
|
result["mode"] = "append"
|
|
279
|
-
|
|
279
|
+
from groundmemory.tools.base import _auto_clear_first_run
|
|
280
|
+
_auto_clear_first_run(session)
|
|
281
|
+
return ok(result)
|
|
@@ -435,16 +435,19 @@ class TestReplaceLines:
|
|
|
435
435
|
)
|
|
436
436
|
assert r["status"] == "error"
|
|
437
437
|
|
|
438
|
-
def
|
|
438
|
+
def test_replace_lines_end_line_beyond_file_clamped(self, session):
|
|
439
|
+
"""end_line beyond file length is silently clamped to the last line."""
|
|
439
440
|
_write_user(session, "a\nb\n")
|
|
440
441
|
r = session.execute_tool(
|
|
441
442
|
"memory_write",
|
|
442
443
|
file="USER.md",
|
|
443
444
|
start_line=1,
|
|
444
445
|
end_line=5,
|
|
445
|
-
content="
|
|
446
|
+
content="CLAMPED",
|
|
446
447
|
)
|
|
447
|
-
assert r["status"] == "
|
|
448
|
+
assert r["status"] == "ok"
|
|
449
|
+
content = _get(session, "USER.md")
|
|
450
|
+
assert "CLAMPED" in content
|
|
448
451
|
|
|
449
452
|
|
|
450
453
|
class TestReplaceLinesImmutable:
|
|
@@ -313,4 +313,49 @@ class TestMemoryWriteDelete:
|
|
|
313
313
|
content = _read(session, "AGENTS.md")
|
|
314
314
|
assert "Rule B." not in content
|
|
315
315
|
assert "Rule A." in content
|
|
316
|
-
assert "Rule C." in content
|
|
316
|
+
assert "Rule C." in content
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
# ===========================================================================
|
|
320
|
+
# FIRST_RUN.md auto-clear
|
|
321
|
+
# ===========================================================================
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class TestFirstRunAutoCleared:
|
|
325
|
+
"""FIRST_RUN.md must be emptied automatically on the first successful write."""
|
|
326
|
+
|
|
327
|
+
def test_first_run_cleared_after_append(self, session):
|
|
328
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() != ""
|
|
329
|
+
session.execute_tool("memory_write", file="USER.md", content="Name is Alice.")
|
|
330
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() == ""
|
|
331
|
+
|
|
332
|
+
def test_first_run_cleared_after_replace_text(self, session):
|
|
333
|
+
_write_user(session, "old name\n")
|
|
334
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() != ""
|
|
335
|
+
session.execute_tool("memory_write", file="USER.md", search="old name", content="new name")
|
|
336
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() == ""
|
|
337
|
+
|
|
338
|
+
def test_first_run_cleared_after_replace_lines(self, session):
|
|
339
|
+
_write_user(session, "line one\nline two\n")
|
|
340
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() != ""
|
|
341
|
+
session.execute_tool("memory_write", file="USER.md", start_line=1, end_line=1, content="replaced")
|
|
342
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() == ""
|
|
343
|
+
|
|
344
|
+
def test_first_run_cleared_after_delete(self, session):
|
|
345
|
+
_write_user(session, "line one\nline two\n")
|
|
346
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() != ""
|
|
347
|
+
session.execute_tool("memory_write", file="USER.md", start_line=1, end_line=1, content="")
|
|
348
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() == ""
|
|
349
|
+
|
|
350
|
+
def test_first_run_already_empty_stays_empty(self, session):
|
|
351
|
+
session.workspace.first_run_file.write_text("", encoding="utf-8")
|
|
352
|
+
session.execute_tool("memory_write", file="USER.md", content="Another entry.")
|
|
353
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() == ""
|
|
354
|
+
|
|
355
|
+
def test_failed_write_does_not_clear_first_run(self, session):
|
|
356
|
+
"""A failed write must not trigger the auto-clear."""
|
|
357
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() != ""
|
|
358
|
+
# This write will fail (empty content)
|
|
359
|
+
r = session.execute_tool("memory_write", file="USER.md", content="")
|
|
360
|
+
assert r["status"] == "error"
|
|
361
|
+
assert session.workspace.first_run_file.read_text(encoding="utf-8").strip() != ""
|
|
@@ -121,6 +121,8 @@ class TestSessionBootstrap:
|
|
|
121
121
|
# An empty workspace should produce minimal/empty bootstrap
|
|
122
122
|
|
|
123
123
|
def test_bootstrap_includes_long_term_memory(self, session):
|
|
124
|
+
# Simulate post-onboarding: FIRST_RUN.md must be empty so MEMORY.md is injected
|
|
125
|
+
session.workspace.first_run_file.write_text("", encoding="utf-8")
|
|
124
126
|
content = "Bootstrap long term fact."
|
|
125
127
|
session.execute_tool("memory_write", file="MEMORY.md", content=content)
|
|
126
128
|
result = session.bootstrap()
|
|
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
|