engmem 0.1.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 (92) hide show
  1. engmem-0.1.1/.github/CODEOWNERS +2 -0
  2. engmem-0.1.1/.github/ISSUE_TEMPLATE/bug_report.yml +41 -0
  3. engmem-0.1.1/.github/ISSUE_TEMPLATE/config.yml +1 -0
  4. engmem-0.1.1/.github/ISSUE_TEMPLATE/feature_request.yml +32 -0
  5. engmem-0.1.1/.github/dependabot.yml +12 -0
  6. engmem-0.1.1/.github/pull_request_template.md +13 -0
  7. engmem-0.1.1/.github/workflows/ci.yml +136 -0
  8. engmem-0.1.1/.github/workflows/release.yml +114 -0
  9. engmem-0.1.1/.gitignore +45 -0
  10. engmem-0.1.1/CONTRIBUTING.md +71 -0
  11. engmem-0.1.1/ENGMEM-SPEC.md +729 -0
  12. engmem-0.1.1/LICENSE +21 -0
  13. engmem-0.1.1/PKG-INFO +282 -0
  14. engmem-0.1.1/README.md +236 -0
  15. engmem-0.1.1/SECURITY.md +35 -0
  16. engmem-0.1.1/docs/.nojekyll +0 -0
  17. engmem-0.1.1/docs/design/contracts/backfill.md +541 -0
  18. engmem-0.1.1/docs/design/contracts/cache.md +210 -0
  19. engmem-0.1.1/docs/design/contracts/gate1.md +563 -0
  20. engmem-0.1.1/docs/design/contracts/install.md +87 -0
  21. engmem-0.1.1/docs/design/contracts/mcp-server.md +343 -0
  22. engmem-0.1.1/docs/design/contracts/output.md +75 -0
  23. engmem-0.1.1/docs/design/contracts/prompt-templates.md +72 -0
  24. engmem-0.1.1/docs/design/contracts/runtime.md +121 -0
  25. engmem-0.1.1/docs/design/contracts/scoring.md +228 -0
  26. engmem-0.1.1/docs/design/contracts/sections.md +161 -0
  27. engmem-0.1.1/docs/design/data-model.md +199 -0
  28. engmem-0.1.1/docs/engmem-anatomy.html +2336 -0
  29. engmem-0.1.1/docs/store-readme.md +45 -0
  30. engmem-0.1.1/pyproject.toml +53 -0
  31. engmem-0.1.1/src/engmem/__init__.py +3 -0
  32. engmem-0.1.1/src/engmem/backfill.py +592 -0
  33. engmem-0.1.1/src/engmem/cache.py +185 -0
  34. engmem-0.1.1/src/engmem/cli.py +573 -0
  35. engmem-0.1.1/src/engmem/gate1.py +248 -0
  36. engmem-0.1.1/src/engmem/gate1_audit.py +181 -0
  37. engmem-0.1.1/src/engmem/install.py +581 -0
  38. engmem-0.1.1/src/engmem/mcp_server.py +1188 -0
  39. engmem-0.1.1/src/engmem/output.py +426 -0
  40. engmem-0.1.1/src/engmem/runtime.py +57 -0
  41. engmem-0.1.1/src/engmem/scoring.py +600 -0
  42. engmem-0.1.1/src/engmem/sections.py +279 -0
  43. engmem-0.1.1/src/engmem/spine.py +490 -0
  44. engmem-0.1.1/src/engmem/staging.py +73 -0
  45. engmem-0.1.1/src/engmem/telemetry.py +274 -0
  46. engmem-0.1.1/src/engmem/templates/engmem.save.md +280 -0
  47. engmem-0.1.1/src/engmem/templates/engmem.save.quick.md +55 -0
  48. engmem-0.1.1/src/engmem/templates/engmem.start.md +161 -0
  49. engmem-0.1.1/tests/conftest.py +274 -0
  50. engmem-0.1.1/tests/fixtures/sessions/1000001-response-cache.md +43 -0
  51. engmem-0.1.1/tests/fixtures/sessions/broken.md +11 -0
  52. engmem-0.1.1/tests/fixtures/sessions/maze-render-new.md +45 -0
  53. engmem-0.1.1/tests/fixtures/sessions/maze-render-old.md +41 -0
  54. engmem-0.1.1/tests/fixtures/sessions/metrics-query-refactor.md +43 -0
  55. engmem-0.1.1/tests/fixtures/sessions/mq-message-sweeper.md +43 -0
  56. engmem-0.1.1/tests/fixtures/sessions/ttl-etag-revalidation-v2.md +46 -0
  57. engmem-0.1.1/tests/fixtures/sessions/wip-something.md +19 -0
  58. engmem-0.1.1/tests/fixtures/telemetry.jsonl +16 -0
  59. engmem-0.1.1/tests/test_backfill.py +1907 -0
  60. engmem-0.1.1/tests/test_backfill_cli.py +489 -0
  61. engmem-0.1.1/tests/test_baseline_cost.py +148 -0
  62. engmem-0.1.1/tests/test_cache.py +442 -0
  63. engmem-0.1.1/tests/test_cli.py +982 -0
  64. engmem-0.1.1/tests/test_commonmark_parity.py +65 -0
  65. engmem-0.1.1/tests/test_degraded_spine.py +420 -0
  66. engmem-0.1.1/tests/test_docs_match_the_code.py +279 -0
  67. engmem-0.1.1/tests/test_encoding.py +47 -0
  68. engmem-0.1.1/tests/test_fixture_writer.py +29 -0
  69. engmem-0.1.1/tests/test_gate1.py +407 -0
  70. engmem-0.1.1/tests/test_gate1_audit.py +594 -0
  71. engmem-0.1.1/tests/test_gate1_report.py +738 -0
  72. engmem-0.1.1/tests/test_install.py +943 -0
  73. engmem-0.1.1/tests/test_lifecycle.py +111 -0
  74. engmem-0.1.1/tests/test_mcp_server.py +1437 -0
  75. engmem-0.1.1/tests/test_mcp_subprocess.py +383 -0
  76. engmem-0.1.1/tests/test_mcp_write_tools.py +1290 -0
  77. engmem-0.1.1/tests/test_no_network.py +117 -0
  78. engmem-0.1.1/tests/test_output.py +1416 -0
  79. engmem-0.1.1/tests/test_runtime.py +270 -0
  80. engmem-0.1.1/tests/test_scoring.py +1221 -0
  81. engmem-0.1.1/tests/test_sections.py +614 -0
  82. engmem-0.1.1/tests/test_spine.py +910 -0
  83. engmem-0.1.1/tests/test_staging.py +132 -0
  84. engmem-0.1.1/tests/test_stdout_is_protocol_only.py +78 -0
  85. engmem-0.1.1/tests/test_stray_docs.py +170 -0
  86. engmem-0.1.1/tests/test_telemetry.py +642 -0
  87. engmem-0.1.1/tests/test_uninstall.py +563 -0
  88. engmem-0.1.1/tests/test_verify_citations.py +262 -0
  89. engmem-0.1.1/tools/baseline_cost.py +83 -0
  90. engmem-0.1.1/tools/gate1_report.py +210 -0
  91. engmem-0.1.1/tools/verify_citations.py +68 -0
  92. engmem-0.1.1/uv.lock +163 -0
