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.
- engmem-0.1.1/.github/CODEOWNERS +2 -0
- engmem-0.1.1/.github/ISSUE_TEMPLATE/bug_report.yml +41 -0
- engmem-0.1.1/.github/ISSUE_TEMPLATE/config.yml +1 -0
- engmem-0.1.1/.github/ISSUE_TEMPLATE/feature_request.yml +32 -0
- engmem-0.1.1/.github/dependabot.yml +12 -0
- engmem-0.1.1/.github/pull_request_template.md +13 -0
- engmem-0.1.1/.github/workflows/ci.yml +136 -0
- engmem-0.1.1/.github/workflows/release.yml +114 -0
- engmem-0.1.1/.gitignore +45 -0
- engmem-0.1.1/CONTRIBUTING.md +71 -0
- engmem-0.1.1/ENGMEM-SPEC.md +729 -0
- engmem-0.1.1/LICENSE +21 -0
- engmem-0.1.1/PKG-INFO +282 -0
- engmem-0.1.1/README.md +236 -0
- engmem-0.1.1/SECURITY.md +35 -0
- engmem-0.1.1/docs/.nojekyll +0 -0
- engmem-0.1.1/docs/design/contracts/backfill.md +541 -0
- engmem-0.1.1/docs/design/contracts/cache.md +210 -0
- engmem-0.1.1/docs/design/contracts/gate1.md +563 -0
- engmem-0.1.1/docs/design/contracts/install.md +87 -0
- engmem-0.1.1/docs/design/contracts/mcp-server.md +343 -0
- engmem-0.1.1/docs/design/contracts/output.md +75 -0
- engmem-0.1.1/docs/design/contracts/prompt-templates.md +72 -0
- engmem-0.1.1/docs/design/contracts/runtime.md +121 -0
- engmem-0.1.1/docs/design/contracts/scoring.md +228 -0
- engmem-0.1.1/docs/design/contracts/sections.md +161 -0
- engmem-0.1.1/docs/design/data-model.md +199 -0
- engmem-0.1.1/docs/engmem-anatomy.html +2336 -0
- engmem-0.1.1/docs/store-readme.md +45 -0
- engmem-0.1.1/pyproject.toml +53 -0
- engmem-0.1.1/src/engmem/__init__.py +3 -0
- engmem-0.1.1/src/engmem/backfill.py +592 -0
- engmem-0.1.1/src/engmem/cache.py +185 -0
- engmem-0.1.1/src/engmem/cli.py +573 -0
- engmem-0.1.1/src/engmem/gate1.py +248 -0
- engmem-0.1.1/src/engmem/gate1_audit.py +181 -0
- engmem-0.1.1/src/engmem/install.py +581 -0
- engmem-0.1.1/src/engmem/mcp_server.py +1188 -0
- engmem-0.1.1/src/engmem/output.py +426 -0
- engmem-0.1.1/src/engmem/runtime.py +57 -0
- engmem-0.1.1/src/engmem/scoring.py +600 -0
- engmem-0.1.1/src/engmem/sections.py +279 -0
- engmem-0.1.1/src/engmem/spine.py +490 -0
- engmem-0.1.1/src/engmem/staging.py +73 -0
- engmem-0.1.1/src/engmem/telemetry.py +274 -0
- engmem-0.1.1/src/engmem/templates/engmem.save.md +280 -0
- engmem-0.1.1/src/engmem/templates/engmem.save.quick.md +55 -0
- engmem-0.1.1/src/engmem/templates/engmem.start.md +161 -0
- engmem-0.1.1/tests/conftest.py +274 -0
- engmem-0.1.1/tests/fixtures/sessions/1000001-response-cache.md +43 -0
- engmem-0.1.1/tests/fixtures/sessions/broken.md +11 -0
- engmem-0.1.1/tests/fixtures/sessions/maze-render-new.md +45 -0
- engmem-0.1.1/tests/fixtures/sessions/maze-render-old.md +41 -0
- engmem-0.1.1/tests/fixtures/sessions/metrics-query-refactor.md +43 -0
- engmem-0.1.1/tests/fixtures/sessions/mq-message-sweeper.md +43 -0
- engmem-0.1.1/tests/fixtures/sessions/ttl-etag-revalidation-v2.md +46 -0
- engmem-0.1.1/tests/fixtures/sessions/wip-something.md +19 -0
- engmem-0.1.1/tests/fixtures/telemetry.jsonl +16 -0
- engmem-0.1.1/tests/test_backfill.py +1907 -0
- engmem-0.1.1/tests/test_backfill_cli.py +489 -0
- engmem-0.1.1/tests/test_baseline_cost.py +148 -0
- engmem-0.1.1/tests/test_cache.py +442 -0
- engmem-0.1.1/tests/test_cli.py +982 -0
- engmem-0.1.1/tests/test_commonmark_parity.py +65 -0
- engmem-0.1.1/tests/test_degraded_spine.py +420 -0
- engmem-0.1.1/tests/test_docs_match_the_code.py +279 -0
- engmem-0.1.1/tests/test_encoding.py +47 -0
- engmem-0.1.1/tests/test_fixture_writer.py +29 -0
- engmem-0.1.1/tests/test_gate1.py +407 -0
- engmem-0.1.1/tests/test_gate1_audit.py +594 -0
- engmem-0.1.1/tests/test_gate1_report.py +738 -0
- engmem-0.1.1/tests/test_install.py +943 -0
- engmem-0.1.1/tests/test_lifecycle.py +111 -0
- engmem-0.1.1/tests/test_mcp_server.py +1437 -0
- engmem-0.1.1/tests/test_mcp_subprocess.py +383 -0
- engmem-0.1.1/tests/test_mcp_write_tools.py +1290 -0
- engmem-0.1.1/tests/test_no_network.py +117 -0
- engmem-0.1.1/tests/test_output.py +1416 -0
- engmem-0.1.1/tests/test_runtime.py +270 -0
- engmem-0.1.1/tests/test_scoring.py +1221 -0
- engmem-0.1.1/tests/test_sections.py +614 -0
- engmem-0.1.1/tests/test_spine.py +910 -0
- engmem-0.1.1/tests/test_staging.py +132 -0
- engmem-0.1.1/tests/test_stdout_is_protocol_only.py +78 -0
- engmem-0.1.1/tests/test_stray_docs.py +170 -0
- engmem-0.1.1/tests/test_telemetry.py +642 -0
- engmem-0.1.1/tests/test_uninstall.py +563 -0
- engmem-0.1.1/tests/test_verify_citations.py +262 -0
- engmem-0.1.1/tools/baseline_cost.py +83 -0
- engmem-0.1.1/tools/gate1_report.py +210 -0
- engmem-0.1.1/tools/verify_citations.py +68 -0
- engmem-0.1.1/uv.lock +163 -0
|
@@ -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
|
engmem-0.1.1/.gitignore
ADDED
|
@@ -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.
|