memdebug 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. memdebug-0.4.0/.github/ISSUE_TEMPLATE/bug_report.yml +87 -0
  2. memdebug-0.4.0/.github/pull_request_template.md +24 -0
  3. {memdebug-0.3.0 → memdebug-0.4.0}/.gitignore +4 -0
  4. {memdebug-0.3.0 → memdebug-0.4.0}/CHANGELOG.md +28 -0
  5. memdebug-0.4.0/CONTRIBUTING.md +101 -0
  6. {memdebug-0.3.0 → memdebug-0.4.0}/PKG-INFO +104 -7
  7. {memdebug-0.3.0 → memdebug-0.4.0}/README.md +103 -6
  8. {memdebug-0.3.0 → memdebug-0.4.0}/ROADMAP.md +12 -7
  9. {memdebug-0.3.0 → memdebug-0.4.0}/docs/agents.md +2 -1
  10. memdebug-0.4.0/docs/demo.cast +21 -0
  11. memdebug-0.4.0/docs/demo.gif +0 -0
  12. {memdebug-0.3.0 → memdebug-0.4.0}/docs/threat-model.md +16 -0
  13. {memdebug-0.3.0 → memdebug-0.4.0}/pyproject.toml +1 -1
  14. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/__init__.py +1 -1
  15. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/agents.py +3 -0
  16. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/monitor.py +30 -7
  17. memdebug-0.4.0/src/memdebug/provenance.py +251 -0
  18. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/selftest.py +204 -63
  19. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/viewer/pages.py +32 -1
  20. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/viewer/server.py +4 -0
  21. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_agents.py +11 -0
  22. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_docker_source.py +3 -1
  23. memdebug-0.4.0/tests/test_provenance.py +245 -0
  24. memdebug-0.4.0/tests/test_provenance_check.py +197 -0
  25. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_release_hygiene.py +2 -1
  26. memdebug-0.4.0/tests/test_selftest_tripwire.py +340 -0
  27. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_viewer.py +13 -1
  28. memdebug-0.4.0/tests/test_viewer_agents.py +81 -0
  29. memdebug-0.3.0/CONTRIBUTING.md +0 -57
  30. {memdebug-0.3.0 → memdebug-0.4.0}/.gitattributes +0 -0
  31. {memdebug-0.3.0 → memdebug-0.4.0}/.github/dependabot.yml +0 -0
  32. {memdebug-0.3.0 → memdebug-0.4.0}/.github/workflows/ci.yml +0 -0
  33. {memdebug-0.3.0 → memdebug-0.4.0}/.github/workflows/release.yml +0 -0
  34. {memdebug-0.3.0 → memdebug-0.4.0}/LICENSE +0 -0
  35. {memdebug-0.3.0 → memdebug-0.4.0}/NOTICE +0 -0
  36. {memdebug-0.3.0 → memdebug-0.4.0}/SECURITY.md +0 -0
  37. {memdebug-0.3.0 → memdebug-0.4.0}/docs/releasing.md +0 -0
  38. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/__main__.py +0 -0
  39. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/__init__.py +0 -0
  40. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/base.py +0 -0
  41. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/common.py +0 -0
  42. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/fileops.py +0 -0
  43. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/folder.py +0 -0
  44. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/folder_restore.py +0 -0
  45. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/markdown_git.py +0 -0
  46. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/mem0.py +0 -0
  47. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/openwebui.py +0 -0
  48. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/adapters/restore.py +0 -0
  49. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/backends.py +0 -0
  50. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/cli.py +0 -0
  51. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/demo.py +0 -0
  52. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/describe.py +0 -0
  53. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/diff.py +0 -0
  54. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/docker_source.py +0 -0
  55. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/errors.py +0 -0
  56. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/hints.py +0 -0
  57. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/ledger.py +0 -0
  58. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/models.py +0 -0
  59. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/paths.py +0 -0
  60. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/reconcile.py +0 -0
  61. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/report.py +0 -0
  62. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/rollback_flow.py +0 -0
  63. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/stores.py +0 -0
  64. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/sync.py +0 -0
  65. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/textsafe.py +0 -0
  66. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/viewer/__init__.py +0 -0
  67. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/viewer/html.py +0 -0
  68. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/viewer/redline.py +0 -0
  69. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/viewer/style.py +0 -0
  70. {memdebug-0.3.0 → memdebug-0.4.0}/src/memdebug/witness.py +0 -0
  71. {memdebug-0.3.0 → memdebug-0.4.0}/tests/conftest.py +0 -0
  72. {memdebug-0.3.0 → memdebug-0.4.0}/tests/payloads.py +0 -0
  73. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_cli.py +0 -0
  74. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_demo.py +0 -0
  75. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_diff.py +0 -0
  76. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_folder_and_openwebui.py +0 -0
  77. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_folder_restore.py +0 -0
  78. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_folder_rollback_cli.py +0 -0
  79. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_hints.py +0 -0
  80. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_ledger.py +0 -0
  81. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_ledger_read.py +0 -0
  82. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_markdown_git.py +0 -0
  83. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_mem0_adapter.py +0 -0
  84. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_models.py +0 -0
  85. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_platform.py +0 -0
  86. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_provenance_openwebui.py +0 -0
  87. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_readable_ids_and_honest_wording.py +0 -0
  88. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_reconcile.py +0 -0
  89. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_report.py +0 -0
  90. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_restore.py +0 -0
  91. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_rollback_cli.py +0 -0
  92. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_snapshots.py +0 -0
  93. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_stores_and_monitor.py +0 -0
  94. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_sync.py +0 -0
  95. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_textsafe.py +0 -0
  96. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_viewer_html.py +0 -0
  97. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_viewer_putback.py +0 -0
  98. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_viewer_redline.py +0 -0
  99. {memdebug-0.3.0 → memdebug-0.4.0}/tests/test_viewer_rollback.py +0 -0
  100. {memdebug-0.3.0 → memdebug-0.4.0}/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,34 @@
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.0] - 2026-10-09
7
+
8
+ ### Added
9
+ - **Who wrote it.** For a changed note that looks suspicious, or that changed outside git, `memdebug check` and `memdebug watch` now also say which
10
+ 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
11
+ 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
12
+ wrote, and shell-made changes cannot be matched (they are only counted). See `docs/threat-model.md`.
13
+ - 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
14
+ to run (`memdebug setup`). It only checks that folders exist, opens no file, and works even while the ledger is busy.
15
+ - Cline is in the agent catalog: `memdebug agents` and `memdebug setup` offer its global rules folder (`~/Documents/Cline/Rules`) once it holds markdown rules.
16
+
17
+ ### Changed
18
+ - `memdebug selftest`, "rollback is safe" and "hostile repository config cannot run programs": the control (ordinary git, tripwires armed) now runs in
19
+ 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
20
+ asserted to run no tripwire. The tripwires now record what started them (time, arguments, and the parent and grandparent command lines where the
21
+ platform shows them: `/proc` on Linux, `ps` on macOS, nothing on Windows), and a failure prints those records and `git --version`. This follows a
22
+ failure on CI that passed on a re-run and could not be reproduced.
23
+ - The repository's ignore file also excludes `backups/` and `*.jsonl` (session logs), and the release hygiene test checks both rules.
24
+
25
+ ### Documentation
26
+ - 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
27
+ "Engineering" sections, checked against the code; claims about snapshots, the ledger and rollback that overstated what the code does were corrected.
28
+ - CONTRIBUTING.md now starts with the private-data rule and covers reporting a security problem, setup, writing tests, changing the viewer and the pull
29
+ 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
30
+ to paste memory contents or session transcripts.
31
+ - `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
32
+ folder as confirmed on Windows 11.
33
+
6
34
  ## [0.3.0] - 2026-10-07
7
35
 
8
36
  ### Added
@@ -0,0 +1,101 @@
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
+ ## Writing tests
55
+
56
+ * Use made-up data only. Every test already gets an empty home folder and no Docker (`isolated_home` and `no_real_docker` in
57
+ `tests/conftest.py`), so never read a real home folder, agent folder, session log or container, and never put a real transcript in a
58
+ fixture. Write a made-up log, as `tests/test_provenance.py` does. A test that sets the home folder sets both `HOME` and `USERPROFILE`.
59
+ * 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 `$`,
60
+ and is added to `test_no_validator_accepts_a_trailing_line_break` in `tests/test_docker_source.py`, which tries every pattern with a
61
+ trailing line break.
62
+ * Give parametrized tests short `ids=`, and mark a POSIX-only test skipped with a reason (see `posix_only` in `tests/test_provenance.py`).
63
+
64
+ ## Changing the viewer
65
+
66
+ The viewer (`memdebug serve`) only looks. It answers GET and HEAD only, sends no JavaScript, and cannot change a store or the ledger (the
67
+ theme switch sets one cookie and nothing else). Pages are built with the escaping builder in `src/memdebug/viewer/html.py`, never by joining
68
+ strings. The only form is the Compare picker, which uses `method="get"` and just navigates; a test fails if any form uses another method.
69
+ 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
70
+ `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
71
+ `tests/test_viewer.py`. What is defended, and what is not: [docs/threat-model.md](docs/threat-model.md#the-viewer).
72
+
73
+ ## Adding a backend
74
+
75
+ Implement the `MemoryAdapter` protocol in `adapters/base.py` (`list_memories`, `read_history`, `history`), read-only,
76
+ validating every row. Look at `adapters/mem0.py` and `adapters/markdown_git.py` for the expected hardening, and add the
77
+ same kinds of tests (hostile rows, oversized values, odd encodings). A backend without a change history should record
78
+ what it observes instead of raising "outside the history" alarms.
79
+
80
+ ## Adding an agent to the catalog
81
+
82
+ 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
83
+ folder also holds credentials.
84
+
85
+ ## Workflows and Dependabot
86
+
87
+ 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
88
+ workflow also run its dry run (docs/releasing.md) from the pull request's branch before merging.
89
+
90
+ ## Releases
91
+
92
+ See `docs/releasing.md`. A test keeps the version, the changelog, the licence files and the links in the documents in agreement.
93
+
94
+ ## Pull requests
95
+
96
+ Keep them focused (one topic), include tests, update the docs that your change makes untrue, and run the whole suite; the pull request
97
+ 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
98
+ `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
99
+ before a pull request can merge, and only the maintainer creates release tags (the maintainer can bypass both rules, so they guard against
100
+ mistakes, not against the maintainer). Commits stay public for good, so use a GitHub no-reply address as your commit email if you would
101
+ 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.0
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).
@@ -170,6 +212,47 @@ it cannot prove (for example symlinks need Developer Mode).
170
212
  - If git reports "dubious ownership" for a repository on another drive, fix the ownership; this tool deliberately ignores your
171
213
  global `safe.directory` setting.
172
214
 
215
+ ## How it works
216
+
217
+ ```
218
+ your agent (Claude Code, Open WebUI, Mem0, ...)
219
+ runs as usual: memdebug never hooks into it
220
+ | writes its memory
221
+ v
222
+ +--------------------------- memory store ---------------------------+
223
+ | markdown notes in git | plain folder | Open WebUI | Mem0 |
224
+ +------------------------------------+------------------------------+
225
+ | read-only readers: nothing the store holds is executed
226
+ v
227
+ +-------------------+ +---------------------------------+ +--------------------------+
228
+ | sync |-->| events: added, changed, deleted,|-->| hints: "worth a second |
229
+ | the store's | | OUTSIDE the store's history | | look" (heuristics) |
230
+ | history vs what | +----------------+----------------+ +--------------------------+
231
+ | is there now vs | | append
232
+ | the ledger | v
233
+ | (check, watch and | +-------------------------------------+
234
+ | snapshot run it) | | ledger: hash-chained, tamper-evident | snapshots = the text
235
+ +-------------------+ | + snapshots | of the memory at a moment
236
+ +-----+----------------+---------+-----+
237
+ | | |
238
+ v v v
239
+ check / watch / serve (read-only diff s1 s2
240
+ timeline / verify browser viewer) compare two moments
241
+ in the terminal
242
+ |
243
+ | you decide to go back
244
+ v
245
+ rollback: plan (writes nothing) -> you confirm -> save what is replaced
246
+ -> write -> check the written files against the plan -> record in the ledger
247
+ a failure while writing puts the files back (if only the final ledger entry fails,
248
+ the files stay restored and you are told)
249
+ the only code that writes to a memory store: git notes and plain folders, never databases
250
+ ```
251
+
252
+ 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
253
+ 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
254
+ its session logs (read-only, ids and times only) to say which session logged an edit to a flagged note: evidence, not proof.
255
+
173
256
  ## How it is built
174
257
 
175
258
  The core (model, ledger, sync, reconcile) imports no backend. Each store type is a read-only adapter: `markdown-git` and `folder`
@@ -185,6 +268,20 @@ installed. Adding a store type is described in [CONTRIBUTING.md](CONTRIBUTING.md
185
268
 
186
269
  See [CONTRIBUTING.md](CONTRIBUTING.md) for the ground rules (everything read from a store is untrusted; adapters only read).
187
270
 
271
+ ## Engineering
272
+
273
+ * **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
274
+ 3.14, including platform-specific cases (real NTFS junctions made with `mklink /J`, case-insensitive file names, CRLF). CI also runs
275
+ `memdebug selftest` and `memdebug demo` on each of those, and builds the package and installs it into a clean environment.
276
+ * **Protections are proven, not assumed:** the project's rule is that a protection only counts once removing it makes a test fail. See
277
+ [CONTRIBUTING.md](CONTRIBUTING.md).
278
+ * **A self-test on your machine:** `memdebug selftest` demonstrates the platform-dependent safety claims on the computer you actually use,
279
+ and says SKIP, never PASS, for anything it could not prove there.
280
+ * **Releases:** PyPI trusted publishing (no stored token), a manual approval before anything is published, provenance attestations (PyPI
281
+ holds them for 0.3.0), and branch and tag rules on `main` and on version tags.
282
+ * **Written down:** a [threat model](docs/threat-model.md), a [security policy](SECURITY.md), a [changelog](CHANGELOG.md) and a
283
+ [roadmap](ROADMAP.md) that says what is not built yet.
284
+
188
285
  ## Rolling back a watched store (folders and git notes)
189
286
 
190
287
  `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
@@ -1,20 +1,62 @@
1
1
  # memdebug
2
2
 
3
+ [![CI](https://github.com/juraj-jumic/memdebug/actions/workflows/ci.yml/badge.svg)](https://github.com/juraj-jumic/memdebug/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/memdebug)](https://pypi.org/project/memdebug/)
5
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue)](https://pypi.org/project/memdebug/)
6
+ [![License](https://img.shields.io/github/license/juraj-jumic/memdebug)](LICENSE)
7
+
3
8
  **See what your AI agent's memory holds, what changed, and put it back.** A local, agent-neutral tool for people who run
4
9
  agents whose memory they can reach: notes in a folder or git repository, Open WebUI's memory, or a self-hosted Mem0.
5
10
 
11
+ ![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)
12
+
13
+ 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).
14
+ The unpaced recording is [`docs/demo.cast`](docs/demo.cast): `asciinema play docs/demo.cast`.
15
+
6
16
  An agent's memory is built from text it read at runtime, and text can be planted: an email, a web page, a document.
7
17
  Nobody reviews all of it. memdebug records what the memory holds in a tamper-evident ledger, shows what changed and when,
8
18
  catches edits that bypassed the store's own history, compares snapshots, flags wording worth a second look, and rolls markdown memory
9
19
  back safely.
10
20
 
11
21
  It is an **observer**: it never sits between the agent and its memory, never talks to the agent, and runs entirely on your
12
- computer. It does not block attacks as they happen (run it next to runtime guards), and it does not yet say *which
13
- conversation* wrote a memory. See [docs/threat-model.md](docs/threat-model.md) for exactly what it does and does not do.
22
+ 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
23
+ behind a flagged change, as evidence and never as proof; for any other agent it cannot say *which conversation* wrote a memory. See
24
+ [docs/threat-model.md](docs/threat-model.md) for exactly what it does and does not do.
14
25
 
15
- > **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
26
+ > **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
16
27
  > the author also runs them on Windows 11, but expect rough edges. [ROADMAP.md](ROADMAP.md) lists what is built and what is next.
17
28
 
29
+ ## Why this matters
30
+
31
+ 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
32
+ 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
33
+ note does not fail loudly. It quietly changes behaviour later, in sessions that look unrelated. memdebug treats memory like any other
34
+ critical state: something to inspect, compare, prove and recover. The design problems it takes on:
35
+
36
+ * **Non-deterministic behaviour, deterministic state.** You cannot replay a language model, but you can pin down what it was *given*. A
37
+ 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),
38
+ 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
39
+ the agent working from when it did that?" becomes a question you can answer from evidence, provided memdebug was watching at the time.
40
+ * **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
41
+ 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
42
+ history**. A plain folder and Open WebUI keep no history, so there memdebug records what changed between two looks but cannot call
43
+ anything "outside the history".
44
+ * **A record that shows tampering.** Entries are only ever appended, and each is hash-chained to the one before, so editing or removing an
45
+ old entry is detectable (`memdebug verify`). Someone who can rewrite the whole ledger file can rebuild a valid chain, and cutting off the
46
+ newest entries is only detectable against a copy of the head hash kept somewhere else (`memdebug witness`). The threat model says so.
47
+ * **Safe recovery, not just detection.** A rollback is a plan you review first, and the plan writes nothing. What it would replace is saved
48
+ 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;
49
+ for git notes, anything git does not already hold is saved under `refs/memdebug/backups/`. The written files are checked against the plan,
50
+ a failure while writing puts the files back, and the rollback is recorded in the ledger and can itself be undone.
51
+ * **Hostile input by design.** Memory text is untrusted: its size is bounded, it is never executed, and everything shown is escaped (terminal
52
+ output through `safe_text`, the viewer through an escaping HTML builder with no JavaScript). git runs with a scrubbed environment and with
53
+ hooks, filters, pagers, external diff helpers and file-system monitors switched off. `memdebug selftest` checks the platform-dependent
54
+ 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
55
+ protected and what is not.
56
+
57
+ What it deliberately is not: it does not hook into your agent, intercept prompts or optimise tokens. It reads the stores your agent already
58
+ writes, from outside (and, for Claude Code, its session logs, read-only, only to say which session logged an edit to a flagged note).
59
+
18
60
  ## Try it in a minute
19
61
 
20
62
  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
@@ -64,7 +106,7 @@ Or register stores yourself (this is what a script would do):
64
106
  | 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 |
65
107
  | mem0 | self-hosted Mem0 | Mem0's `history.db` | a change made directly in storage | no |
66
108
 
67
- 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)
109
+ 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)
68
110
  for where each keeps its memory and where that was verified. Where memory sits in one file beside credentials (`~/.gemini`, `~/.codex`,
69
111
  `~/.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
70
112
  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
@@ -106,8 +148,8 @@ user service or a login item. memdebug does not install anything that starts by
106
148
 
107
149
  ## The viewer
108
150
 
109
- `memdebug serve` starts a read-only web page (timeline with an inspector, snapshots, compare,
110
- integrity) on **127.0.0.1 only**, and prints a link that contains a random secret. Open that link;
151
+ `memdebug serve` starts a read-only web page (timeline with an inspector, snapshots, compare, the agents it
152
+ found, integrity) on **127.0.0.1 only**, and prints a link that contains a random secret. Open that link;
111
153
  the secret moves into a cookie and disappears from the address bar. It uses only Python's standard
112
154
  library and sends no JavaScript at all. Run `memdebug verify` once first if the ledger is from an
113
155
  older version (the viewer itself never upgrades or creates anything).
@@ -137,6 +179,47 @@ it cannot prove (for example symlinks need Developer Mode).
137
179
  - If git reports "dubious ownership" for a repository on another drive, fix the ownership; this tool deliberately ignores your
138
180
  global `safe.directory` setting.
139
181
 
182
+ ## How it works
183
+
184
+ ```
185
+ your agent (Claude Code, Open WebUI, Mem0, ...)
186
+ runs as usual: memdebug never hooks into it
187
+ | writes its memory
188
+ v
189
+ +--------------------------- memory store ---------------------------+
190
+ | markdown notes in git | plain folder | Open WebUI | Mem0 |
191
+ +------------------------------------+------------------------------+
192
+ | read-only readers: nothing the store holds is executed
193
+ v
194
+ +-------------------+ +---------------------------------+ +--------------------------+
195
+ | sync |-->| events: added, changed, deleted,|-->| hints: "worth a second |
196
+ | the store's | | OUTSIDE the store's history | | look" (heuristics) |
197
+ | history vs what | +----------------+----------------+ +--------------------------+
198
+ | is there now vs | | append
199
+ | the ledger | v
200
+ | (check, watch and | +-------------------------------------+
201
+ | snapshot run it) | | ledger: hash-chained, tamper-evident | snapshots = the text
202
+ +-------------------+ | + snapshots | of the memory at a moment
203
+ +-----+----------------+---------+-----+
204
+ | | |
205
+ v v v
206
+ check / watch / serve (read-only diff s1 s2
207
+ timeline / verify browser viewer) compare two moments
208
+ in the terminal
209
+ |
210
+ | you decide to go back
211
+ v
212
+ rollback: plan (writes nothing) -> you confirm -> save what is replaced
213
+ -> write -> check the written files against the plan -> record in the ledger
214
+ a failure while writing puts the files back (if only the final ledger entry fails,
215
+ the files stay restored and you are told)
216
+ the only code that writes to a memory store: git notes and plain folders, never databases
217
+ ```
218
+
219
+ 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
220
+ 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
221
+ its session logs (read-only, ids and times only) to say which session logged an edit to a flagged note: evidence, not proof.
222
+
140
223
  ## How it is built
141
224
 
142
225
  The core (model, ledger, sync, reconcile) imports no backend. Each store type is a read-only adapter: `markdown-git` and `folder`
@@ -152,6 +235,20 @@ installed. Adding a store type is described in [CONTRIBUTING.md](CONTRIBUTING.md
152
235
 
153
236
  See [CONTRIBUTING.md](CONTRIBUTING.md) for the ground rules (everything read from a store is untrusted; adapters only read).
154
237
 
238
+ ## Engineering
239
+
240
+ * **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
241
+ 3.14, including platform-specific cases (real NTFS junctions made with `mklink /J`, case-insensitive file names, CRLF). CI also runs
242
+ `memdebug selftest` and `memdebug demo` on each of those, and builds the package and installs it into a clean environment.
243
+ * **Protections are proven, not assumed:** the project's rule is that a protection only counts once removing it makes a test fail. See
244
+ [CONTRIBUTING.md](CONTRIBUTING.md).
245
+ * **A self-test on your machine:** `memdebug selftest` demonstrates the platform-dependent safety claims on the computer you actually use,
246
+ and says SKIP, never PASS, for anything it could not prove there.
247
+ * **Releases:** PyPI trusted publishing (no stored token), a manual approval before anything is published, provenance attestations (PyPI
248
+ holds them for 0.3.0), and branch and tag rules on `main` and on version tags.
249
+ * **Written down:** a [threat model](docs/threat-model.md), a [security policy](SECURITY.md), a [changelog](CHANGELOG.md) and a
250
+ [roadmap](ROADMAP.md) that says what is not built yet.
251
+
155
252
  ## Rolling back a watched store (folders and git notes)
156
253
 
157
254
  `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