pagelore 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.
- pagelore-0.4.0/.gitignore +15 -0
- pagelore-0.4.0/AGENTS.md +39 -0
- pagelore-0.4.0/CHANGELOG.md +601 -0
- pagelore-0.4.0/LICENSE +21 -0
- pagelore-0.4.0/PKG-INFO +568 -0
- pagelore-0.4.0/README.md +542 -0
- pagelore-0.4.0/pyproject.toml +80 -0
- pagelore-0.4.0/src/pagelore/__init__.py +11 -0
- pagelore-0.4.0/src/pagelore/__main__.py +22 -0
- pagelore-0.4.0/src/pagelore/cli.py +169 -0
- pagelore-0.4.0/src/pagelore/data/AGENT.md +39 -0
- pagelore-0.4.0/src/pagelore/data/page-template.md +19 -0
- pagelore-0.4.0/src/pagelore/doctor.py +116 -0
- pagelore-0.4.0/src/pagelore/index.py +323 -0
- pagelore-0.4.0/src/pagelore/init.py +231 -0
- pagelore-0.4.0/src/pagelore/instructions.py +158 -0
- pagelore-0.4.0/src/pagelore/lib.py +491 -0
- pagelore-0.4.0/src/pagelore/search.py +481 -0
- pagelore-0.4.0/src/pagelore/stats.py +134 -0
- pagelore-0.4.0/src/pagelore/uninstall.py +63 -0
- pagelore-0.4.0/src/pagelore/write.py +480 -0
- pagelore-0.4.0/tests/conftest.py +65 -0
- pagelore-0.4.0/tests/test_concurrency.py +228 -0
- pagelore-0.4.0/tests/test_frontmatter.py +29 -0
- pagelore-0.4.0/tests/test_hostile_store.py +110 -0
- pagelore-0.4.0/tests/test_index.py +267 -0
- pagelore-0.4.0/tests/test_init.py +268 -0
- pagelore-0.4.0/tests/test_instruction_block.py +166 -0
- pagelore-0.4.0/tests/test_log.py +131 -0
- pagelore-0.4.0/tests/test_merge.py +152 -0
- pagelore-0.4.0/tests/test_read_gate.py +102 -0
- pagelore-0.4.0/tests/test_retrieval_quality.py +78 -0
- pagelore-0.4.0/tests/test_search.py +38 -0
- pagelore-0.4.0/tests/test_stats.py +111 -0
- pagelore-0.4.0/tests/test_store_hygiene.py +85 -0
- pagelore-0.4.0/tests/test_store_privacy.py +101 -0
- pagelore-0.4.0/tests/test_supersede.py +143 -0
- pagelore-0.4.0/tests/test_touching.py +138 -0
- pagelore-0.4.0/tests/test_version_is_single_sourced.py +126 -0
- pagelore-0.4.0/tests/test_write.py +37 -0
- pagelore-0.4.0/tests/test_write_validation.py +135 -0
pagelore-0.4.0/AGENTS.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!-- pagelore 0.4.0 — the block an agent reads every turn. Managed by `lore`: it is
|
|
2
|
+
rewritten whenever the installed version changes, so edits here are lost. Put your
|
|
3
|
+
own rules in the file that includes this one. Include it by path rather than copying
|
|
4
|
+
it; a copy goes stale on the next release and nothing says so. -->
|
|
5
|
+
|
|
6
|
+
# Project memory
|
|
7
|
+
|
|
8
|
+
Durable decisions, contracts and bug post-mortems live as markdown pages in `.memory/`. Treat them as the record of why this project looks the way it does.
|
|
9
|
+
|
|
10
|
+
**Before stating anything about this project** — what it is, what it does, how a part of it works, why it is that way, what was decided or rejected — and before changing an unfamiliar subsystem, search first:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
lore search "your query"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The first output line is the store's absolute path; open a full page with `cat <that path>/<slug>.md`. A hit marked `⚠ superseded by <slug>` was replaced — read the replacement first. Add `--touching <path>` to put the pages written against a file you are about to change first. If nothing relevant comes back, say so rather than guessing.
|
|
17
|
+
|
|
18
|
+
The trigger is the kind of claim you are about to make, not the wording of the question; "how does X work" and "what do you know about this project" are memory questions too. This file is not a substitute for the search — it carries instructions rather than reasons, and it goes stale while a page stays dated and sourced. Skip the search only for mechanical work (a command, a typo, a rename) and for general programming questions.
|
|
19
|
+
|
|
20
|
+
**After an architectural decision, a non-obvious bugfix, or a contract change**, write the page:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
lore write --slug short-kebab-slug --title "One line" --kind decision \
|
|
24
|
+
--source path/to/file --body - <<'PMEOF'
|
|
25
|
+
## Cause
|
|
26
|
+
|
|
27
|
+
What a future agent could not reconstruct from the code...
|
|
28
|
+
PMEOF
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`--kind` is one of `decision`, `bug`, `concept`, `howto`. The terminator is `PMEOF`, not `EOF`, so a page that documents heredocs cannot end its own body early. When a decision reverses an earlier one, add `--supersedes <old-slug>`: that stamps the old page and demotes it, instead of leaving two pages that both read as current.
|
|
32
|
+
|
|
33
|
+
The command validates and rejects: no sources, a source path that does not exist, a resulting page too short to be worth keeping. A rejection exits non-zero and prints a `FIX:` line with the command to run instead — follow it rather than writing the markdown file by hand.
|
|
34
|
+
|
|
35
|
+
Re-running the same slug replaces same-header sections in place and appends new ones, so amendments are cheap and safe.
|
|
36
|
+
|
|
37
|
+
Skip this for typos, reverts, formatting and test-only edits. Before you report the work done, ask whether it changed three or more files; if so, write the page or say in one line that there is nothing worth keeping. One topic per page; cross-link with `[[other-slug]]`.
|
|
38
|
+
|
|
39
|
+
`lore` is on PATH and works from any directory; `lore stats` says what the store has been doing. If the shell answers `lore: command not found`, the program is not on this shell's PATH — say so rather than writing pages by hand, because a page written around the command is skipped by search until someone rewrites it.
|
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
5
|
+
versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.4.0] - 2026-09-19
|
|
10
|
+
|
|
11
|
+
Read the first two entries before you upgrade. The second one describes a failure
|
|
12
|
+
that produces no error message at all.
|
|
13
|
+
|
|
14
|
+
### Your notes are safe
|
|
15
|
+
|
|
16
|
+
Nothing about `.memory/` changed — pages, frontmatter, the log and the index are
|
|
17
|
+
byte-compatible, and `lore search` finds every page you already have. There is
|
|
18
|
+
nothing to migrate and nothing to export.
|
|
19
|
+
|
|
20
|
+
### If you used the `@…/USE.md` include line, your agent will silently load nothing
|
|
21
|
+
|
|
22
|
+
That file lived in the skill directory, and the skill directory is gone. An `@path`
|
|
23
|
+
pointing at a file that does not exist produces **no error in any harness** — the
|
|
24
|
+
agent simply stops searching, and nothing connects that to the upgrade. If your
|
|
25
|
+
`CLAUDE.md` or `GEMINI.md` contains a line like
|
|
26
|
+
|
|
27
|
+
@~/.agents/skills/project-memory/USE.md
|
|
28
|
+
|
|
29
|
+
it is now dead. Fix:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
sh install.sh --uninstall # takes out the old block; or edit the line out yourself
|
|
33
|
+
pipx install pagelore
|
|
34
|
+
lore init # writes the new block and offers the new line
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`lore init` recognises the old marker and replaces that block rather than adding a
|
|
38
|
+
second one, so forgetting the first step costs you nothing. `lore doctor` reports
|
|
39
|
+
an include pointing at a file that is gone.
|
|
40
|
+
|
|
41
|
+
### If you pasted the block's text instead, you get a loud error
|
|
42
|
+
|
|
43
|
+
Codex and Cursor users pasted the text rather than an import. That copy still tells
|
|
44
|
+
the agent to run a script path that no longer exists, so the agent gets
|
|
45
|
+
`No such file or directory` and can tell you about it. Same fix: `lore init`, then
|
|
46
|
+
replace the pasted block.
|
|
47
|
+
|
|
48
|
+
### Installing it
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pipx install pagelore # Python, no Node needed
|
|
52
|
+
npm install -g pagelore # Node, no pip needed — it vendors the Python
|
|
53
|
+
lore init
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`lore init` writes the instruction block, then asks which agent should use it and
|
|
57
|
+
shows the exact line and file before changing anything. Its default connects
|
|
58
|
+
nothing. Inside a git repository it also asks whether this project's pages are
|
|
59
|
+
private or tracked.
|
|
60
|
+
|
|
61
|
+
`install.sh` and the `curl … | bash` one-liner still exist, because a published URL
|
|
62
|
+
cannot be recalled and deleting the file would pipe GitHub's 404 page into a shell.
|
|
63
|
+
They install nothing now: they print these commands, still run the old
|
|
64
|
+
`--uninstall` path, and exit 1 so a pipeline fails loudly. Scheduled for deletion
|
|
65
|
+
in 0.6.0.
|
|
66
|
+
|
|
67
|
+
### Why the name changed
|
|
68
|
+
|
|
69
|
+
The package is `pagelore` and the command is `lore` because `project-memory` and
|
|
70
|
+
`pm` are both already taken on PyPI **and** on npm, by unrelated products in the
|
|
71
|
+
same niche. Not churn — there was no free name to keep.
|
|
72
|
+
|
|
73
|
+
### Removed: plugin and extension installs
|
|
74
|
+
|
|
75
|
+
The skill directory, all six plugin manifests and the Gemini extension file are
|
|
76
|
+
gone; there is no Agent Skills packaging any more. If you installed it that way,
|
|
77
|
+
that install is separate and this release cannot reach it:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Claude Code /plugin uninstall project-memory
|
|
81
|
+
Gemini CLI gemini extensions uninstall project-memory
|
|
82
|
+
Codex, Cursor, Kimi remove the directory you pointed them at
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The marketplace entry is gone too, so `/plugin marketplace add Krowli/project-memory`
|
|
86
|
+
now 404s. Tag `v0.3.5` is the final plugin release and stays installable.
|
|
87
|
+
|
|
88
|
+
**This cost something measurable, and here is the number.** A registered skill
|
|
89
|
+
directory gave a harness a description to index, and that description alone made
|
|
90
|
+
the agent search before answering in **15 of 15** sessions with no instruction line
|
|
91
|
+
anywhere. With nothing registered and no line, it is **0 of 15**. So the packaging
|
|
92
|
+
was not dead weight; it was a working fallback for the user who installs and then
|
|
93
|
+
connects nothing. The recommended setup is 15/15 either way, which is why the trade
|
|
94
|
+
was made, and three things now cover the gap that did not exist before: `lore init`
|
|
95
|
+
runs at install time and offers the line, `lore doctor` calls an unconnected
|
|
96
|
+
install a fault rather than a neutral state, and a bare `lore` says so in one line.
|
|
97
|
+
|
|
98
|
+
### What you get for it
|
|
99
|
+
|
|
100
|
+
- The command an agent runs is `lore search "…"`, with no path in it. It is correct
|
|
101
|
+
in every layout instead of in one of three. A user hit exactly this in 0.3.4: a
|
|
102
|
+
project-scoped install, and the documented verification command answered
|
|
103
|
+
`No such file or directory`. The instruction block carried three sentences
|
|
104
|
+
explaining which of three layouts you might be in; they are deleted.
|
|
105
|
+
- You can grant an agent `Bash(lore:*)` instead of `Bash(python3:*)` — one program
|
|
106
|
+
rather than arbitrary Python.
|
|
107
|
+
- Updates arrive with `pipx upgrade pagelore`. Twice this month a change landed on
|
|
108
|
+
`main` and reached zero users because no manifest was bumped.
|
|
109
|
+
- The block that makes an agent search is refreshed by every `lore` invocation, so
|
|
110
|
+
a new release does not need you to re-copy anything.
|
|
111
|
+
- `lore doctor` names the three states that used to be invisible: a dangling
|
|
112
|
+
include, a stale pasted copy, a leftover skill directory.
|
|
113
|
+
- `lore uninstall` takes the block back out. `pipx uninstall` cannot, because it
|
|
114
|
+
never learns about a file this program edited.
|
|
115
|
+
|
|
116
|
+
### Changed, under the hood
|
|
117
|
+
|
|
118
|
+
- One literal in `src/pagelore/__init__.py` is the version. It used to live in ten
|
|
119
|
+
hand-edited places guarded by five drift tests; `npm/package.json` carries the
|
|
120
|
+
only remaining copy, because npm cannot read a Python file, and both the packer
|
|
121
|
+
and a test refuse a mismatch.
|
|
122
|
+
- The built distribution contains the program. Until now it never had: `py-modules
|
|
123
|
+
= []` in `pyproject.toml` silenced an auto-discovery failure, and every published
|
|
124
|
+
artefact held LICENSE, README, pyproject and ten test files. CI now opens the
|
|
125
|
+
wheel and asserts the modules and data files are inside it, installs it with
|
|
126
|
+
pipx on three operating systems, and uses it the way a user does.
|
|
127
|
+
- Seven tests that checked whether one hard-coded script path resolved in three
|
|
128
|
+
documented install layouts are deleted, not replaced. That class of test cannot
|
|
129
|
+
exist any more, which is the clearest single argument for the repackage.
|
|
130
|
+
- The 15 pages in this repository's own store had their `sources:` repointed by
|
|
131
|
+
`tools/retarget_sources.py` rather than through `lore write`, because
|
|
132
|
+
`write_page` unions sources and never removes one — amending through the command
|
|
133
|
+
would have left every page citing both the live path and the dead one, exit 0,
|
|
134
|
+
silently. `updated:` was not bumped: the content did not change that day, and the
|
|
135
|
+
date feeds ranking.
|
|
136
|
+
|
|
137
|
+
### Added
|
|
138
|
+
|
|
139
|
+
- **`lore init`, `lore doctor`, `lore uninstall`.** Interactivity in `init` is an
|
|
140
|
+
injectable parameter defaulting to `isatty()`, so the questions are tested in
|
|
141
|
+
process with one pty test proving the default wiring. The shell installer this
|
|
142
|
+
replaces shipped a dead prompt — a question placed after the value it decided —
|
|
143
|
+
and it passed review because nothing could reach that branch.
|
|
144
|
+
- **`npm install -g pagelore`**, a shim that finds an interpreter and hands it the
|
|
145
|
+
vendored Python. No pip, no `postinstall`, so `--ignore-scripts` and a corporate
|
|
146
|
+
registry mirror both work. `python -m pagelore` exits 69 below the Python floor
|
|
147
|
+
and the shim reads that as "try the next candidate", printing one message after
|
|
148
|
+
it has tried them all and naming each. Candidate order is platform-specific
|
|
149
|
+
because Windows has no `python3`. `PROJECT_MEMORY_PYTHON` is exclusive: a named
|
|
150
|
+
interpreter that does not work fails loudly instead of falling back silently.
|
|
151
|
+
- **`evals/speed.py`**, so the timing table in the README is reproducible. The
|
|
152
|
+
earlier figures came from an uncommitted script and are superseded rather than
|
|
153
|
+
comparable. One search end to end, 90 pages: 52 ms warm, 76 ms with the index
|
|
154
|
+
refused; 1 000 pages: 69 against 341 ms; 5 000 pages: 139 against 1 507 ms.
|
|
155
|
+
- **`evals/mcp_probe.py`**, a stdio MCP server exposing the two tools, and
|
|
156
|
+
`evals/acceptance.py --pointer mcp|mcp+include` to run a real session against
|
|
157
|
+
it. The CLI was chosen early and never measured against MCP; this settles it
|
|
158
|
+
with numbers rather than opinion. Retrieval quality was never the question —
|
|
159
|
+
the server calls the same functions — so what was measured is whether an agent
|
|
160
|
+
reaches for the memory more reliably when the tools are in its tool list.
|
|
161
|
+
Fifteen sessions per arm: the instruction line 15/15, MCP on Claude Code's
|
|
162
|
+
default settings **0/15**, MCP with `ENABLE_TOOL_SEARCH=false` 15/15, both
|
|
163
|
+
together 15/15. The zero is Claude Code deferring MCP tools behind tool search
|
|
164
|
+
by default, so the single advantage MCP was supposed to have is absent out of
|
|
165
|
+
the box. MCP is not shipped; the probe stays so the question can be re-run.
|
|
166
|
+
|
|
167
|
+
## [0.3.5] - 2026-09-19
|
|
168
|
+
|
|
169
|
+
The final release installed as a copied skill directory. Nothing in the code
|
|
170
|
+
changed; this tag exists so that the install instructions published for 0.3.x keep
|
|
171
|
+
resolving to something, instead of to a 404, after the packaging is deleted.
|
|
172
|
+
|
|
173
|
+
**The program is now installed with a package manager, and the command is `lore`:**
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pipx install pagelore # or: npm install -g pagelore
|
|
177
|
+
lore init
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Your pages are safe and unchanged — `.memory/` is byte-compatible and `lore search`
|
|
181
|
+
finds every page you already have.
|
|
182
|
+
|
|
183
|
+
If you installed 0.3.x, remove it before or after installing the new one; the two
|
|
184
|
+
do not interfere, but leaving the old block in your agent's instruction file leaves
|
|
185
|
+
an `@path` pointing at a directory that is about to disappear, and a dead `@path`
|
|
186
|
+
produces no error in any harness. The agent just stops searching.
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
sh install.sh --uninstall # the shell install
|
|
190
|
+
/plugin uninstall project-memory # Claude Code
|
|
191
|
+
gemini extensions uninstall project-memory # Gemini CLI
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
See the 0.4.0 notes for the full upgrade path and for why the name changed:
|
|
195
|
+
`project-memory` and `pm` were both already taken on PyPI and on npm.
|
|
196
|
+
|
|
197
|
+
## [0.3.4] - 2026-09-19
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
|
|
201
|
+
- **The install asks which agents should use it, and writes the line itself.**
|
|
202
|
+
Printing instructions and leaving the user to carry them out was the step
|
|
203
|
+
people finished the install without taking. It now lists Claude Code, Gemini
|
|
204
|
+
and Codex with the file each one reads, shows the exact path and the exact
|
|
205
|
+
line before touching anything, and writes only after a confirmation. What it
|
|
206
|
+
writes is fenced by marker comments: a second install replaces that block
|
|
207
|
+
rather than stacking another copy, `--uninstall` takes it back out, and
|
|
208
|
+
everything the user wrote around it survives. Declining prints the line to
|
|
209
|
+
add by hand, which is what it used to do unconditionally.
|
|
210
|
+
|
|
211
|
+
### Fixed
|
|
212
|
+
|
|
213
|
+
- **The "install here or everywhere" question could not change anything.** It
|
|
214
|
+
ran after the destination had already been worked out, so both answers led to
|
|
215
|
+
the same directory. It now runs before, and a pty-driven test asserts that
|
|
216
|
+
answering "only this one" puts the skill in the repository and not in the
|
|
217
|
+
home directory — the questions are interactive, so a test that pipes stdin
|
|
218
|
+
proves nothing about them.
|
|
219
|
+
|
|
220
|
+
## [0.3.3] - 2026-09-19
|
|
221
|
+
|
|
222
|
+
### Added
|
|
223
|
+
|
|
224
|
+
- **The install ends by naming the line that makes it work.** It printed where
|
|
225
|
+
the files went and stopped, so someone who ran one command was finished and
|
|
226
|
+
had no way to know they were not: the scripts sit on disk and the agent never
|
|
227
|
+
reaches for them until the instruction block is in its configuration. The
|
|
228
|
+
final message now gives that line with the path filled in for the install it
|
|
229
|
+
just did, the file to put it in for each agent, and the command that undoes
|
|
230
|
+
the whole thing.
|
|
231
|
+
- **It asks whether to install for every project or only for this one**, when
|
|
232
|
+
there is a terminal to answer on and a repository under foot for the second
|
|
233
|
+
option to mean anything. Before, standing inside a project and running it with
|
|
234
|
+
no flags installed globally in silence, which reads as a script with no
|
|
235
|
+
opinion rather than one that made a choice. Piped through CI it stays global
|
|
236
|
+
without stalling.
|
|
237
|
+
|
|
238
|
+
## [0.3.2] - 2026-09-19
|
|
239
|
+
|
|
240
|
+
### Added
|
|
241
|
+
|
|
242
|
+
- **`./install.sh --uninstall`.** There was no way to take the skill off a
|
|
243
|
+
machine, so the only instruction anyone could be given was a pair of
|
|
244
|
+
`rm -rf` typed by hand, next to a directory of the user's own pages. It
|
|
245
|
+
removes the skill directory and the symlink the install made, names every
|
|
246
|
+
path it deletes, and leaves three things while saying so: the `.memory/`
|
|
247
|
+
stores in your projects, the line you added to your agent's instruction file,
|
|
248
|
+
and any agent definition you wrote. A symlink is removed only when it
|
|
249
|
+
resolves to the directory being deleted, so a link of yours pointing
|
|
250
|
+
elsewhere survives. It runs before the git and Python checks, because taking
|
|
251
|
+
a program off a machine must not depend on the toolchain that put it there.
|
|
252
|
+
README documents it.
|
|
253
|
+
|
|
254
|
+
## [0.3.1] - 2026-09-19
|
|
255
|
+
|
|
256
|
+
### Changed
|
|
257
|
+
|
|
258
|
+
- **README documents how an update actually reaches a user**, per install path,
|
|
259
|
+
because it does not reach them by itself: Claude Code leaves auto-update off
|
|
260
|
+
by default for third-party marketplaces, and a `curl` install has no update
|
|
261
|
+
channel at all beyond `./install.sh --check`. Everything in 0.3.0 that came
|
|
262
|
+
after the tag — the shipped instruction block and the 3.9 floor — was
|
|
263
|
+
unreachable for anyone who had already installed, because a user only
|
|
264
|
+
receives an update when the manifest version changes. Hence this release.
|
|
265
|
+
|
|
266
|
+
- **The Python floor drops from 3.11 to 3.9**, which is what a stock macOS
|
|
267
|
+
already ships — until now `install.sh` refused the interpreter at
|
|
268
|
+
`/usr/bin/python3`, so the skill would not run on a Mac without a Python
|
|
269
|
+
installed first. The floor was never earned: the five scripts carry no 3.10 or
|
|
270
|
+
3.11 construct, all of them already use `from __future__ import annotations`,
|
|
271
|
+
and the whole suite passes under 3.9.6. CI now runs 3.9 on Linux and Intel
|
|
272
|
+
macOS alongside 3.11 and 3.13 (GitHub publishes no 3.9 build for arm64 macOS
|
|
273
|
+
or Windows), and a test fails if the four places that declare the floor drift
|
|
274
|
+
apart. Windows, which ships no Python at all, stays a documented prerequisite.
|
|
275
|
+
- README rewritten around the three questions people ask first: is the skill
|
|
276
|
+
global or local (global by default, the store is always per project), how to
|
|
277
|
+
make an agent keep the memory without hooks (one paste into the agent's
|
|
278
|
+
global instruction file, paths per vendor docs), and what was measured, with
|
|
279
|
+
every table and the command that reproduces it.
|
|
280
|
+
- The calibration medians quoted in `SKILL.md` and `references/retrieval.md`
|
|
281
|
+
(5.32 against 5.29) predated the FTS5 index; re-measured with the current
|
|
282
|
+
harness they are 9.93 against 8.73. The conclusion is unchanged: a score
|
|
283
|
+
threshold cannot tell an answerable question from an unanswerable one.
|
|
284
|
+
|
|
285
|
+
## [0.3.0] - 2026-09-16
|
|
286
|
+
|
|
287
|
+
### Added
|
|
288
|
+
|
|
289
|
+
- **`memory_search.py --touching PATH`** — the pages whose `sources` cite that
|
|
290
|
+
file, or anything under that directory, come first, marked `▸ touches <path>`,
|
|
291
|
+
with or without query words. `sources` was the one field every page must carry
|
|
292
|
+
and the one field ranking never read. Measured on a new 50-query `touching`
|
|
293
|
+
set in `evals/corpus.json`: typing the path as words puts the right page first
|
|
294
|
+
38% of the time (nDCG@10 0.545); the flag puts it first by construction
|
|
295
|
+
(0.986, paired +0.441 [+0.349, +0.539]). Without the flag nothing changes,
|
|
296
|
+
and the existing tables did not move. Sources are not in the index, so a
|
|
297
|
+
`--touching` search reads every page.
|
|
298
|
+
- **The write gate runs on read.** A page that arrived around `memory_write.py`
|
|
299
|
+
and is under 200 characters is not ranked; search names it on stderr and in
|
|
300
|
+
`--json` so it can be rewritten through the script. A page with no sources is
|
|
301
|
+
shown, marked `⚠ no sources`. `MIN_BODY` moved to `memory_lib` so writer and
|
|
302
|
+
reader share one floor. Search runs on every agent the skill is installed in,
|
|
303
|
+
which is what makes it the place for the check.
|
|
304
|
+
- Every log line carries the session id Claude Code exports to the Bash tool
|
|
305
|
+
(`CLAUDE_CODE_SESSION_ID`), and `memory_stats.py` reports sessions, sessions
|
|
306
|
+
that searched and never wrote, and writes per session — the write side's
|
|
307
|
+
"did it happen", collected by the scripts themselves.
|
|
308
|
+
- `evals/acceptance.py`: a real session of the agent you name, a question only
|
|
309
|
+
the store answers, and a check of the store's log for the search. The only
|
|
310
|
+
proof that an agent uses the memory unprompted; run it per harness.
|
|
311
|
+
- `docs/research/`: primary-source notes behind decisions, starting with how
|
|
312
|
+
superpowers stays portable across thirteen harnesses.
|
|
313
|
+
|
|
314
|
+
### Removed
|
|
315
|
+
|
|
316
|
+
- **The three Claude Code hooks.** SessionStart announced the memory,
|
|
317
|
+
PreToolUse denied a hand-written page, Stop reminded a session that changed
|
|
318
|
+
files and wrote nothing — and every one existed on one harness while the
|
|
319
|
+
skill claims to work on any. `hooks/` is gone, `install.sh` no longer writes
|
|
320
|
+
`settings.json` (`--no-hook` with it), and the Cursor manifest declares no
|
|
321
|
+
hooks. Announcing the memory is the skill `description`, the context files
|
|
322
|
+
and the `AGENTS.md` snippet, the way superpowers runs on Codex, Devin and
|
|
323
|
+
Grok; the gate moved to read; the reminder became a finishing rule in
|
|
324
|
+
`SKILL.md` and the pointer files. See `.memory/scripts-carry-the-contract-not-hooks.md`.
|
|
325
|
+
|
|
326
|
+
### Changed
|
|
327
|
+
|
|
328
|
+
- `evals/dense_probe.py --static MODEL` runs the hybrid over a model2vec static
|
|
329
|
+
embedding model and reports the cold process cost next to the shipped search.
|
|
330
|
+
Measured with three `potion` models: no hybrid gain clears zero, and the
|
|
331
|
+
cheapest cold start is 527 ms against 86 ms for the whole shipped search — the
|
|
332
|
+
cheap form of the embedding idea is refused on the same grounds as the
|
|
333
|
+
expensive one. See `references/retrieval.md`.
|
|
334
|
+
|
|
335
|
+
## [0.2.2] - 2026-08-18
|
|
336
|
+
|
|
337
|
+
### Fixed
|
|
338
|
+
|
|
339
|
+
- **The hooks were registered as `python3`, which is not a command Windows has.**
|
|
340
|
+
Its installer puts `python`, `py` and `pymanager` on PATH; `python3` exists only
|
|
341
|
+
as an optional versioned alias. So on Windows both hooks silently never ran —
|
|
342
|
+
the agent was never told it had a memory, and the write guard blocked nothing,
|
|
343
|
+
which is precisely the hole it exists to close. `install.sh` now resolves a
|
|
344
|
+
working interpreter (`python3`, then `python`, then `py -3`, each checked for
|
|
345
|
+
3.11+) and writes that one into `settings.json`; `--interpreter` forces a
|
|
346
|
+
choice. It also no longer accepts a Python below 3.11, which the old existence
|
|
347
|
+
check did — on macOS that is `/usr/bin/python3`, still 3.9.
|
|
348
|
+
- **The suite could not have caught it.** Every hook test invoked `sys.executable`
|
|
349
|
+
and never the string the installer writes, and `actions/setup-python` puts a
|
|
350
|
+
`python3` shim on Windows runners, so both layers hid it. A test now takes the
|
|
351
|
+
command out of the generated `settings.json` and runs it through a shell.
|
|
352
|
+
- `hooks/hooks.json` cannot branch per platform, so the plugin path still
|
|
353
|
+
hard-codes `python3`. That limitation is now in the README rather than in a
|
|
354
|
+
surprise, with a test that keeps the two in sync.
|
|
355
|
+
|
|
356
|
+
## [0.2.1] - 2026-08-18
|
|
357
|
+
|
|
358
|
+
### Fixed
|
|
359
|
+
|
|
360
|
+
- **Contended writes terminated a sibling writer on Windows.** The lock decides
|
|
361
|
+
staleness by asking whether the owning process still exists, and used
|
|
362
|
+
`os.kill(pid, 0)` to ask. That is a liveness probe on POSIX and a kill on
|
|
363
|
+
Windows, where every signal but CTRL_C and CTRL_BREAK is delivered by calling
|
|
364
|
+
TerminateProcess. CI caught it as a hung test run, which was the mild version of
|
|
365
|
+
the symptom. Liveness now goes through a platform check — `OpenProcess` on
|
|
366
|
+
Windows, distinguishing "no such process" from "access denied" — and falls back
|
|
367
|
+
to the mtime rule wherever the answer is unknowable. A test pins `os.kill` to
|
|
368
|
+
that one guarded probe.
|
|
369
|
+
- The host identity in a lock file came from `os.uname()`, which does not exist on
|
|
370
|
+
Windows, so every lock there was written and compared as `unknown-host`.
|
|
371
|
+
- **A concurrent search made a write fail outright on Windows.** `os.replace`
|
|
372
|
+
replaces a file regardless of who has it open on POSIX and refuses with
|
|
373
|
+
WinError 5 while any reader holds a handle — and a search reading the store is
|
|
374
|
+
exactly that reader. The replace now retries for a few seconds instead of
|
|
375
|
+
raising.
|
|
376
|
+
- **A process that had exited was reported as running on Windows.** `OpenProcess`
|
|
377
|
+
succeeds for as long as anyone holds a handle to a dead process, so the handle
|
|
378
|
+
alone means nothing; the exit code decides, with 259 (`STILL_ACTIVE`) meaning
|
|
379
|
+
running.
|
|
380
|
+
- **17 of 200 concurrent log lines went missing on Windows.** One `O_APPEND` write
|
|
381
|
+
is atomic against other processes on POSIX and is not on Windows; appends within
|
|
382
|
+
a process are now serialised.
|
|
383
|
+
- Tests that depend on POSIX file modes, `mkfifo` or `geteuid` are skipped on
|
|
384
|
+
Windows rather than faked, so the Windows run reports what it actually covered.
|
|
385
|
+
|
|
386
|
+
## [0.2.0] - 2026-08-18
|
|
387
|
+
|
|
388
|
+
An audit of the skill against its own claims. Four ways a re-run could destroy
|
|
389
|
+
part of a page, a store that could take retrieval down or leak a file it never
|
|
390
|
+
owned, a write gate with an unguarded side door, and a contract naming a script
|
|
391
|
+
path that existed in one install mode out of four.
|
|
392
|
+
|
|
393
|
+
### Fixed
|
|
394
|
+
|
|
395
|
+
- **Re-running a write no longer loses content.** `## ` lines inside a code fence
|
|
396
|
+
are content, not headings — the previous splitter deleted the closing fence and
|
|
397
|
+
everything after it. A body with no heading at all used to be dropped whenever
|
|
398
|
+
the page already had lead-in prose. Sections are replaced in place instead of
|
|
399
|
+
moving to the end of the file, so a one-section amendment reads as one in
|
|
400
|
+
`git diff`. Each of these exited 0 and logged a successful write.
|
|
401
|
+
- **Frontmatter fields the tooling does not own are preserved.** Rebuilding from a
|
|
402
|
+
fixed whitelist silently deleted anything else the page carried, which blocked
|
|
403
|
+
extending the format at all.
|
|
404
|
+
- **The 200-character floor applies to the resulting page, not to the increment.**
|
|
405
|
+
Recording "this was reversed in June, here is why" against an existing page was
|
|
406
|
+
refused — the cheapest and most valuable write in the system.
|
|
407
|
+
- **Concurrent writes to one slug no longer lose sections.** An advisory per-page
|
|
408
|
+
lock plus `os.replace`; measured before the fix, twelve to twenty parallel
|
|
409
|
+
writers lost up to 16 of 20 sections, all exiting 0. A reader can no longer
|
|
410
|
+
observe a half-written page, and log lines cannot interleave.
|
|
411
|
+
- **One undecodable page no longer kills every search in the project.** Pages are
|
|
412
|
+
read with `errors="replace"`.
|
|
413
|
+
- **NFD text is findable.** `\w+` does not match combining marks, so the NFD form
|
|
414
|
+
of `ёлка` tokenised as `['е', 'лка']` and recall across an NFC/NFD boundary was
|
|
415
|
+
zero — on macOS, which produces that form. Text is normalised to NFC and folded
|
|
416
|
+
with `casefold()`, which also covers `STRASSE` / `straße`.
|
|
417
|
+
- **A search no longer creates a store or edits `.gitignore`.** Logging a miss used
|
|
418
|
+
to dirty the working tree of a repository that never opted in. The write path
|
|
419
|
+
still creates and shields the store it needs, including on a refusal.
|
|
420
|
+
- **A page symlinked outside the store is ignored.** `ln -s ../.env
|
|
421
|
+
.memory/env-notes.md` made an ordinary search rank and print a secret.
|
|
422
|
+
- **Only top-level `*.md` files are indexed.** `mkdir archive; mv` used to leave the
|
|
423
|
+
page indexed and return two hits with the same slug.
|
|
424
|
+
- **A store that is a dangling symlink is refused with a `FIX:` line** instead of a
|
|
425
|
+
raw `FileExistsError` — the shape `install.sh --store home` produces if its
|
|
426
|
+
target is gone.
|
|
427
|
+
- `memory_stats.py` reports a real median for even counts and tolerates a log line
|
|
428
|
+
from another writer.
|
|
429
|
+
- **Every command in `CLAUDE.md`, `AGENTS.md` and `GEMINI.md` now runs.** All three
|
|
430
|
+
hard-coded `.agents/skills/…`, which exists only after `install.sh --project` —
|
|
431
|
+
not in a clone, which is the case `CLAUDE.md` says it exists for. A test extracts
|
|
432
|
+
every command from those files and checks it.
|
|
433
|
+
- `install.sh --help` no longer truncates mid-table, hiding two store modes.
|
|
434
|
+
- A missing `--kind` is logged as `no_kind` rather than sharing `bad_kind` with an
|
|
435
|
+
invalid one; the two call for opposite fixes.
|
|
436
|
+
|
|
437
|
+
### Added
|
|
438
|
+
|
|
439
|
+
- **The runtime knows its own version.** `--version` on every script, and the
|
|
440
|
+
session hook tells the agent which version the project is running. A `curl`
|
|
441
|
+
install has no package manager to ask, so until now neither the user nor the
|
|
442
|
+
agent could tell 0.1.0 from 0.2.0 on disk.
|
|
443
|
+
- **`install.sh` installs the latest released tag**, not the tip of `main`, so an
|
|
444
|
+
install is reproducible and a version number means something.
|
|
445
|
+
`PROJECT_MEMORY_REF` still takes a branch or a specific tag, and
|
|
446
|
+
`install.sh --check` reports what is installed against what is released without
|
|
447
|
+
installing anything. A test now guards the `--help` line range, which had
|
|
448
|
+
silently truncated once already.
|
|
449
|
+
|
|
450
|
+
- **A reproducible evaluation, in the repository.** `python3 evals/run.py --by-type`
|
|
451
|
+
over 90 pages and 270 queries with paired bootstrap intervals, plus an ambiguous
|
|
452
|
+
set and an unanswerable set. `evals/gate_value.py` measures what the write gate
|
|
453
|
+
is worth by putting the stubs it refuses back into the corpus. Nothing in
|
|
454
|
+
`references/retrieval.md` is now argued from figures a reader cannot re-run.
|
|
455
|
+
- **A persistent SQLite FTS5 index** (`memory_index.py`), and it is a cache the
|
|
456
|
+
search is allowed to ignore. End to end, as a shell invocation: 235 ms → 99 ms at
|
|
457
|
+
90 pages, 1887 ms → 174 ms at 1000, 4637 ms → 196 ms at 5000. The ~5000-page
|
|
458
|
+
ceiling the documentation used to name is gone. It lives in the cache directory
|
|
459
|
+
rather than the store, uses no WAL, rebuilds whole rather than repairing, elects
|
|
460
|
+
one builder without waiting, and falls back to reading the markdown on any error
|
|
461
|
+
at all. `PROJECT_MEMORY_NO_FTS5=1` forces the fallback, and CI now runs the whole
|
|
462
|
+
suite twice so that path cannot rot.
|
|
463
|
+
- `--json` reports `served_by`, so two agents served by different paths can explain
|
|
464
|
+
a difference in tail ordering rather than wondering about it.
|
|
465
|
+
|
|
466
|
+
### Fixed while measuring
|
|
467
|
+
|
|
468
|
+
- **Turkish `İ` was unfindable by its ASCII spelling**: `casefold` turns it into
|
|
469
|
+
`i` plus a combining dot, which matches nothing anyone types.
|
|
470
|
+
- **The evaluation's own FTS5 baseline was misconfigured** — without `tokenchars
|
|
471
|
+
'_'` the pre-tokenised round trip changed 73 of 486 texts — and `bm25()` weights
|
|
472
|
+
are positional over every column, so passing two weights for a three-column table
|
|
473
|
+
gave the title weight to the unindexed slug and left the title at 1.0. The
|
|
474
|
+
title-weight regression test caught the second one.
|
|
475
|
+
- **The supersession tests were still weak.** The fixture is now an unlinked
|
|
476
|
+
control pair: without the link the obsolete page must rank first, and adding the
|
|
477
|
+
link alone must reverse it. The earlier version guarded a score comparison that
|
|
478
|
+
the ranker change quietly invalidated.
|
|
479
|
+
|
|
480
|
+
### Changed
|
|
481
|
+
|
|
482
|
+
- `references/retrieval.md` reports measured numbers with paired intervals, states
|
|
483
|
+
what the harness cannot tell you, and records the negative result it produced:
|
|
484
|
+
no score or word-overlap threshold can separate a question the store can answer
|
|
485
|
+
from one it cannot (top-hit medians 5.32 against 5.29). `SKILL.md` now tells the
|
|
486
|
+
agent that a result list is not evidence that an answer exists — the previous
|
|
487
|
+
wording, "if search returns nothing relevant, say so", described a case that
|
|
488
|
+
almost never happens.
|
|
489
|
+
|
|
490
|
+
- **`--supersedes <slug>`.** The replaced page is stamped `status: superseded` and
|
|
491
|
+
`superseded_by:`, scored at half its BM25F score and marked in every result
|
|
492
|
+
line. Ranking previously had no recency or authority term and tied
|
|
493
|
+
alphabetically, so a reversed decision could outrank the decision that reversed
|
|
494
|
+
it — the failure the README opens with.
|
|
495
|
+
- **A `PreToolUse` hook that denies a hand-written page** and names
|
|
496
|
+
`memory_write.py` instead. "Writes are refused, not requested" was itself a
|
|
497
|
+
request while the ordinary Write tool could walk around the validator. Escape
|
|
498
|
+
hatch: `PROJECT_MEMORY_ALLOW_HAND_EDIT=1`.
|
|
499
|
+
- **Ranking regression tests.** `W_TITLE` could be set from 5 to 0 — the parameter
|
|
500
|
+
the documentation calls the one that matters — and the whole suite stayed green.
|
|
501
|
+
- Snippets follow the query instead of being the page's first 100 characters, and
|
|
502
|
+
every result line carries the `updated` date; the header line carries the store's
|
|
503
|
+
absolute path, so the documented `cat` works from any directory.
|
|
504
|
+
- `.memory/` in this repository, tracked on purpose. The project had none, across
|
|
505
|
+
fifteen commits of exactly the work it says to record.
|
|
506
|
+
- The session hook fires on `resume` as well, which the test named for it did not
|
|
507
|
+
actually cover.
|
|
508
|
+
|
|
509
|
+
- The documented heredoc terminator is `PMEOF`, not `EOF`: a page documenting
|
|
510
|
+
heredocs ended its own body early and the shell executed the rest of the text.
|
|
511
|
+
- `references/page-format.md` matches the write path — `kind` is required and one
|
|
512
|
+
of four values, `sources` is required, and `note` is gone from both the reference
|
|
513
|
+
and the shipped template.
|
|
514
|
+
- `references/retrieval.md` states that the benchmark's artifacts are not in this
|
|
515
|
+
repository, so its figures are reported rather than reproducible; corrects the
|
|
516
|
+
stub-size units; scopes the latency table to one machine and page size; and
|
|
517
|
+
replaces the claim that `grep` is never cheaper with what it actually is — faster,
|
|
518
|
+
and not an alternative, because it returns an unranked list.
|
|
519
|
+
- README: the injection is ~1.8 KB rather than ~1.3 KB, CI runs on `main` and pull
|
|
520
|
+
requests rather than every push, a user-scope install neither asks about nor
|
|
521
|
+
creates a store, and Contributing names all eight files carrying the version.
|
|
522
|
+
- The read trigger names a class of claim instead of a list of question
|
|
523
|
+
phrasings. Agents read `"why…"` / `"what did we decide…"` as exhaustive and
|
|
524
|
+
answered "what do you know about this project" straight from `AGENTS.md`,
|
|
525
|
+
never searching. It now fires before stating anything about the project —
|
|
526
|
+
what it is, what it does, how a part works, why it is that way.
|
|
527
|
+
- The session hook, `SKILL.md` and the three context files say explicitly that
|
|
528
|
+
`AGENTS.md` / `CLAUDE.md` / `README.md` already in context are not a substitute
|
|
529
|
+
for the search: they carry instructions rather than reasons and they drift,
|
|
530
|
+
while a page stays dated and sourced. Both surfaces also name what does *not*
|
|
531
|
+
need a search — a command, a typo, a rename, a file the user named, general
|
|
532
|
+
programming questions — so the wider trigger does not become a search before
|
|
533
|
+
every turn.
|
|
534
|
+
|
|
535
|
+
### Fixed after an independent audit of the fixes below
|
|
536
|
+
|
|
537
|
+
The changes above were then audited by agents whose task was to break them. What
|
|
538
|
+
they found, all of it now covered by a test that fails when the fix is removed:
|
|
539
|
+
|
|
540
|
+
- **`--supersedes` copied a symlinked file into the store.** Stamping the replaced
|
|
541
|
+
page walked through its path, so `ln -s ../.env .memory/env-notes.md` — blocked
|
|
542
|
+
on the read path — was read and rewritten as a real page containing the secret,
|
|
543
|
+
which the next search then printed. The write path now refuses to touch anything
|
|
544
|
+
that is not a contained regular file.
|
|
545
|
+
- **A nested ```` fence still exposed a quoted heading.** The fence pattern matched
|
|
546
|
+
exactly three backticks, so the first inner ``` closed a longer outer fence —
|
|
547
|
+
and quoting a memory page, the case the fix was written for, requires exactly
|
|
548
|
+
that. Fences are now three *or more* characters, closed CommonMark-style.
|
|
549
|
+
- **A repeated `## ` header in an incoming body lost its first copy** on a merge,
|
|
550
|
+
because the incoming sections were built as a dict. Introduced by the in-place
|
|
551
|
+
replacement fix. Same-header chunks are joined, and only the first stored
|
|
552
|
+
occurrence is replaced, so a page with two identical headers is no longer
|
|
553
|
+
filled with the same text twice.
|
|
554
|
+
- **An orphan lock defeated the locking.** A lock left by a killed writer was not
|
|
555
|
+
stale for thirty seconds, so every other writer stalled for the full timeout and
|
|
556
|
+
then deleted whatever lock it found — including live ones, losing 2 to 4 of 10
|
|
557
|
+
writers' sections. Staleness is now decided by asking whether the owning process
|
|
558
|
+
still exists, and a live holder's lock is never removed.
|
|
559
|
+
- **One hostile entry could take down every search**: a directory named `notes.md`,
|
|
560
|
+
a broken symlink, an unreadable file — and a FIFO did not fail the search, it
|
|
561
|
+
hung it forever. Only readable regular files are indexed now, and the test for
|
|
562
|
+
it runs in a subprocess with a timeout so a regression fails CI instead of
|
|
563
|
+
hanging it.
|
|
564
|
+
- **The write guard was trivially bypassable**: the extension check was
|
|
565
|
+
case-sensitive, so `.memory/page.MD` was allowed and clobbers `page.md` on a
|
|
566
|
+
case-insensitive filesystem; `page` with no extension was allowed too; and the
|
|
567
|
+
documented `--store home` mode has no `.memory` in its path at all, so it was
|
|
568
|
+
entirely unguarded. It now covers every file in a store, resolves symlinks, and
|
|
569
|
+
stops over-blocking `.memory/../src/x.ts`.
|
|
570
|
+
- **The supersession tests were vacuous.** The whole mechanism could be deleted and
|
|
571
|
+
the suite stayed green, because both fixture pages had the same title and body.
|
|
572
|
+
The fixture now asserts that it is a real inversion before testing the fix.
|
|
573
|
+
- **The session-hook budget was only ever measured from the short in-repo path**; a
|
|
574
|
+
normal install location pushed it to 2201 characters against its own 2000 limit.
|
|
575
|
+
The text is shorter and the prose is bounded separately from the path.
|
|
576
|
+
- Self-supersession and supersession cycles are refused; an empty `--body` is
|
|
577
|
+
refused rather than silently bumping `updated:`; a read-only store produces a
|
|
578
|
+
refusal instead of a traceback; a killed write leaves no `.tmp` behind; a title
|
|
579
|
+
ending in a quote no longer loses that character on every rewrite; and
|
|
580
|
+
`memory_stats.py` reports a true median.
|
|
581
|
+
|
|
582
|
+
## [0.1.0] - 2026-08-08
|
|
583
|
+
|
|
584
|
+
### Added
|
|
585
|
+
- `project-memory` skill (`SKILL.md`) with read-before-answer and
|
|
586
|
+
write-after-work workflows.
|
|
587
|
+
- `memory_search.py` — ranked search over the markdown store.
|
|
588
|
+
- `memory_write.py` — create/section-merge pages with stable frontmatter.
|
|
589
|
+
- Claude Code plugin and marketplace manifests.
|
|
590
|
+
- Test suite covering search, writing, frontmatter tolerance and manifests.
|
|
591
|
+
|
|
592
|
+
[Unreleased]: https://github.com/Krowli/project-memory/compare/v0.3.4...HEAD
|
|
593
|
+
[0.3.4]: https://github.com/Krowli/project-memory/compare/v0.3.3...v0.3.4
|
|
594
|
+
[0.3.3]: https://github.com/Krowli/project-memory/compare/v0.3.2...v0.3.3
|
|
595
|
+
[0.3.2]: https://github.com/Krowli/project-memory/compare/v0.3.1...v0.3.2
|
|
596
|
+
[0.3.1]: https://github.com/Krowli/project-memory/compare/v0.3.0...v0.3.1
|
|
597
|
+
[0.3.0]: https://github.com/Krowli/project-memory/compare/v0.2.2...v0.3.0
|
|
598
|
+
[0.2.2]: https://github.com/Krowli/project-memory/compare/v0.2.1...v0.2.2
|
|
599
|
+
[0.2.1]: https://github.com/Krowli/project-memory/compare/v0.2.0...v0.2.1
|
|
600
|
+
[0.2.0]: https://github.com/Krowli/project-memory/compare/v0.1.0...v0.2.0
|
|
601
|
+
[0.1.0]: https://github.com/Krowli/project-memory/releases/tag/v0.1.0
|