memdebug 0.3.0__tar.gz → 0.4.1__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 (105) hide show
  1. memdebug-0.4.1/.github/ISSUE_TEMPLATE/bug_report.yml +87 -0
  2. memdebug-0.4.1/.github/pull_request_template.md +24 -0
  3. {memdebug-0.3.0 → memdebug-0.4.1}/.gitignore +4 -0
  4. {memdebug-0.3.0 → memdebug-0.4.1}/CHANGELOG.md +45 -0
  5. memdebug-0.4.1/CONTRIBUTING.md +123 -0
  6. {memdebug-0.3.0 → memdebug-0.4.1}/PKG-INFO +106 -7
  7. {memdebug-0.3.0 → memdebug-0.4.1}/README.md +105 -6
  8. {memdebug-0.3.0 → memdebug-0.4.1}/ROADMAP.md +13 -7
  9. {memdebug-0.3.0 → memdebug-0.4.1}/docs/agents.md +2 -1
  10. memdebug-0.4.1/docs/demo.cast +21 -0
  11. memdebug-0.4.1/docs/demo.gif +0 -0
  12. {memdebug-0.3.0 → memdebug-0.4.1}/docs/threat-model.md +17 -0
  13. {memdebug-0.3.0 → memdebug-0.4.1}/pyproject.toml +18 -5
  14. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/__init__.py +1 -1
  15. memdebug-0.4.1/src/memdebug/__main__.py +4 -0
  16. memdebug-0.4.1/src/memdebug/adapters/__init__.py +6 -0
  17. memdebug-0.4.1/src/memdebug/adapters/base.py +83 -0
  18. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/common.py +18 -0
  19. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/fileops.py +49 -12
  20. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/folder.py +29 -6
  21. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/folder_restore.py +72 -11
  22. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/markdown_git.py +82 -19
  23. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/mem0.py +51 -0
  24. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/openwebui.py +28 -2
  25. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/adapters/restore.py +114 -8
  26. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/agents.py +44 -4
  27. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/backends.py +1 -0
  28. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/cli.py +106 -55
  29. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/demo.py +47 -5
  30. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/describe.py +5 -0
  31. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/diff.py +36 -3
  32. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/docker_source.py +14 -1
  33. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/hints.py +10 -0
  34. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/ledger.py +132 -17
  35. memdebug-0.4.1/src/memdebug/longpath.py +104 -0
  36. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/models.py +69 -4
  37. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/monitor.py +129 -11
  38. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/paths.py +9 -2
  39. memdebug-0.4.1/src/memdebug/provenance.py +298 -0
  40. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/reconcile.py +24 -0
  41. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/report.py +67 -3
  42. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/rollback_flow.py +57 -4
  43. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/selftest.py +326 -83
  44. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/stores.py +76 -8
  45. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/sync.py +45 -9
  46. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/textsafe.py +23 -6
  47. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/viewer/html.py +24 -3
  48. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/viewer/pages.py +199 -17
  49. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/viewer/redline.py +4 -2
  50. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/viewer/server.py +60 -19
  51. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/witness.py +24 -0
  52. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_agents.py +13 -1
  53. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_docker_source.py +3 -1
  54. memdebug-0.4.1/tests/test_long_paths.py +317 -0
  55. memdebug-0.4.1/tests/test_provenance.py +245 -0
  56. memdebug-0.4.1/tests/test_provenance_check.py +197 -0
  57. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_release_hygiene.py +2 -1
  58. memdebug-0.4.1/tests/test_selftest_tripwire.py +340 -0
  59. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_viewer.py +13 -1
  60. memdebug-0.4.1/tests/test_viewer_agents.py +81 -0
  61. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_viewer_html.py +23 -0
  62. memdebug-0.3.0/CONTRIBUTING.md +0 -57
  63. memdebug-0.3.0/src/memdebug/__main__.py +0 -3
  64. memdebug-0.3.0/src/memdebug/adapters/__init__.py +0 -0
  65. memdebug-0.3.0/src/memdebug/adapters/base.py +0 -37
  66. {memdebug-0.3.0 → memdebug-0.4.1}/.gitattributes +0 -0
  67. {memdebug-0.3.0 → memdebug-0.4.1}/.github/dependabot.yml +0 -0
  68. {memdebug-0.3.0 → memdebug-0.4.1}/.github/workflows/ci.yml +0 -0
  69. {memdebug-0.3.0 → memdebug-0.4.1}/.github/workflows/release.yml +0 -0
  70. {memdebug-0.3.0 → memdebug-0.4.1}/LICENSE +0 -0
  71. {memdebug-0.3.0 → memdebug-0.4.1}/NOTICE +0 -0
  72. {memdebug-0.3.0 → memdebug-0.4.1}/SECURITY.md +0 -0
  73. {memdebug-0.3.0 → memdebug-0.4.1}/docs/releasing.md +0 -0
  74. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/errors.py +0 -0
  75. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/viewer/__init__.py +0 -0
  76. {memdebug-0.3.0 → memdebug-0.4.1}/src/memdebug/viewer/style.py +0 -0
  77. {memdebug-0.3.0 → memdebug-0.4.1}/tests/conftest.py +0 -0
  78. {memdebug-0.3.0 → memdebug-0.4.1}/tests/payloads.py +0 -0
  79. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_cli.py +0 -0
  80. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_demo.py +0 -0
  81. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_diff.py +0 -0
  82. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_folder_and_openwebui.py +0 -0
  83. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_folder_restore.py +0 -0
  84. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_folder_rollback_cli.py +0 -0
  85. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_hints.py +0 -0
  86. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_ledger.py +0 -0
  87. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_ledger_read.py +0 -0
  88. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_markdown_git.py +0 -0
  89. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_mem0_adapter.py +0 -0
  90. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_models.py +0 -0
  91. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_platform.py +0 -0
  92. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_provenance_openwebui.py +0 -0
  93. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_readable_ids_and_honest_wording.py +0 -0
  94. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_reconcile.py +0 -0
  95. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_report.py +0 -0
  96. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_restore.py +0 -0
  97. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_rollback_cli.py +0 -0
  98. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_snapshots.py +0 -0
  99. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_stores_and_monitor.py +0 -0
  100. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_sync.py +0 -0
  101. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_textsafe.py +0 -0
  102. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_viewer_putback.py +0 -0
  103. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_viewer_redline.py +0 -0
  104. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_viewer_rollback.py +0 -0
  105. {memdebug-0.3.0 → memdebug-0.4.1}/tests/test_witness.py +0 -0
