memdebug 0.2.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 (103) 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.2.0 → memdebug-0.4.0}/.gitignore +4 -0
  4. memdebug-0.4.0/CHANGELOG.md +82 -0
  5. memdebug-0.4.0/CONTRIBUTING.md +101 -0
  6. memdebug-0.4.0/PKG-INFO +356 -0
  7. memdebug-0.4.0/README.md +323 -0
  8. {memdebug-0.2.0 → memdebug-0.4.0}/ROADMAP.md +19 -14
  9. {memdebug-0.2.0 → memdebug-0.4.0}/docs/agents.md +3 -2
  10. memdebug-0.4.0/docs/demo.cast +21 -0
  11. memdebug-0.4.0/docs/demo.gif +0 -0
  12. {memdebug-0.2.0 → memdebug-0.4.0}/docs/releasing.md +4 -3
  13. {memdebug-0.2.0 → memdebug-0.4.0}/docs/threat-model.md +38 -1
  14. {memdebug-0.2.0 → memdebug-0.4.0}/pyproject.toml +1 -1
  15. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/__init__.py +1 -1
  16. memdebug-0.4.0/src/memdebug/adapters/fileops.py +190 -0
  17. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/folder.py +5 -0
  18. memdebug-0.4.0/src/memdebug/adapters/folder_restore.py +275 -0
  19. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/restore.py +5 -159
  20. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/agents.py +3 -0
  21. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/cli.py +103 -12
  22. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/monitor.py +54 -7
  23. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/paths.py +5 -0
  24. memdebug-0.4.0/src/memdebug/provenance.py +251 -0
  25. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/rollback_flow.py +12 -3
  26. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/selftest.py +253 -63
  27. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/pages.py +112 -4
  28. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/server.py +4 -0
  29. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/style.py +1 -0
  30. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_agents.py +11 -0
  31. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_demo.py +9 -0
  32. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_docker_source.py +3 -1
  33. memdebug-0.4.0/tests/test_folder_restore.py +473 -0
  34. memdebug-0.4.0/tests/test_folder_rollback_cli.py +235 -0
  35. memdebug-0.4.0/tests/test_provenance.py +245 -0
  36. memdebug-0.4.0/tests/test_provenance_check.py +197 -0
  37. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_release_hygiene.py +2 -1
  38. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_restore.py +3 -3
  39. memdebug-0.4.0/tests/test_selftest_tripwire.py +340 -0
  40. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer.py +13 -1
  41. memdebug-0.4.0/tests/test_viewer_agents.py +81 -0
  42. memdebug-0.4.0/tests/test_viewer_putback.py +257 -0
  43. memdebug-0.2.0/CHANGELOG.md +0 -33
  44. memdebug-0.2.0/CONTRIBUTING.md +0 -57
  45. memdebug-0.2.0/PKG-INFO +0 -206
  46. memdebug-0.2.0/README.md +0 -173
  47. {memdebug-0.2.0 → memdebug-0.4.0}/.gitattributes +0 -0
  48. {memdebug-0.2.0 → memdebug-0.4.0}/.github/dependabot.yml +0 -0
  49. {memdebug-0.2.0 → memdebug-0.4.0}/.github/workflows/ci.yml +0 -0
  50. {memdebug-0.2.0 → memdebug-0.4.0}/.github/workflows/release.yml +0 -0
  51. {memdebug-0.2.0 → memdebug-0.4.0}/LICENSE +0 -0
  52. {memdebug-0.2.0 → memdebug-0.4.0}/NOTICE +0 -0
  53. {memdebug-0.2.0 → memdebug-0.4.0}/SECURITY.md +0 -0
  54. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/__main__.py +0 -0
  55. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/__init__.py +0 -0
  56. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/base.py +0 -0
  57. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/common.py +0 -0
  58. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/markdown_git.py +0 -0
  59. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/mem0.py +0 -0
  60. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/adapters/openwebui.py +0 -0
  61. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/backends.py +0 -0
  62. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/demo.py +0 -0
  63. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/describe.py +0 -0
  64. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/diff.py +0 -0
  65. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/docker_source.py +0 -0
  66. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/errors.py +0 -0
  67. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/hints.py +0 -0
  68. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/ledger.py +0 -0
  69. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/models.py +0 -0
  70. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/reconcile.py +0 -0
  71. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/report.py +0 -0
  72. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/stores.py +0 -0
  73. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/sync.py +0 -0
  74. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/textsafe.py +0 -0
  75. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/__init__.py +0 -0
  76. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/html.py +0 -0
  77. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/viewer/redline.py +0 -0
  78. {memdebug-0.2.0 → memdebug-0.4.0}/src/memdebug/witness.py +0 -0
  79. {memdebug-0.2.0 → memdebug-0.4.0}/tests/conftest.py +0 -0
  80. {memdebug-0.2.0 → memdebug-0.4.0}/tests/payloads.py +0 -0
  81. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_cli.py +0 -0
  82. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_diff.py +0 -0
  83. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_folder_and_openwebui.py +0 -0
  84. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_hints.py +0 -0
  85. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_ledger.py +0 -0
  86. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_ledger_read.py +0 -0
  87. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_markdown_git.py +0 -0
  88. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_mem0_adapter.py +0 -0
  89. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_models.py +0 -0
  90. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_platform.py +0 -0
  91. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_provenance_openwebui.py +0 -0
  92. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_readable_ids_and_honest_wording.py +0 -0
  93. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_reconcile.py +0 -0
  94. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_report.py +0 -0
  95. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_rollback_cli.py +0 -0
  96. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_snapshots.py +0 -0
  97. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_stores_and_monitor.py +0 -0
  98. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_sync.py +0 -0
  99. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_textsafe.py +0 -0
  100. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer_html.py +0 -0
  101. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer_redline.py +0 -0
  102. {memdebug-0.2.0 → memdebug-0.4.0}/tests/test_viewer_rollback.py +0 -0
  103. {memdebug-0.2.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
@@ -0,0 +1,82 @@
1
+ # Changelog
2
+
3
+ All notable changes. The format follows [Keep a Changelog](https://keepachangelog.com/); versions follow [Semantic Versioning](https://semver.org/)
4
+ (alpha: anything may change before 1.0).
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
+
34
+ ## [0.3.0] - 2026-10-07
35
+
36
+ ### Added
37
+ - **Rollback for plain folders.** `memdebug rollback store NAME --to s1` restores a watched folder of notes (and, by the same command, git notes) to a
38
+ snapshot: a dry run first, everything it replaces saved first byte for byte in a private folder next to the ledger, every write undone if a later
39
+ step fails, and the rollback recorded in the ledger and itself undoable. A folder is rebuilt from the snapshot's text, so line endings are LF (or CRLF
40
+ if the file it replaces uses CRLF throughout), and text a snapshot could not keep faithfully is skipped, never written.
41
+ - `memdebug selftest` has a new check, "folder rollback is safe", that proves the plain-folder rollback claims on your machine without needing git.
42
+ - `memdebug snapshot store NAME` saves a known-good copy of a watched store to roll back to. It refuses while the ledger holds an outside-history
43
+ change or flagged wording since the store's last snapshot, until you add `--include-changes`.
44
+
45
+ ### Changed
46
+ - **The viewer** shows how to put a store back: snapshot pages, the compare page, the page of a change made outside a store's history and the
47
+ overview's "Needs a look" give the `memdebug rollback store` command as text to copy (the viewer itself still only reads). A store name from the
48
+ ledger is only put into a command if it is safe to paste; for an outside change the snapshot offered is always one taken before it. A rollback of a
49
+ plain folder is described as in place with a backup, not as a commit.
50
+ - The file-writing safety code of the git rollback engine (links, junctions, case clashes, atomic writes, undo) now lives in one shared module used by
51
+ both engines. No behaviour change.
52
+ - `memdebug demo` now ends by pointing at `memdebug setup`, the guided way to watch your own agents, instead of an advanced command.
53
+ - Documentation: the README explains installing from PyPI, with a route that works on Windows without pipx.
54
+
55
+ ## [0.2.0] - 2026-10-07
56
+
57
+ ### Added
58
+ - **Witness:** a second copy of the ledger's head kept elsewhere, so a rewritten or cut-short ledger is noticed (`memdebug witness`, `verify --witness`).
59
+ - **Reports** in Markdown, JSON and SARIF, with CI exit codes (`memdebug report`).
60
+ - **Guided use:** `setup`, `add`, `stores`, `remove`, `check`, `status`, `watch`, `agents`, and a registry of watched stores.
61
+ - **More stores:** plain folders of markdown, Open WebUI's memory (read from a copy of its database, or copied by memdebug itself from a Docker
62
+ container), and single-file memory for agents that keep credentials beside it (Gemini CLI, Codex CLI, Claude Code's global file).
63
+ - **An agent catalog** (Claude Code, OpenClaw, Gemini CLI, Codex CLI, Windsurf, Open WebUI) with locations taken from each agent's documentation.
64
+ - **Hints** ("worth a second look") for instructions to send data, remove confirmation or weaken safeguards, hidden characters and secret-like strings.
65
+ - **A first slice of provenance for Open WebUI:** the app's own `created_by` label and how close a chat was, as evidence only.
66
+ - `memdebug demo`, `memdebug --version`, Apache-2.0 licence, security policy, threat model, roadmap and contributing guide.
67
+
68
+ ### Changed
69
+ - The release workflow re-downloads and re-checks the built files in a separate job before it can publish, so a dry run tests the whole hand-over.
70
+ - The viewer says plainly when a store keeps no history, instead of claiming every change went through one.
71
+ - Opaque ids (UUIDs) are shown short, with what the memory says next to them in `check`.
72
+ - Every pattern that validates an identifier now ends with a strict end-of-text anchor (a trailing newline used to slip through some of them).
73
+
74
+ ### Fixed
75
+ - The witness reader accepts Windows line endings and a byte-order mark (git's autocrlf and some editors add them).
76
+ - Tests can no longer touch the Docker, home folder or data folder of the machine they run on.
77
+
78
+ ## [0.1.0] - 2026-10-05
79
+
80
+ ### Added
81
+ - Tamper-evident ledger with snapshots, `verify`, and compare; markdown/git and Mem0 stores; detection of changes that bypass a store's own history.
82
+ - A read-only browser viewer; rollback for markdown/git with backups, undo on failure and a recorded, undoable result; `selftest`.
@@ -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).