@@ -0,0 +1,2 @@
1
+ # Every change needs a review from the owner.
2
+ * @AlejandroKolio
@@ -0,0 +1,41 @@
1
+ name: Bug report
2
+ description: Something behaved differently from what the output claimed
3
+ labels: [bug]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Please redact anything private. Document titles, ticket ids, and matched text
9
+ from your store often quote proprietary work — replace them with placeholders.
10
+
11
+ - type: textarea
12
+ id: what-happened
13
+ attributes:
14
+ label: What happened
15
+ description: The command you ran and its full output, redacted as needed.
16
+ render: shell
17
+ validations:
18
+ required: true
19
+
20
+ - type: textarea
21
+ id: expected
22
+ attributes:
23
+ label: What you expected instead
24
+ validations:
25
+ required: true
26
+
27
+ - type: input
28
+ id: version
29
+ attributes:
30
+ label: engmem version and Python version
31
+ placeholder: "engmem 0.1.0, Python 3.13 on macOS 15"
32
+ validations:
33
+ required: true
34
+
35
+ - type: textarea
36
+ id: store-shape
37
+ attributes:
38
+ label: Store shape
39
+ description: >-
40
+ Roughly how many documents, and anything unusual about them — nested
41
+ directories, hand-written front matter, documents predating engmem.
@@ -0,0 +1 @@
1
+ blank_issues_enabled: true
@@ -0,0 +1,32 @@
1
+ name: Feature request
2
+ description: Propose a change to what engmem does
3
+ labels: [enhancement]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ engmem is deliberately small: plain files, no database, no derived index, one
9
+ runtime dependency. `ENGMEM-SPEC.md` §9 lists what was considered and cut, and
10
+ why — worth reading first, since a proposal it already covers needs to argue
11
+ against the recorded reason rather than restate the idea.
12
+
13
+ - type: textarea
14
+ id: problem
15
+ attributes:
16
+ label: The problem
17
+ description: What went wrong in real use. Not the solution yet — the situation.
18
+ validations:
19
+ required: true
20
+
21
+ - type: textarea
22
+ id: proposal
23
+ attributes:
24
+ label: What you propose
25
+ validations:
26
+ required: true
27
+
28
+ - type: textarea
29
+ id: alternatives
30
+ attributes:
31
+ label: What you considered and rejected
32
+ description: Including doing nothing.
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ # The actions in ci.yml and release.yml are third-party code running with repository
4
+ # access. A tag can be moved under you; this is what keeps the pinned versions current.
5
+ # It does not reach `pypa/gh-action-pypi-publish@release/v1` — a branch has no version
6
+ # to raise, so the action that mints the PyPI token is the one this never reports on.
7
+ # That trade-off is argued at the `uses:` line in release.yml.
8
+ - package-ecosystem: github-actions
9
+ directory: "/"
10
+ schedule:
11
+ interval: monthly
12
+ open-pull-requests-limit: 3
@@ -0,0 +1,13 @@
1
+ ## What this changes
2
+
3
+ <!-- The behaviour, in a sentence or two. Why, not just what. -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] A failing test was written first and observed to fail (ENGMEM-SPEC.md §10, principle I)
8
+ - [ ] `uv run pytest -q` is green
9
+ - [ ] No existing assertion was weakened to make something pass
10
+ - [ ] Comments explain *why*, in a line or two — long rationale went to the spec instead
11
+ - [ ] Examples in tests and docs are invented, with no real ticket, repository, class, or
12
+ business term from any private codebase
13
+ - [ ] Runtime dependencies unchanged (still `pyyaml` only), or the reason is stated here
@@ -0,0 +1,136 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, master]
6
+ pull_request:
7
+
8
+ # a fork's pull request runs with this token; nothing here needs to write
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ${{ matrix.os }}
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ # POSIX-only tests (chmod-based permission checks, symlink containment)
19
+ # skip themselves on Windows via tests/conftest.py rather than failing.
20
+ os: [ubuntu-latest, macos-latest, windows-latest]
21
+ python-version: ["3.11", "3.12", "3.13"]
22
+
23
+ steps:
24
+ - uses: actions/checkout@v7
25
+
26
+ - name: Install uv
27
+ uses: astral-sh/setup-uv@v7
28
+ with:
29
+ enable-cache: true
30
+
31
+ - name: Set up Python ${{ matrix.python-version }}
32
+ run: uv python install ${{ matrix.python-version }}
33
+
34
+ - name: Install the project and its dev group
35
+ run: uv sync --group dev --python ${{ matrix.python-version }}
36
+
37
+ - name: Run the test suite
38
+ run: uv run --python ${{ matrix.python-version }} pytest -q
39
+
40
+ runtime-dependencies:
41
+ # engmem's runtime is pyyaml and markdown-it-py, nothing else. A dev-only tool
42
+ # leaking into the runtime dependency set is a defect this catches before
43
+ # release, not after.
44
+ runs-on: ubuntu-latest
45
+ steps:
46
+ - uses: actions/checkout@v7
47
+ - uses: astral-sh/setup-uv@v7
48
+ - run: uv build --wheel
49
+ - name: Assert the wheel requires nothing beyond the two declared dependencies
50
+ run: |
51
+ python - <<'PY'
52
+ import pathlib, zipfile
53
+ wheel = next(pathlib.Path("dist").glob("*.whl"))
54
+ names = zipfile.ZipFile(wheel).namelist()
55
+ meta = zipfile.ZipFile(wheel).read(
56
+ next(n for n in names if n.endswith("METADATA"))
57
+ ).decode()
58
+ requires = sorted(
59
+ line.split(":", 1)[1].strip()
60
+ for line in meta.splitlines()
61
+ if line.startswith("Requires-Dist:")
62
+ )
63
+ print("Requires-Dist:", requires)
64
+ assert requires == ["markdown-it-py", "pyyaml"], (
65
+ f"runtime dependencies drifted: {requires}"
66
+ )
67
+ PY
68
+
69
+ packaging:
70
+ # What ships is not what is committed. hatchling reads `.gitignore`, so a file can be
71
+ # ignored and still tracked — `.coverage` was, and rode into every sdist and every
72
+ # source archive until it was untracked. Both halves are asserted here, on every pull
73
+ # request: the release workflow would only learn after a tag was cut, and a published
74
+ # version cannot be re-uploaded.
75
+ runs-on: ubuntu-latest
76
+ steps:
77
+ - uses: actions/checkout@v7
78
+ - uses: astral-sh/setup-uv@v7
79
+ - run: uv build --sdist
80
+
81
+ - name: Assert the sdist ships exactly the expected top-level entries
82
+ run: |
83
+ python - <<'PY'
84
+ import pathlib, tarfile
85
+ # An allowlist, not a list of forbidden names: a denylist only catches the stray
86
+ # file someone already thought of, and rots silently as new ones appear. Growing
87
+ # a top-level entry is deliberate, so failing here is the review, not the bug.
88
+ ALLOWED = {
89
+ ".github", ".gitignore", "CONTRIBUTING.md", "ENGMEM-SPEC.md", "LICENSE",
90
+ "PKG-INFO", "README.md", "SECURITY.md", "docs", "pyproject.toml",
91
+ "src", "tests", "tools", "uv.lock",
92
+ }
93
+ sdist = next(pathlib.Path("dist").glob("*.tar.gz"))
94
+ top = {
95
+ name.split("/", 1)[1].split("/", 1)[0]
96
+ for name in tarfile.open(sdist).getnames()
97
+ if "/" in name
98
+ }
99
+ assert not top - ALLOWED, f"unexpected entries in the sdist: {sorted(top - ALLOWED)}"
100
+ assert not ALLOWED - top, f"entries missing from the sdist: {sorted(ALLOWED - top)}"
101
+ print(f"{sdist.name}: {len(top)} top-level entries, all expected")
102
+ PY
103
+
104
+ - name: Assert no ignored-class file is tracked
105
+ run: |
106
+ python - <<'PY'
107
+ import subprocess
108
+ tracked = sorted(subprocess.run(
109
+ ["git", "ls-files"], capture_output=True, text=True, check=True
110
+ ).stdout.split())
111
+ # `--no-index` is the point: it answers "would this be ignored", which is exactly
112
+ # the question `git add` never asks again once a file is tracked.
113
+ ignored = subprocess.run(
114
+ ["git", "check-ignore", "--no-index", *tracked],
115
+ capture_output=True, text=True,
116
+ ).stdout.split()
117
+ assert not ignored, f"tracked files that .gitignore says to ignore: {ignored}"
118
+ print(f"{len(tracked)} tracked files, none ignored-class")
119
+ PY
120
+
121
+ # The single check branch protection should require. Requiring the matrix jobs by name
122
+ # means every matrix change silently blocks every PR: the named check stops appearing
123
+ # and nothing reports it.
124
+ ci:
125
+ if: always()
126
+ needs: [test, runtime-dependencies, packaging]
127
+ runs-on: ubuntu-latest
128
+ steps:
129
+ - name: Fail unless every job succeeded
130
+ run: |
131
+ echo "test: ${{ needs.test.result }}"
132
+ echo "runtime-dependencies: ${{ needs.runtime-dependencies.result }}"
133
+ echo "packaging: ${{ needs.packaging.result }}"
134
+ [ "${{ needs.test.result }}" = "success" ] \
135
+ && [ "${{ needs.runtime-dependencies.result }}" = "success" ] \
136
+ && [ "${{ needs.packaging.result }}" = "success" ]
@@ -0,0 +1,114 @@
1
+ name: Release
2
+
3
+ # A tag is the only trigger. `ci.yml` runs on pushes to main and on pull requests,
4
+ # neither of which a tag push is — so this workflow re-runs the suite itself rather
5
+ # than trusting that main was green when the tag was cut.
6
+ on:
7
+ push:
8
+ tags: ["v*"]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ guard:
15
+ # Two things that cannot be undone once uploaded: a version number that does not
16
+ # match its tag, and a release built from a red tree. PyPI refuses a re-upload of
17
+ # an existing version, so both are caught here or not at all.
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+
22
+ - name: Install uv
23
+ uses: astral-sh/setup-uv@v7
24
+ with:
25
+ enable-cache: true
26
+
27
+ - name: Assert the tag matches the version in pyproject.toml
28
+ run: |
29
+ python - <<'PY'
30
+ import os, pathlib, tomllib
31
+ tag = os.environ["GITHUB_REF_NAME"]
32
+ version = tomllib.loads(
33
+ pathlib.Path("pyproject.toml").read_text(encoding="utf-8")
34
+ )["project"]["version"]
35
+ print(f"tag={tag} version={version}")
36
+ assert tag == f"v{version}", (
37
+ f"tag {tag} does not match pyproject version {version} — "
38
+ f"expected v{version}"
39
+ )
40
+ PY
41
+
42
+ - name: Run the test suite
43
+ run: uv run pytest -q
44
+
45
+ build:
46
+ needs: guard
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v7
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@v7
53
+ with:
54
+ enable-cache: true
55
+
56
+ - name: Build the sdist and the wheel
57
+ run: uv build
58
+
59
+ - name: Re-assert the sdist ships exactly the expected top-level entries
60
+ # `ci.yml`'s packaging job owns this rule and runs it on every pull request, which
61
+ # is where it can still be acted on. Repeated here only because the artefact this
62
+ # job uploads is the one that gets published, and a published version is final.
63
+ run: |
64
+ python - <<'PY'
65
+ import pathlib, tarfile
66
+ ALLOWED = {
67
+ ".github", ".gitignore", "CONTRIBUTING.md", "ENGMEM-SPEC.md", "LICENSE",
68
+ "PKG-INFO", "README.md", "SECURITY.md", "docs", "pyproject.toml",
69
+ "src", "tests", "tools", "uv.lock",
70
+ }
71
+ sdist = next(pathlib.Path("dist").glob("*.tar.gz"))
72
+ top = {
73
+ name.split("/", 1)[1].split("/", 1)[0]
74
+ for name in tarfile.open(sdist).getnames()
75
+ if "/" in name
76
+ }
77
+ assert not top - ALLOWED, f"unexpected entries in the sdist: {sorted(top - ALLOWED)}"
78
+ assert not ALLOWED - top, f"entries missing from the sdist: {sorted(ALLOWED - top)}"
79
+ print(f"{sdist.name}: {len(top)} top-level entries, all expected")
80
+ PY
81
+
82
+ - uses: actions/upload-artifact@v7
83
+ with:
84
+ name: dist
85
+ path: dist/
86
+ if-no-files-found: error
87
+
88
+ publish:
89
+ needs: build
90
+ runs-on: ubuntu-latest
91
+
92
+ # Trusted Publishing: PyPI trusts this repository, this workflow file and this
93
+ # environment, and mints a short-lived token per run. There is no long-lived API
94
+ # token in repository secrets, so there is nothing to leak or rotate.
95
+ environment:
96
+ name: pypi
97
+ url: https://pypi.org/p/engmem
98
+
99
+ permissions:
100
+ id-token: write # the OIDC token PyPI verifies; nothing else is needed
101
+
102
+ steps:
103
+ - uses: actions/download-artifact@v8
104
+ with:
105
+ name: dist
106
+ path: dist/
107
+
108
+ - name: Publish to PyPI
109
+ # `release/v1` is a BRANCH, not a tag — deliberately, and it is PyPA's documented
110
+ # form: security fixes reach this job without a manual bump. The cost is that
111
+ # Dependabot has no version to raise, so the one action holding `id-token: write`
112
+ # is also the one action nothing reports on. Read its release notes before trusting
113
+ # a run you did not expect.
114
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,45 @@
1
+ # Build and environment
2
+ .venv/
3
+ dist/
4
+ build/
5
+ *.egg-info/
6
+ __pycache__/
7
+ *.pyc
8
+ .pytest_cache/
9
+ .env
10
+
11
+ # Editors and OS
12
+ .idea/
13
+ .vscode/
14
+ .DS_Store
15
+
16
+ # engmem's own installed output: `engmem install` writes these into the working
17
+ # repository (`--agent copilot-ide`, or `--agent claude --local`). They are generated
18
+ # copies of src/engmem/templates/, so committing them guarantees a stale duplicate.
19
+ .github/prompts/
20
+ .github/copilot-instructions.md
21
+ .claude/
22
+
23
+ # A store must never live inside this repository, but nothing stops one from being
24
+ # pointed here. These are the two things it would leave: the documents themselves, and
25
+ # the search log — which records the queries, and a query quotes the work.
26
+ # Anchored to the root on purpose: unanchored, `sessions/` would also swallow
27
+ # tests/fixtures/sessions/, the golden documents search ranking is defined against.
28
+ /sessions/
29
+ /telemetry.jsonl
30
+
31
+ # Never commit: working notes and raw session transcripts may quote private material
32
+ # from whatever codebase the tool was used on.
33
+ .tmp/
34
+ chat-transcript-*.md
35
+
36
+ # Agent tool configuration: local convenience, not part of the project.
37
+ CLAUDE.md
38
+
39
+ # Coverage databases and stray CI logs: never project content. Adding a pattern
40
+ # here does nothing for a file git already tracks -- `.coverage` was tracked, and
41
+ # shipped in every sdist and source archive until it was untracked.
42
+ .coverage
43
+ .coverage.*
44
+ coverage.xml
45
+ htmlcov/
@@ -0,0 +1,71 @@
1
+ # Contributing
2
+
3
+ ## Getting set up
4
+
5
+ The project uses [uv](https://docs.astral.sh/uv/). Python 3.11 or newer.
6
+
7
+ uv sync --group dev
8
+ uv run pytest -q
9
+
10
+ The test suite is the whole check — it runs offline, needs no fixtures beyond the
11
+ repository, and finishes in seconds.
12
+
13
+ ## Pull requests
14
+
15
+ Branch, push, open a PR against `main`. One check gates the merge: **`ci`**, which
16
+ passes only when the test matrix (Linux, macOS, Windows × Python 3.11–3.13) and the
17
+ runtime-dependency job both succeed.
18
+
19
+ Squash merge is the only merge method; write the PR title as the commit message you
20
+ want in the history.
21
+
22
+ ## Releases
23
+
24
+ The version lives in `pyproject.toml` only; `engmem.__version__` reads it from the
25
+ installed package metadata. Until 1.0 every release bumps the patch number:
26
+
27
+ uv version --bump patch
28
+ git commit -am "release: v$(uv version --short)"
29
+ git tag "v$(uv version --short)" && git push --tags
30
+
31
+ The tag triggers `release.yml`, which refuses a tag that does not match `pyproject.toml`,
32
+ re-runs the suite, builds, and publishes to PyPI.
33
+
34
+ ## What the tests enforce
35
+
36
+ Some tests exist to protect invariants rather than behaviour. If one of these fails,
37
+ the fix is usually the change, not the test.
38
+
39
+ - **Two runtime dependencies.** The wheel must require `pyyaml` and `markdown-it-py`
40
+ and nothing else. A new runtime import fails `runtime-dependencies` in CI. Dev-only
41
+ tools belong in the `dev` dependency group.
42
+ - **No network.** `tests/conftest.py` blocks sockets for the entire suite.
43
+ - **stdout is protocol-only.** `test_stdout_is_protocol_only.py` walks the AST and
44
+ fails if anything prints to stdout outside the output layer — the MCP server shares
45
+ that stream, and one stray `print` corrupts every message on it.
46
+ - **Docs match the code.** `test_docs_match_the_code.py` checks that names, constants,
47
+ and CLI flags quoted in the documentation still exist.
48
+ - **Fixtures land as bytes.** `write_file` in `tests/conftest.py` writes exactly the
49
+ text it is given. Text mode translates every `\n` to `\r\n` on Windows, so a test
50
+ pinning a document's line endings, byte-order mark, or exact bytes was handed a
51
+ document it never asked for — and the failure only ever showed up on the Windows leg.
52
+ Where the bytes matter, write them through `write_file` or `write_bytes`; a bare
53
+ `path.write_text(...)` is fine only when the test does not care what lands on disk.
54
+
55
+ ## Fixtures and examples
56
+
57
+ Every fixture, example, and document in this repository is invented. Do not add
58
+ material from real work — not a class name, not a ticket number, not a paraphrased
59
+ incident, in tests or docs alike. If you need a scenario, make one up; the existing
60
+ fixtures under `tests/fixtures/sessions/` show the shape.
61
+
62
+ This is not decorative. The store is designed to hold things people cannot publish,
63
+ so the project itself has to demonstrate the discipline it asks for.
64
+
65
+ ## Style
66
+
67
+ Match the surrounding code. Comments explain why something is not the obvious choice;
68
+ anything a reader can get from the code itself does not need a comment.
69
+
70
+ Documentation is prose, not bullet dumps. If a change alters behaviour described in
71
+ `README.md` or `ENGMEM-SPEC.md`, update it in the same PR.