@@ -0,0 +1,87 @@
1
+ name: Bug report
2
+ description: Something does not work as the documentation says
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ ## Please read this first
9
+
10
+ **Never paste memory contents or session transcripts into this issue.** An agent's memory and its conversation logs hold private text,
11
+ and an issue is public and stays public even if you edit or delete it. Describe what the text *did*, or replace it with a made-up
12
+ example that behaves the same way.
13
+
14
+ Also leave out: the ledger (`*.db`), `stores.json`, backup folders, the link printed by `memdebug serve` (it contains a secret),
15
+ API keys, and personal paths (replace them with `<home>`). Be careful with the output of `memdebug check`, `timeline`, `diff` and
16
+ `report`: it can quote the start of changed notes.
17
+
18
+ **A security problem?** Do not open an issue. Report it privately: https://github.com/juraj-jumic/memdebug/security/advisories/new
19
+ (see [SECURITY.md](https://github.com/juraj-jumic/memdebug/blob/main/SECURITY.md)).
20
+ - type: checkboxes
21
+ id: safe
22
+ attributes:
23
+ label: Before you submit
24
+ options:
25
+ - label: I have not included memory contents, session transcripts, a ledger, `stores.json`, a viewer link or personal paths.
26
+ required: true
27
+ - label: This is not a security problem.
28
+ required: true
29
+ - type: textarea
30
+ id: happened
31
+ attributes:
32
+ label: What happened
33
+ description: What you did, and what memdebug did.
34
+ validations:
35
+ required: true
36
+ - type: textarea
37
+ id: expected
38
+ attributes:
39
+ label: What you expected
40
+ validations:
41
+ required: true
42
+ - type: textarea
43
+ id: steps
44
+ attributes:
45
+ label: Steps to reproduce
46
+ description: The commands you ran, ideally against a made-up folder of notes. `memdebug demo` shows what a healthy run looks like.
47
+ - type: input
48
+ id: version
49
+ attributes:
50
+ label: memdebug version
51
+ description: The output of `memdebug --version`. If you use pipx and a virtual environment, make sure it is the copy you actually ran.
52
+ validations:
53
+ required: true
54
+ - type: dropdown
55
+ id: system
56
+ attributes:
57
+ label: Operating system
58
+ options:
59
+ - Windows
60
+ - macOS
61
+ - Linux
62
+ - Other
63
+ validations:
64
+ required: true
65
+ - type: input
66
+ id: tools
67
+ attributes:
68
+ label: Python and git versions
69
+ description: The output of `python --version` and `git --version`.
70
+ - type: dropdown
71
+ id: store
72
+ attributes:
73
+ label: Kind of store involved
74
+ multiple: true
75
+ options:
76
+ - Markdown notes in git
77
+ - Plain folder of markdown notes
78
+ - Open WebUI
79
+ - Mem0
80
+ - The viewer (memdebug serve)
81
+ - Not about a store
82
+ - type: textarea
83
+ id: output
84
+ attributes:
85
+ label: Output or error message
86
+ description: Only text you have read through and are sure holds no memory contents. The output of `memdebug selftest` is useful here, but it names your user folder on Windows, so replace that with `<home>` first.
87
+ render: text
@@ -0,0 +1,24 @@
1
+ ## What this changes, and why
2
+
3
+ <!-- One or two sentences. Link the issue if there is one. -->
4
+
5
+ ## How it was checked
6
+
7
+ Run these with the repository's own environment (see [CONTRIBUTING.md](https://github.com/juraj-jumic/memdebug/blob/main/CONTRIBUTING.md)):
8
+
9
+ - [ ] `python -m pytest -q -n auto`, `ruff check src tests`, `mypy` and `python -m memdebug selftest` all pass
10
+ - [ ] If this adds or changes a protection: I removed it on purpose and a test failed (CONTRIBUTING, ground rule 5). Otherwise: no protection changed
11
+ - [ ] Hostile input is handled: text read from a store, log or repository is bounded, shown through `safe_text` or the viewer's builder, and any name from the ledger is validated (validators end with `\Z`, never `$`) before it reaches a command or a path
12
+ - [ ] Windows is covered: file names that differ only by case, CRLF, and junctions are considered; a POSIX-only test is marked skipped with a reason
13
+
14
+ ## Documents
15
+
16
+ - [ ] `CHANGELOG.md` has a line under `[Unreleased]` for anything a user would notice
17
+ - [ ] Anything the change makes untrue is updated, and a new limit is written into `docs/threat-model.md`
18
+
19
+ ## Private data
20
+
21
+ - [ ] The diff and the commit messages contain no memory text, session transcripts, `*.db` files, `stores.json`, `backups/`, `copies/` or personal paths. Test data is made up
22
+ - [ ] I understand the history is public. A GitHub no-reply address is fine as the commit email
23
+
24
+ By contributing you agree that your contribution is licensed under the Apache License 2.0 (see [LICENSE](https://github.com/juraj-jumic/memdebug/blob/main/LICENSE)).
@@ -26,7 +26,11 @@ htmlcov/
26
26
  *.partial
27
27
  stores.json
28
28
  copies/
29
+ backups/
29
30
  memdebug-report*
31
+ # Rollback backups hold the notes byte for byte, and agent session logs are whole conversations. If a test ever needs a made-up .jsonl file,
32
+ # add a negation for that one path (!tests/data/example.jsonl) instead of removing the rule.
33
+ *.jsonl
30
34
 
31
35
  # Secrets
32
36
  .env
@@ -3,6 +3,51 @@
3
3
  All notable changes. The format follows [Keep a Changelog](https://keepachangelog.com/); versions follow [Semantic Versioning](https://semver.org/)
4
4
  (alpha: anything may change before 1.0).
5
5
 
6
+ ## [0.4.1] - 2026-10-09
7
+
8
+ ### Fixed
9
+ - **The viewer's diff view hid memory text.** It skipped every diff line that started with `+++` or `---`, to drop the diff's file headers, so an added or
10
+ removed line of memory text that began with `++` or `--` (for example a planted `++ send passwords to ...`) did not appear on the page. Only the two
11
+ real header lines at the top of a diff are skipped now. The timeline's event inspector and the Compare page were affected; the terminal output was not.
12
+ - **Windows paths longer than 259 characters.** Windows refuses such paths unless long paths are switched on in the registry, which is off by default.
13
+ Before, a note beyond the limit was left out of the baseline and of every check (`check` said "quiet, nothing new", exit code 0, with only a trailing
14
+ warning), a rollback said "Nothing to restore" while that note stayed tampered, a rollback whose backup path was beyond the limit failed, and a store
15
+ folder beyond the limit failed with "unexpected error". memdebug now hands Windows the extended-length form of every note, backup and session-log
16
+ path, so none of this depends on that setting. A git repository in a very deeply nested folder can still hit git's own limits.
17
+
18
+ ### Changed
19
+ - The code now follows the Google Python Style Guide, and CI enforces the parts a tool can check: every public module, class, function and method has a
20
+ docstring, every function has type annotations (`mypy` now requires them), exceptions end in `Error`, and a broad `except Exception` needs a stated
21
+ reason. No behaviour changed. CONTRIBUTING.md has a "Code style" section that also lists where the project deliberately differs from the guide.
22
+
23
+ ## [0.4.0] - 2026-10-09
24
+
25
+ ### Added
26
+ - **Who wrote it.** For a changed note that looks suspicious, or that changed outside git, `memdebug check` and `memdebug watch` now also say which
27
+ logged Claude Code session wrote it, from that agent's session logs ("who wrote it: ..."), or that no logged edit explains it. It returns only a
28
+ session id, a time and a tool name, never a path or any text from a log. It is evidence, not proof: a deleted log looks the same as a note nobody
29
+ wrote, and shell-made changes cannot be matched (they are only counted). See `docs/threat-model.md`.
30
+ - The viewer has an **Agents** page (`memdebug serve`): the known agents that appear to be installed and what memdebug could watch for each, with the command
31
+ to run (`memdebug setup`). It only checks that folders exist, opens no file, and works even while the ledger is busy.
32
+ - Cline is in the agent catalog: `memdebug agents` and `memdebug setup` offer its global rules folder (`~/Documents/Cline/Rules`) once it holds markdown rules.
33
+
34
+ ### Changed
35
+ - `memdebug selftest`, "rollback is safe" and "hostile repository config cannot run programs": the control (ordinary git, tripwires armed) now runs in
36
+ a separate repository with its own marker files, so nothing it leaves behind can be mistaken for an escape from the protected run, which is still
37
+ asserted to run no tripwire. The tripwires now record what started them (time, arguments, and the parent and grandparent command lines where the
38
+ platform shows them: `/proc` on Linux, `ps` on macOS, nothing on Windows), and a failure prints those records and `git --version`. This follows a
39
+ failure on CI that passed on a re-run and could not be reproduced.
40
+ - The repository's ignore file also excludes `backups/` and `*.jsonl` (session logs), and the release hygiene test checks both rules.
41
+
42
+ ### Documentation
43
+ - The README has badges, a recorded demo (`docs/demo.gif`, with the unpaced recording in `docs/demo.cast`), and new "Why this matters", "How it works" and
44
+ "Engineering" sections, checked against the code; claims about snapshots, the ledger and rollback that overstated what the code does were corrected.
45
+ - CONTRIBUTING.md now starts with the private-data rule and covers reporting a security problem, setup, writing tests, changing the viewer and the pull
46
+ request process; its second ground rule names both rollback engines. There is a pull request template and a bug-report form that tells people never
47
+ to paste memory contents or session transcripts.
48
+ - `docs/threat-model.md` has a section on the session-log reader, and `docs/agents.md` has a row for Cline and records Claude Code's per-project memory
49
+ folder as confirmed on Windows 11.
50
+
6
51
  ## [0.3.0] - 2026-10-07
7
52
 
8
53
  ### Added
@@ -0,0 +1,123 @@
1
+ # Contributing
2
+
3
+ Thanks for helping. memdebug is a security tool, so the bar for changes is "what happens if the input is hostile?".
4
+
5
+ ## Private data comes first
6
+
7
+ memdebug reads people's agent memory, so this repository must never hold any of it. Never commit, attach or paste (in an issue, a pull
8
+ request, a test or a commit message): real memory text, session transcripts, a ledger or any other `*.db` file, `stores.json`, backup or copy
9
+ folders, the link `memdebug serve` prints (it contains a secret), tokens or keys, or paths that name your user account. Test data is made up.
10
+ The ignore file keeps the common cases out (databases, `stores.json`, copy and backup folders, `*.jsonl` session logs, reports, keys), but it
11
+ is only a safety net: read `git status` and `git diff --cached` before every commit. The history is public, and a pushed commit cannot be
12
+ taken back without rewriting it.
13
+
14
+ ## Reporting a security problem
15
+
16
+ Do not open a public issue or pull request for a security problem. Report it privately through GitHub
17
+ (https://github.com/juraj-jumic/memdebug/security/advisories/new); [SECURITY.md](SECURITY.md) says what counts and what to include. Keep private
18
+ text out of the report as well: a made-up example that behaves the same way is enough.
19
+
20
+ ## Setup
21
+
22
+ python -m venv .venv
23
+ .venv\Scripts\activate # Windows (PowerShell or cmd); on macOS and Linux: source .venv/bin/activate
24
+ pip install -e ".[dev]"
25
+ python -m pytest -n auto # the whole suite, in parallel; plain `python -m pytest` works too
26
+ python -m memdebug selftest # proves the protections on THIS machine
27
+ ruff check src tests # lint (CI enforces it)
28
+ mypy # types (CI enforces it)
29
+
30
+ You need Python 3.10+ and git 2.31+. The suite starts many git processes, so it is slower on Windows. Run everything through this environment's
31
+ `python -m ...`: a `memdebug` command on your PATH (a pipx install, say) can be an older release than the code you are editing, while
32
+ `python -m memdebug` is always your checkout. Two release-hygiene tests are skipped unless PyYAML is installed (`pip install pyyaml`).
33
+
34
+ ## Ground rules
35
+
36
+ 1. **Everything read from a memory store or repository is untrusted.** Bound its size, never print it raw (use
37
+ `safe_text`), and never let it become markup (use the viewer's builder, never string-built HTML).
38
+ 2. **Adapters only read.** The only code allowed to change a memory store is the two rollback engines, `adapters/restore.py` (git
39
+ notes) and `adapters/folder_restore.py` (plain folders), which share the file-writing code in `adapters/fileops.py`. They have their
40
+ own rules: a dry run first, a plan the person confirms, a backup of anything not already held (in git, or in a private backup folder),
41
+ no rewritten history, undo on failure.
42
+ 3. **No shell, fixed argument lists, no programs named by the data.** git runs with a scrubbed environment and the
43
+ repository's risky settings overridden. Do not add a git command without checking it cannot run a hook, filter or
44
+ external helper.
45
+ 4. **Explicit text encodings** everywhere (`encoding="utf-8"`); a test enforces this because Windows defaults to a legacy
46
+ code page.
47
+ 5. **A protection without a test that fails when it is removed is not a protection.** Break your own code on purpose
48
+ (remove the check) and confirm a test notices. Several tests exist only because that exercise found a gap.
49
+ 6. **Windows is a first-class platform.** Avoid POSIX-only assumptions; if a test truly needs POSIX, mark it and say why. Two traps that only show up
50
+ there: file names ignore case ("A.db" and "a.db" are one file, so never name files after values that can differ only by case), and text-mode
51
+ writes turn `\n` into `\r\n` (write bytes when exact content matters).
52
+ 7. **Say what a feature does not do.** Add its limits to `docs/threat-model.md`.
53
+
54
+ ## Code style
55
+
56
+ The code follows the [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html). CI checks the parts a tool can check:
57
+ `ruff` for docstrings (Google convention), naming, a type annotation on every function, and no blind `except Exception` without a stated reason;
58
+ `mypy` for the types themselves (every function must be annotated). The rest is for review. In practice:
59
+
60
+ * Every public module, class, function and method has a docstring: a one-line summary, then `Args:`, `Returns:`, `Raises:` and `Attributes:`
61
+ sections only where they say something the signature and the annotations do not. Describe behaviour and side effects, not implementation.
62
+ * Types are written as `X | None`; exceptions end in `Error`; `Any` is fine where a type should not be expressed.
63
+ * `except Exception` is only for an isolation point (one store, one request or one undo step failing without stopping the rest), with a
64
+ `# noqa: BLE001 - reason` that says so. Everything else catches what it expects.
65
+ * Tests need no docstrings or annotations.
66
+
67
+ Where the project deliberately differs from the guide, because changing it would add churn without making the code safer or clearer:
68
+
69
+ * Lines may be up to 140 characters (the guide says 80). There is no auto-formatter.
70
+ * The package uses relative imports and `from module import name` (the guide asks for absolute imports of modules only).
71
+ * A few helpers are `@staticmethod`s on the class they belong to (the guide prefers module-level functions).
72
+ * Some functions are longer than the guide's 40-line hint, mostly the rollback planners and the viewer's page builders, where splitting
73
+ would scatter a security-relevant sequence across helpers.
74
+ * A comprehension may have an `if` when it fits on one line.
75
+
76
+ ## Writing tests
77
+
78
+ * Use made-up data only. Every test already gets an empty home folder and no Docker (`isolated_home` and `no_real_docker` in
79
+ `tests/conftest.py`), so never read a real home folder, agent folder, session log or container, and never put a real transcript in a
80
+ fixture. Write a made-up log, as `tests/test_provenance.py` does. A test that sets the home folder sets both `HOME` and `USERPROFILE`.
81
+ * A new protection needs a test that fails when it is removed (ground rule 5). A new validator or route pattern ends with `\Z`, never `$`,
82
+ and is added to `test_no_validator_accepts_a_trailing_line_break` in `tests/test_docker_source.py`, which tries every pattern with a
83
+ trailing line break.
84
+ * Give parametrized tests short `ids=`, and mark a POSIX-only test skipped with a reason (see `posix_only` in `tests/test_provenance.py`).
85
+
86
+ ## Changing the viewer
87
+
88
+ The viewer (`memdebug serve`) only looks. It answers GET and HEAD only, sends no JavaScript, and cannot change a store or the ledger (the
89
+ theme switch sets one cookie and nothing else). Pages are built with the escaping builder in `src/memdebug/viewer/html.py`, never by joining
90
+ strings. The only form is the Compare picker, which uses `method="get"` and just navigates; a test fails if any form uses another method.
91
+ Where a person may want to act, show the command as text to paste, as the "put it back" guidance does, and build it only from names that pass
92
+ `valid_name`. A new page needs a route anchored with `\Z`, an entry in the trailing-line-break test, and an entry in `ALL_PAGES` in
93
+ `tests/test_viewer.py`. What is defended, and what is not: [docs/threat-model.md](docs/threat-model.md#the-viewer).
94
+
95
+ ## Adding a backend
96
+
97
+ Implement the `MemoryAdapter` protocol in `adapters/base.py` (`list_memories`, `read_history`, `history`), read-only,
98
+ validating every row. Look at `adapters/mem0.py` and `adapters/markdown_git.py` for the expected hardening, and add the
99
+ same kinds of tests (hostile rows, oversized values, odd encodings). A backend without a change history should record
100
+ what it observes instead of raising "outside the history" alarms.
101
+
102
+ ## Adding an agent to the catalog
103
+
104
+ See `docs/agents.md`: a documented location (with a link), a test using a fake home folder, and watch only the named files when the
105
+ folder also holds credentials.
106
+
107
+ ## Workflows and Dependabot
108
+
109
+ Dependabot proposes updates to the pinned actions in one grouped pull request a week. Let CI run on it, and for changes to the release
110
+ workflow also run its dry run (docs/releasing.md) from the pull request's branch before merging.
111
+
112
+ ## Releases
113
+
114
+ See `docs/releasing.md`. A test keeps the version, the changelog, the licence files and the links in the documents in agreement.
115
+
116
+ ## Pull requests
117
+
118
+ Keep them focused (one topic), include tests, update the docs that your change makes untrue, and run the whole suite; the pull request
119
+ template lists what to check. Add a line under `[Unreleased]` in `CHANGELOG.md` for anything a user would notice, and write a new limit into
120
+ `docs/threat-model.md`. Do not bump the version or rename the changelog's heading: that is part of a release. The required checks must pass
121
+ before a pull request can merge, and only the maintainer creates release tags (the maintainer can bypass both rules, so they guard against
122
+ mistakes, not against the maintainer). Commits stay public for good, so use a GitHub no-reply address as your commit email if you would
123
+ rather not publish your own. By contributing you agree that your contribution is licensed under the Apache License 2.0 (see LICENSE).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: memdebug
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Inspect, compare and roll back what an AI agent's memory holds. Local-first and agent-neutral.
5
5
  Project-URL: Homepage, https://github.com/juraj-jumic/memdebug
6
6
  Project-URL: Source, https://github.com/juraj-jumic/memdebug
@@ -33,21 +33,63 @@ Description-Content-Type: text/markdown
33
33
 
34
34
  # memdebug
35
35
 
36
+ [![CI](https://github.com/juraj-jumic/memdebug/actions/workflows/ci.yml/badge.svg)](https://github.com/juraj-jumic/memdebug/actions/workflows/ci.yml)
37
+ [![PyPI](https://img.shields.io/pypi/v/memdebug)](https://pypi.org/project/memdebug/)
38
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://pypi.org/project/memdebug/)
39
+ [![License](https://img.shields.io/github/license/juraj-jumic/memdebug)](LICENSE)
40
+
36
41
  **See what your AI agent's memory holds, what changed, and put it back.** A local, agent-neutral tool for people who run
37
42
  agents whose memory they can reach: notes in a folder or git repository, Open WebUI's memory, or a self-hosted Mem0.
38
43
 
44
+ ![memdebug demo: an edit that bypassed git is caught and undone without losing anything, and a tampered copy of the record is detected](docs/demo.gif)
45
+
46
+ That is `memdebug demo` as released in 0.3.0, paced for reading (the real run takes about a second on Linux and a few seconds on Windows).
47
+ The unpaced recording is [`docs/demo.cast`](docs/demo.cast): `asciinema play docs/demo.cast`.
48
+
39
49
  An agent's memory is built from text it read at runtime, and text can be planted: an email, a web page, a document.
40
50
  Nobody reviews all of it. memdebug records what the memory holds in a tamper-evident ledger, shows what changed and when,
41
51
  catches edits that bypassed the store's own history, compares snapshots, flags wording worth a second look, and rolls markdown memory
42
52
  back safely.
43
53
 
44
54
  It is an **observer**: it never sits between the agent and its memory, never talks to the agent, and runs entirely on your
45
- computer. It does not block attacks as they happen (run it next to runtime guards), and it does not yet say *which
46
- conversation* wrote a memory. See [docs/threat-model.md](docs/threat-model.md) for exactly what it does and does not do.
55
+ computer. It does not block attacks as they happen (run it next to runtime guards). For Claude Code it can point at the logged session
56
+ behind a flagged change, as evidence and never as proof; for any other agent it cannot say *which conversation* wrote a memory. See
57
+ [docs/threat-model.md](docs/threat-model.md) for exactly what it does and does not do.
47
58
 
48
- > **Status: alpha (0.3).** The parts described here work. The tests run on every push on Linux, Windows and macOS (Python 3.10, 3.12 and 3.14), and
59
+ > **Status: alpha (0.4).** The parts described here work. The tests run on every push on Linux, Windows and macOS (Python 3.10, 3.12 and 3.14), and
49
60
  > the author also runs them on Windows 11, but expect rough edges. [ROADMAP.md](ROADMAP.md) lists what is built and what is next.
50
61
 
62
+ ## Why this matters
63
+
64
+ An agent's memory is text that is read back into the model at the start of every session. That makes it an unusual kind of state: it
65
+ persists, nobody reviews it, and anything the agent reads (an email, a web page, a document) can try to write itself into it. A poisoned
66
+ note does not fail loudly. It quietly changes behaviour later, in sessions that look unrelated. memdebug treats memory like any other
67
+ critical state: something to inspect, compare, prove and recover. The design problems it takes on:
68
+
69
+ * **Non-deterministic behaviour, deterministic state.** You cannot replay a language model, but you can pin down what it was *given*. A
70
+ snapshot is the memory's text at a moment, as memdebug read it (line endings normalised, and a very long text cut with a hash of the whole),
71
+ a diff is exactly what changed between two snapshots, and for git notes and plain folders a rollback puts a known-good state back. "What was
72
+ the agent working from when it did that?" becomes a question you can answer from evidence, provided memdebug was watching at the time.
73
+ * **Views of memory that can disagree.** For markdown in git and for Mem0 there are three: the store's own history, what is actually there
74
+ now, and memdebug's own record. They are reconciled, and the case that matters most is flagged: a change that **bypassed the store's own
75
+ history**. A plain folder and Open WebUI keep no history, so there memdebug records what changed between two looks but cannot call
76
+ anything "outside the history".
77
+ * **A record that shows tampering.** Entries are only ever appended, and each is hash-chained to the one before, so editing or removing an
78
+ old entry is detectable (`memdebug verify`). Someone who can rewrite the whole ledger file can rebuild a valid chain, and cutting off the
79
+ newest entries is only detectable against a copy of the head hash kept somewhere else (`memdebug witness`). The threat model says so.
80
+ * **Safe recovery, not just detection.** A rollback is a plan you review first, and the plan writes nothing. What it would replace is saved
81
+ before anything is touched: for a plain folder every replaced file is copied byte for byte into a private backup folder next to the ledger;
82
+ for git notes, anything git does not already hold is saved under `refs/memdebug/backups/`. The written files are checked against the plan,
83
+ a failure while writing puts the files back, and the rollback is recorded in the ledger and can itself be undone.
84
+ * **Hostile input by design.** Memory text is untrusted: its size is bounded, it is never executed, and everything shown is escaped (terminal
85
+ output through `safe_text`, the viewer through an escaping HTML builder with no JavaScript). git runs with a scrubbed environment and with
86
+ hooks, filters, pagers, external diff helpers and file-system monitors switched off. `memdebug selftest` checks the platform-dependent
87
+ protections on your own machine instead of asking you to take them on trust, and [docs/threat-model.md](docs/threat-model.md) lists what is
88
+ protected and what is not.
89
+
90
+ What it deliberately is not: it does not hook into your agent, intercept prompts or optimise tokens. It reads the stores your agent already
91
+ writes, from outside (and, for Claude Code, its session logs, read-only, only to say which session logged an edit to a flagged note).
92
+
51
93
  ## Try it in a minute
52
94
 
53
95
  You need Python 3.10 or newer and git 2.31 or newer. Install memdebug from PyPI into its own virtual environment, which works the same way
@@ -97,7 +139,7 @@ Or register stores yourself (this is what a script would do):
97
139
  | openwebui | the `memory` table of Open WebUI's `webui.db`, copied from its Docker container (or from a copy you made) | none | not applicable | no |
98
140
  | mem0 | self-hosted Mem0 | Mem0's `history.db` | a change made directly in storage | no |
99
141
 
100
- Which agents does it know? Claude Code, OpenClaw, Gemini CLI, Codex CLI, Windsurf and Open WebUI (in Docker): see [docs/agents.md](docs/agents.md)
142
+ Which agents does it know? Claude Code, OpenClaw, Gemini CLI, Codex CLI, Windsurf, Cline and Open WebUI (in Docker): see [docs/agents.md](docs/agents.md)
101
143
  for where each keeps its memory and where that was verified. Where memory sits in one file beside credentials (`~/.gemini`, `~/.codex`,
102
144
  `~/.claude`), memdebug watches only that file. If Open WebUI runs in Docker, `memdebug setup` finds it and takes a read-only copy of its database before every look; there is nothing to
103
145
  set up by hand (`windows-tools/copy-webui-db.ps1` is only a fallback for other setups). A store with no history can still be watched, but memdebug cannot tell an
@@ -139,8 +181,8 @@ user service or a login item. memdebug does not install anything that starts by
139
181
 
140
182
  ## The viewer
141
183
 
142
- `memdebug serve` starts a read-only web page (timeline with an inspector, snapshots, compare,
143
- integrity) on **127.0.0.1 only**, and prints a link that contains a random secret. Open that link;
184
+ `memdebug serve` starts a read-only web page (timeline with an inspector, snapshots, compare, the agents it
185
+ found, integrity) on **127.0.0.1 only**, and prints a link that contains a random secret. Open that link;
144
186
  the secret moves into a cookie and disappears from the address bar. It uses only Python's standard
145
187
  library and sends no JavaScript at all. Run `memdebug verify` once first if the ledger is from an
146
188
  older version (the viewer itself never upgrades or creates anything).
@@ -165,11 +207,54 @@ it cannot prove (for example symlinks need Developer Mode).
165
207
  - git is found through PATH entries that are absolute paths only; the current folder is never searched.
166
208
  - Names that mean something special on Windows (`NUL.md`, `con.md`, `file:stream.md`, trailing dots or spaces, `GIT~1`, `.GIT`)
167
209
  are rejected on every platform, so the history and the files always agree.
210
+ - Paths longer than 259 characters work without changing any Windows setting: memdebug hands Windows the extended-length form of every note, backup and
211
+ log path. A git repository in a very deeply nested folder can still hit git's own limits.
168
212
  - Directory junctions and symlinks are never followed, including by rollback. A hung git is stopped with `taskkill /T`.
169
213
  - Ledger file permissions are not enforced by this tool on Windows; the default location is private to your user account.
170
214
  - If git reports "dubious ownership" for a repository on another drive, fix the ownership; this tool deliberately ignores your
171
215
  global `safe.directory` setting.
172
216
 
217
+ ## How it works
218
+
219
+ ```
220
+ your agent (Claude Code, Open WebUI, Mem0, ...)
221
+ runs as usual: memdebug never hooks into it
222
+ | writes its memory
223
+ v
224
+ +--------------------------- memory store ---------------------------+
225
+ | markdown notes in git | plain folder | Open WebUI | Mem0 |
226
+ +------------------------------------+------------------------------+
227
+ | read-only readers: nothing the store holds is executed
228
+ v
229
+ +-------------------+ +---------------------------------+ +--------------------------+
230
+ | sync |-->| events: added, changed, deleted,|-->| hints: "worth a second |
231
+ | the store's | | OUTSIDE the store's history | | look" (heuristics) |
232
+ | history vs what | +----------------+----------------+ +--------------------------+
233
+ | is there now vs | | append
234
+ | the ledger | v
235
+ | (check, watch and | +-------------------------------------+
236
+ | snapshot run it) | | ledger: hash-chained, tamper-evident | snapshots = the text
237
+ +-------------------+ | + snapshots | of the memory at a moment
238
+ +-----+----------------+---------+-----+
239
+ | | |
240
+ v v v
241
+ check / watch / serve (read-only diff s1 s2
242
+ timeline / verify browser viewer) compare two moments
243
+ in the terminal
244
+ |
245
+ | you decide to go back
246
+ v
247
+ rollback: plan (writes nothing) -> you confirm -> save what is replaced
248
+ -> write -> check the written files against the plan -> record in the ledger
249
+ a failure while writing puts the files back (if only the final ledger entry fails,
250
+ the files stay restored and you are told)
251
+ the only code that writes to a memory store: git notes and plain folders, never databases
252
+ ```
253
+
254
+ memdebug's own files (the ledger, the list of watched stores, folder-rollback backups, copies of Open WebUI's database) live in its own data
255
+ folder, or where you point `--db`, `--out` or `--file`; memdebug does not write them inside a store. For Claude Code, `check` and `watch` also read
256
+ its session logs (read-only, ids and times only) to say which session logged an edit to a flagged note: evidence, not proof.
257
+
173
258
  ## How it is built
174
259
 
175
260
  The core (model, ledger, sync, reconcile) imports no backend. Each store type is a read-only adapter: `markdown-git` and `folder`
@@ -185,6 +270,20 @@ installed. Adding a store type is described in [CONTRIBUTING.md](CONTRIBUTING.md
185
270
 
186
271
  See [CONTRIBUTING.md](CONTRIBUTING.md) for the ground rules (everything read from a store is untrusted; adapters only read).
187
272
 
273
+ ## Engineering
274
+
275
+ * **Tests:** 1,500+ `pytest` tests, run on every push to `main` and every pull request on Linux, Windows and macOS with Python 3.10, 3.12 and
276
+ 3.14, including platform-specific cases (real NTFS junctions made with `mklink /J`, case-insensitive file names, CRLF). CI also runs
277
+ `memdebug selftest` and `memdebug demo` on each of those, and builds the package and installs it into a clean environment.
278
+ * **Protections are proven, not assumed:** the project's rule is that a protection only counts once removing it makes a test fail. See
279
+ [CONTRIBUTING.md](CONTRIBUTING.md).
280
+ * **A self-test on your machine:** `memdebug selftest` demonstrates the platform-dependent safety claims on the computer you actually use,
281
+ and says SKIP, never PASS, for anything it could not prove there.
282
+ * **Releases:** PyPI trusted publishing (no stored token), a manual approval before anything is published, provenance attestations (PyPI
283
+ holds them for 0.3.0 and 0.4.0), and branch and tag rules on `main` and on version tags.
284
+ * **Written down:** a [threat model](docs/threat-model.md), a [security policy](SECURITY.md), a [changelog](CHANGELOG.md) and a
285
+ [roadmap](ROADMAP.md) that says what is not built yet.
286
+
188
287
  ## Rolling back a watched store (folders and git notes)
189
288
 
190
289
  `memdebug rollback store NAME --to s1` does the same job by the name you see in `memdebug stores`, for a plain folder of notes as well as for