hippocamp 0.2.4 → 0.2.6
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.
- package/AGENTS.md +3 -1
- package/README.md +29 -312
- package/docs/development.md +34 -0
- package/docs/dream.md +46 -0
- package/docs/how-it-works.md +119 -0
- package/docs/results.md +59 -0
- package/docs/setup.md +65 -0
- package/package.json +1 -1
- package/scripts/hippocamp-mcp.cjs +1 -1
- package/scripts/install-common.cjs +14 -5
- package/test/install-common.test.cjs +21 -4
- package/test/package-paths.test.cjs +12 -0
package/AGENTS.md
CHANGED
|
@@ -38,6 +38,8 @@ This repository is a local-first MCP package for Git-backed agent memory. Keep c
|
|
|
38
38
|
- `assets/railway/run-dream.sh`: short-lived hosted Dream runner that clones Lagoon and opens one Dream PR per project.
|
|
39
39
|
- `Dockerfile.railway` and `railway.json`: optional Railway cron deployment for overnight Dream runs.
|
|
40
40
|
- `assets/brand/hippocamp-mascot.png`: README mascot asset.
|
|
41
|
+
- `assets/brand/hippocamp-demo.gif`: README demo, loaded from GitHub and kept out of the npm package by `.npmignore` (keep `.npmignore` in sync with `.gitignore`).
|
|
42
|
+
- `docs/`: user docs (setup, how it works, Dream, results, development). The README stays a short overview that links to them.
|
|
41
43
|
|
|
42
44
|
## Project Invariants
|
|
43
45
|
|
|
@@ -62,7 +64,7 @@ This repository is a local-first MCP package for Git-backed agent memory. Keep c
|
|
|
62
64
|
|
|
63
65
|
- Follow the existing style and keep changes surgical.
|
|
64
66
|
- Do not add abstractions, config knobs, or new subsystems unless the task explicitly requires them.
|
|
65
|
-
- If behavior changes, update `README.md` and this file only when operational guidance actually changes.
|
|
67
|
+
- If behavior changes, update the matching page in `docs/`, `README.md` only when the overview changes, and this file only when operational guidance actually changes.
|
|
66
68
|
- Prefer extending `scripts/hippocamp-memory.cjs` over duplicating path, sync, or search logic.
|
|
67
69
|
- Keep the product narrow. Simpler is better in this repo.
|
|
68
70
|
|
package/README.md
CHANGED
|
@@ -1,339 +1,56 @@
|
|
|
1
1
|
# Hippocamp
|
|
2
2
|
|
|
3
|
-
[](https://github.com/guillaumegay13/hippocamp/actions/workflows/ci.yml)
|
|
3
|
+
[](https://www.npmjs.com/package/hippocamp) [](https://github.com/guillaumegay13/hippocamp/actions/workflows/ci.yml)
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<img src="./assets/brand/hippocamp-mascot.png" alt="Hippocamp mascot" width="220" />
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
-
Agent memory
|
|
9
|
+
Agent memory, made simple.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Plain Markdown in your own Git repo, shared by all your coding agents, like Claude Code and Codex. One command to install. No API key, no database, no server to run.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Every session starts by waking up from it, and every decision an agent records is a commit you can read, diff, and revert. Open source (MIT).
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npx hippocamp@latest install
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
This installs Hippocamp into every supported agent
|
|
19
|
+
This installs Hippocamp into every supported agent it finds and creates your memory repo, `~/.lagoon`. Restart your agent and it starts each task by waking up from memory.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+

|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
A replay of a real session. Neither prompt mentions Hippocamp.
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
- **History you can audit.** Every memory change is a Git commit with an author, a diff, and a timestamp. Roll back a bad memory like bad code.
|
|
27
|
-
- **Files you own.** Markdown in your own private repo, readable without any UI, portable across machines through your normal Git credentials.
|
|
28
|
-
- **Nothing to sign up for.** Open source, local, free. No account, no API key, no vendor that can change terms or shut down.
|
|
29
|
-
- **Reviewed compaction.** Optional Dream mode summarizes memory offline and proposes the result as a pull request.
|
|
25
|
+
## Why Hippocamp?
|
|
30
26
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
LongMemEval-S, all 470 answerable questions, top 5 results, no model involved (`npm run eval:retrieval`).
|
|
38
|
-
|
|
39
|
-
| Measure | Hippocamp | BM25 reference |
|
|
40
|
-
| --- | ---: | ---: |
|
|
41
|
-
| Right session in top 5 (Recall@5) | 90.5% | 91.4% |
|
|
42
|
-
| At least one right session in top 5 (Hit@5) | 96.0% | 96.8% |
|
|
43
|
-
| Returned text contains the answer turn | 87.9% | n/a |
|
|
44
|
-
| Text returned per query | 5.9k chars | 65.5k chars |
|
|
45
|
-
| Search latency, cold index | 42 ms | n/a |
|
|
46
|
-
|
|
47
|
-
BM25 returns whole sessions; Hippocamp returns the matching paragraphs of each result.
|
|
27
|
+
- **Written by the agent itself.** The agent doing the work decides what is worth keeping, in its own words. Unlike memory services that run a second model to extract facts, nothing rewrites it behind your back: events are saved as written, and optional Dream compaction of the two summary files arrives as a pull request you review.
|
|
28
|
+
- **One memory for every agent.** Claude Code, Codex, and your other agents share the same repo. Built-in memory, like Claude Code's or Codex's, stays inside one tool.
|
|
29
|
+
- **History you can audit.** Every memory is a Git commit. Roll back a bad memory like bad code.
|
|
30
|
+
- **Files you own.** Plain Markdown, readable without any UI, synced across machines with your normal Git credentials.
|
|
31
|
+
- **Nothing to sign up for.** No vendor that can change terms or shut down.
|
|
48
32
|
|
|
49
|
-
|
|
33
|
+
## Results
|
|
50
34
|
|
|
51
|
-
|
|
35
|
+
Reproducible with the scripts in this repo.
|
|
52
36
|
|
|
53
|
-
|
|
|
37
|
+
| | Hippocamp | Reference |
|
|
54
38
|
| --- | ---: | ---: |
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
59
|
-
The same reader reaches 90% with the correct sessions, so the remaining gap is retrieval, not the reader.
|
|
60
|
-
|
|
61
|
-
### Real coding questions
|
|
62
|
-
|
|
63
|
-
53 questions about real work in a personal Lagoon across 13 projects (`npm run eval:lagoon`). This is our own test set, kept in the private Lagoon.
|
|
64
|
-
|
|
65
|
-
| Measure | Result |
|
|
66
|
-
| --- | ---: |
|
|
67
|
-
| Right event in top 5, 43 answerable questions | 42 / 43 |
|
|
68
|
-
| No results for 10 questions no memory is about | 10 / 10 |
|
|
69
|
-
|
|
70
|
-
### Why less text matters
|
|
71
|
-
|
|
72
|
-
Memory results share the agent's context with code, diffs, and tool output, and agents search several times per task. About 6k characters per search is roughly 1.5k tokens; whole sessions would be about 16k. Fewer tokens make each turn cheaper and faster, and keep the relevant lines from being buried. Less text is only useful when the answer is still in it, which is why we report answer accuracy next to text size.
|
|
73
|
-
|
|
74
|
-
### Limits
|
|
75
|
-
|
|
76
|
-
- Recall@5 is just under the BM25 reference, by less than 1 point, with 11 times less text.
|
|
77
|
-
- No embeddings or semantic search: a memory written in other words can be missed. On LongMemEval-S, 9 of 470 questions miss with no word in common with the answer.
|
|
78
|
-
- Answer accuracy is measured on 100 questions, about plus or minus 4 points.
|
|
79
|
-
- Self-reported scores from other memory systems use their own readers and graders, so they are not directly comparable with these.
|
|
80
|
-
|
|
81
|
-
## What It Stores
|
|
82
|
-
|
|
83
|
-
- durable preferences and working style
|
|
84
|
-
- current project state and open threads
|
|
85
|
-
- meaningful events and decisions
|
|
86
|
-
- references to commits, PRs, issues, reviews, and CI instead of duplicated GitHub facts
|
|
87
|
-
|
|
88
|
-
## Why Git
|
|
89
|
-
|
|
90
|
-
Git is a practical default for agent memory:
|
|
91
|
-
|
|
92
|
-
- Auditable: every memory change has a diff, author, timestamp, and commit history.
|
|
93
|
-
- Readable: memory stays as Markdown files that humans and agents can inspect without a special UI.
|
|
94
|
-
- Collaborative: multiple agents and humans can review, branch, merge, and roll back the same memory repo.
|
|
95
|
-
- Portable: a private remote lets the same memory follow you across machines and agent environments.
|
|
96
|
-
- Agent-native: coding agents are already connected to GitHub through MCP, CLIs, or local Git credentials, so Hippocamp does not need an external database, hosted dependency, or separate token setup.
|
|
97
|
-
|
|
98
|
-
## Install
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
npx hippocamp@latest install
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
To install into one agent only:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
npx hippocamp@latest install-claude
|
|
108
|
-
npx hippocamp@latest install-codex
|
|
109
|
-
npx hippocamp@latest install-grok
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Upgrade with the same command: `npx hippocamp@latest upgrade`. Installers default to `~/.lagoon` as the memory repo; pass `--global-root /absolute/path/to/lagoon` to use another clone.
|
|
113
|
-
|
|
114
|
-
Installed through npx, the agent starts the server with `npx -y --prefer-offline hippocamp@<version> mcp`, so it keeps working if npm cleans its cache. From a source checkout, `npm install` then `npm run install:claude` (or `install:codex`, `install:grok`) registers the checkout directly.
|
|
115
|
-
|
|
116
|
-
The Codex installer refreshes `~/.codex/AGENTS.md`. The Claude installer refreshes `~/.claude/CLAUDE.md`. The Grok installer refreshes `~/.grok/rules/hippocamp.md` and registers the MCP server with `HIPPOCAMP_AGENT=grok`.
|
|
117
|
-
Managed instruction blocks tell the agent to call `wake_up` at the start of new top-level coding tasks before repo exploration or edits.
|
|
118
|
-
|
|
119
|
-
## Lagoon Repo
|
|
120
|
-
|
|
121
|
-
Hippocamp keeps memory in a local Git repo. The installer creates `~/.lagoon` if it is missing. To use memory you already have on another machine, clone it first:
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
git clone git@github.com:YOUR_USER/lagoon.git ~/.lagoon
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
The repo should usually be private. Pushing memory to a private remote keeps it available across machines and agent environments while still using normal Git access controls. Hippocamp does not need a GitHub token for local MCP mode; it uses your normal local Git credentials.
|
|
128
|
-
|
|
129
|
-
Memory works locally without a remote. To back it up privately and share it across machines, create a private remote:
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
gh repo create lagoon --private --source ~/.lagoon --remote origin --push
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
If push auth is not configured yet, use your preferred GitHub setup. With GitHub CLI:
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
gh auth login
|
|
139
|
-
gh auth setup-git
|
|
140
|
-
cd ~/.lagoon
|
|
141
|
-
git push --dry-run
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
Once `git push --dry-run` works from `~/.lagoon`, Hippocamp can sync memory.
|
|
145
|
-
|
|
146
|
-
## Memory Layout
|
|
147
|
-
|
|
148
|
-
Global memory lives in:
|
|
149
|
-
|
|
150
|
-
```text
|
|
151
|
-
~/.lagoon/
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Project memory lives in:
|
|
155
|
-
|
|
156
|
-
```text
|
|
157
|
-
~/.lagoon/projects/<project-slug>/
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Suggested files:
|
|
161
|
-
|
|
162
|
-
```text
|
|
163
|
-
identity.md
|
|
164
|
-
how_i_work.md
|
|
165
|
-
preferences.md
|
|
166
|
-
open_loops.md
|
|
167
|
-
events/YYYY-MM-DD.md
|
|
168
|
-
events/YYYY-MM-DD.index.json
|
|
169
|
-
projects/<project-slug>/
|
|
170
|
-
project.md
|
|
171
|
-
current_state.md
|
|
172
|
-
open_threads.md
|
|
173
|
-
events/YYYY-MM-DD.md
|
|
174
|
-
events/YYYY-MM-DD.index.json
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
## MCP Tools
|
|
178
|
-
|
|
179
|
-
The local MCP server exposes:
|
|
180
|
-
|
|
181
|
-
- `wake_up`
|
|
182
|
-
- `read_memory_file`
|
|
183
|
-
- `write_memory_file`
|
|
184
|
-
- `append_event`
|
|
185
|
-
- `list_memory_files`
|
|
186
|
-
- `search_memory`
|
|
187
|
-
- `sync_memory`
|
|
188
|
-
|
|
189
|
-
Typical agent flow:
|
|
190
|
-
|
|
191
|
-
1. Call `wake_up` at the start of a top-level task.
|
|
192
|
-
2. Read the returned global and project memory.
|
|
193
|
-
3. Use `search_memory` for task-specific recall after wake-up.
|
|
194
|
-
4. Use `append_event` for meaningful milestones, with a title and 1-8 concise cues.
|
|
195
|
-
5. Update curated files like `current_state.md` and `open_threads.md` before finishing.
|
|
196
|
-
|
|
197
|
-
Writes sync by default. If sync fails or is skipped, call `sync_memory`.
|
|
198
|
-
|
|
199
|
-
Event writes update a sibling `events/YYYY-MM-DD.index.json` file. The Markdown event remains the canonical memory; the sidecar is rebuildable. Search ranks indexed events and curated files with BM25 and typo tolerance ([MiniSearch](https://github.com/lucaong/minisearch)), weighting cues above titles and titles above bodies. It returns small coherent evidence blocks from indexed events. If no indexed result is strong enough, it returns no event result instead of scanning whole event logs.
|
|
200
|
-
|
|
201
|
-
## Dream
|
|
202
|
-
|
|
203
|
-
Dream is offline compaction for project wake-up context. It rewrites only:
|
|
204
|
-
|
|
205
|
-
```text
|
|
206
|
-
projects/<project-slug>/current_state.md
|
|
207
|
-
projects/<project-slug>/open_threads.md
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
It does not dump event logs into the model prompt, does not rewrite append-only events, and does not run during normal MCP wake-up. When a project is over the threshold, Dream builds a capped thread evidence pack by searching cue-indexed project events for `open_threads.md` bullets. The evidence budget follows `--target-chars`, so the same size target controls both the desired output and the supporting context.
|
|
211
|
-
|
|
212
|
-
Dream uses an Eve-style compaction loop. It rewrites the complete curated snapshot, measures the result, and compacts that coherent result again when it is still over the target. It never crops a wake-up file. After three passes, it fails without writing either file if the snapshot still does not fit.
|
|
213
|
-
|
|
214
|
-
Dry-run is the default:
|
|
215
|
-
|
|
216
|
-
```bash
|
|
217
|
-
npm run dream -- --all --dry-run --json
|
|
218
|
-
npm run dream -- --project my-project --write
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
`--write` requires a Manifest/OpenAI-compatible Responses API endpoint. The default environment variables are:
|
|
222
|
-
|
|
223
|
-
- `MANIFEST_BASE_URL`
|
|
224
|
-
- `MANIFEST_API_KEY`
|
|
225
|
-
- `HIPPOCAMP_DREAM_MODEL` (defaults to `auto`; override if you want a specific provider/model)
|
|
226
|
-
|
|
227
|
-
The GitHub Actions template at `assets/github-actions/hippocamp-dream.yml` is meant to be copied into the private Lagoon memory repo as `.github/workflows/hippocamp-dream.yml`. It runs on a schedule only, scans projects over the wake-up threshold, and creates or updates one PR per project. With `HIPPOCAMP_DREAM_AUTO_MERGE=true`, Dream squash-merges the PR only when the repository name is `lagoon` and the PR changes only the two curated project files.
|
|
228
|
-
|
|
229
|
-
### Railway
|
|
230
|
-
|
|
231
|
-
Railway can run Dream overnight even when your computer is offline. The included cron service starts once per day at `03:17 UTC`, clones the private Lagoon repo, creates or updates one Dream PR per candidate project, and exits. Set `HIPPOCAMP_DREAM_AUTO_MERGE=true` to enable the same Lagoon-only auto-merge policy.
|
|
232
|
-
|
|
233
|
-
[](https://railway.com/deploy/hippocamp)
|
|
234
|
-
|
|
235
|
-
Required template variables:
|
|
236
|
-
|
|
237
|
-
- `LAGOON_REPOSITORY`: private memory repo in `owner/repository` form
|
|
238
|
-
- `GITHUB_TOKEN`: fine-grained GitHub token limited to that repo, with read/write access to Contents and Pull requests
|
|
239
|
-
- `MANIFEST_BASE_URL`: Manifest/OpenAI-compatible base URL
|
|
240
|
-
- `MANIFEST_API_KEY`: API key for Dream model requests
|
|
241
|
-
|
|
242
|
-
Optional variables keep the CLI defaults: `HIPPOCAMP_DREAM_MODEL=auto`, `HIPPOCAMP_DREAM_THRESHOLD_CHARS=20000`, and `HIPPOCAMP_DREAM_TARGET_CHARS=15000`. `HIPPOCAMP_DREAM_AUTO_MERGE=true` enables a guarded squash merge for a repository named `lagoon`.
|
|
243
|
-
|
|
244
|
-
Railway is an optional deployment target. Local MCP reads and writes do not use Railway or require these hosted credentials.
|
|
245
|
-
|
|
246
|
-
## Memory Rules
|
|
247
|
-
|
|
248
|
-
- Keep memory concise.
|
|
249
|
-
- Prefer curated summaries over raw event history.
|
|
250
|
-
- Give events a short `Cues:` section; cues weigh the most in search ranking.
|
|
251
|
-
- Do not duplicate GitHub-owned facts such as commits, PRs, issues, reviews, or CI results.
|
|
252
|
-
- Store artifact references plus the missing rationale, preference, assumption, or follow-up context.
|
|
253
|
-
- Use project scope for project-specific state.
|
|
254
|
-
- Use global scope only for durable context that should follow you across projects.
|
|
255
|
-
|
|
256
|
-
Events are stamped automatically with agent/session provenance when available (`Agent:` / `Session:` lines). Installers set `HIPPOCAMP_AGENT`; session defaults to a process-lifetime id. No manual config is required.
|
|
257
|
-
|
|
258
|
-
Example event content:
|
|
259
|
-
|
|
260
|
-
```md
|
|
261
|
-
Agent: claude
|
|
262
|
-
Session: mcp-a1b2c3d4
|
|
263
|
-
|
|
264
|
-
Cues:
|
|
265
|
-
- local-first
|
|
266
|
-
- token-free
|
|
267
|
-
- install-story
|
|
268
|
-
|
|
269
|
-
References:
|
|
270
|
-
- commit: abc1234
|
|
271
|
-
- pr: #12
|
|
272
|
-
|
|
273
|
-
Decision:
|
|
274
|
-
Keep the local MCP path token-free and rely on normal git credentials.
|
|
275
|
-
|
|
276
|
-
Why:
|
|
277
|
-
This reduces onboarding friction for open-source users and avoids cloud auth concerns in the MVP.
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
## Commands
|
|
281
|
-
|
|
282
|
-
```bash
|
|
283
|
-
npm run mcp
|
|
284
|
-
npm run mcp:help
|
|
285
|
-
npm run mcp:smoke
|
|
286
|
-
npm run dream
|
|
287
|
-
npm run dream:railway
|
|
288
|
-
npm run eval:lagoon
|
|
289
|
-
npm run eval:qa
|
|
290
|
-
npm run eval:retrieval
|
|
291
|
-
npm run install:codex
|
|
292
|
-
npm run install:claude
|
|
293
|
-
npm run install:grok
|
|
294
|
-
npm run upgrade:codex
|
|
295
|
-
npm run upgrade:claude
|
|
296
|
-
npm run upgrade:grok
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
`npm run eval:retrieval` measures search on LongMemEval-S without any model: Recall@5, Hit@5, MRR, snippet evidence (the returned text comes from an answer turn), returned context size, and latency, next to an in-process BM25 reference. Run it with `--help` for the one-time data download.
|
|
300
|
-
|
|
301
|
-
`npm run eval:lagoon` runs the same kind of check, read-only, against your real Lagoon. It reads cases from `<Lagoon root>/evals/retrieval-cases.json`: a project, a query, and the event ids that answer it. An empty list means no memory should be returned. Keep the cases in Lagoon, not in this repo, because they quote private work.
|
|
302
|
-
|
|
303
|
-
`npm run eval:qa` measures answer accuracy on the same LongMemEval-S data with the official LongMemEval reader and grader prompts. It compares Hippocamp top 5 (`hippocamp`), top 10 (`hippocamp-k10`), and the labeled answer sessions (`oracle`). It calls an OpenAI-compatible endpoint from `MANIFEST_BASE_URL` and `MANIFEST_API_KEY`; set `QA_READER_MODEL` and `QA_GRADER_MODEL` to change models. Results are cached in `.context/qa-results.jsonl`, and `--dry-run` shows the jobs and token estimates without any API call.
|
|
304
|
-
|
|
305
|
-
## Releases
|
|
306
|
-
|
|
307
|
-
Pull requests and pushes to `main` run syntax and MCP smoke checks in GitHub Actions.
|
|
308
|
-
|
|
309
|
-
To publish a package version:
|
|
310
|
-
|
|
311
|
-
1. Update the version in `package.json` and `package-lock.json`, then merge it to `main`.
|
|
312
|
-
2. Publish a GitHub Release whose tag is `v<version>`.
|
|
313
|
-
3. The `Publish Package` workflow verifies the tag, runs the MCP checks, and publishes to npm through trusted publishing.
|
|
314
|
-
|
|
315
|
-
The npm package must trust the GitHub Actions workflow `publish.yml` in `guillaumegay13/hippocamp`. This uses short-lived OIDC credentials instead of an npm token.
|
|
316
|
-
|
|
317
|
-
## Configuration
|
|
39
|
+
| Right memory in top 5, LongMemEval-S (470 questions) | 90.5% | BM25: 91.4% |
|
|
40
|
+
| Text returned per search | 5.9k chars | Whole sessions: 65.5k |
|
|
41
|
+
| Answer accuracy, Claude Sonnet 5, top 10 results | 77% | Correct sessions given directly: 90% |
|
|
42
|
+
| Unrelated questions that return nothing (our own test set) | 10 / 10 | |
|
|
318
43
|
|
|
319
|
-
|
|
44
|
+
Full tables, method, and limits: [docs/results.md](docs/results.md).
|
|
320
45
|
|
|
321
|
-
|
|
322
|
-
- `HIPPOCAMP_PROJECT_ROOT`: project root used to infer the current project slug. Default: current working directory
|
|
323
|
-
- Git worktrees, including Conductor workspaces, resolve to the main repository name so every workspace shares the same project memory.
|
|
324
|
-
- `MANIFEST_BASE_URL`: OpenAI-compatible base URL used by `hippocamp dream --write`
|
|
325
|
-
- `MANIFEST_API_KEY`: API key used by `hippocamp dream --write`
|
|
326
|
-
- `HIPPOCAMP_DREAM_MODEL`: model used by Dream. Default: `auto`
|
|
327
|
-
- `HIPPOCAMP_DREAM_THRESHOLD_CHARS`: wake-up size required before Dream proposes compaction. Default: `20000`
|
|
328
|
-
- `HIPPOCAMP_DREAM_TARGET_CHARS`: target combined size for `current_state.md` and `open_threads.md`. Default: `15000`
|
|
329
|
-
- `HIPPOCAMP_DREAM_AUTO_MERGE`: set to `true` for a guarded squash merge in a repository named `lagoon`
|
|
46
|
+
## Docs
|
|
330
47
|
|
|
331
|
-
|
|
48
|
+
- [Setup](docs/setup.md): install options, the Lagoon repo, a private remote, configuration
|
|
49
|
+
- [How it works](docs/how-it-works.md): memory layout, MCP tools, memory rules
|
|
50
|
+
- [Dream](docs/dream.md): optional overnight compaction as a reviewed pull request
|
|
51
|
+
- [Results](docs/results.md): evals and how to rerun them
|
|
52
|
+
- [Development](docs/development.md): commands and releases
|
|
332
53
|
|
|
333
|
-
|
|
334
|
-
- Not a database
|
|
335
|
-
- Not a notes app
|
|
336
|
-
- Not a vector store
|
|
337
|
-
- Not a queue
|
|
54
|
+
## License
|
|
338
55
|
|
|
339
|
-
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Development
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run mcp
|
|
9
|
+
npm run mcp:help
|
|
10
|
+
npm run mcp:smoke
|
|
11
|
+
npm run dream
|
|
12
|
+
npm run dream:railway
|
|
13
|
+
npm run eval:lagoon
|
|
14
|
+
npm run eval:qa
|
|
15
|
+
npm run eval:retrieval
|
|
16
|
+
npm run install:codex
|
|
17
|
+
npm run install:claude
|
|
18
|
+
npm run install:grok
|
|
19
|
+
npm run upgrade:codex
|
|
20
|
+
npm run upgrade:claude
|
|
21
|
+
npm run upgrade:grok
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Releases
|
|
25
|
+
|
|
26
|
+
Pull requests and pushes to `main` run syntax and MCP smoke checks in GitHub Actions.
|
|
27
|
+
|
|
28
|
+
To publish a package version:
|
|
29
|
+
|
|
30
|
+
1. Update the version in `package.json` and `package-lock.json`, then merge it to `main`.
|
|
31
|
+
2. Publish a GitHub Release whose tag is `v<version>`.
|
|
32
|
+
3. The `Publish Package` workflow verifies the tag, runs the MCP checks, and publishes to npm through trusted publishing.
|
|
33
|
+
|
|
34
|
+
The npm package must trust the GitHub Actions workflow `publish.yml` in `guillaumegay13/hippocamp`. This uses short-lived OIDC credentials instead of an npm token.
|
package/docs/dream.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Dream
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
Dream is offline compaction for project wake-up context. It rewrites only:
|
|
6
|
+
|
|
7
|
+
```text
|
|
8
|
+
projects/<project-slug>/current_state.md
|
|
9
|
+
projects/<project-slug>/open_threads.md
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
It does not dump event logs into the model prompt, does not rewrite append-only events, and does not run during normal MCP wake-up. When a project is over the threshold, Dream builds a capped thread evidence pack by searching cue-indexed project events for `open_threads.md` bullets. The evidence budget follows `--target-chars`, so the same size target controls both the desired output and the supporting context.
|
|
13
|
+
|
|
14
|
+
Dream uses an Eve-style compaction loop. It rewrites the complete curated snapshot, measures the result, and compacts that coherent result again when it is still over the target. It never crops a wake-up file. After three passes, it fails without writing either file if the snapshot still does not fit.
|
|
15
|
+
|
|
16
|
+
Dry-run is the default:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm run dream -- --all --dry-run --json
|
|
20
|
+
npm run dream -- --project my-project --write
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`--write` requires a Manifest/OpenAI-compatible Responses API endpoint. The default environment variables are:
|
|
24
|
+
|
|
25
|
+
- `MANIFEST_BASE_URL`
|
|
26
|
+
- `MANIFEST_API_KEY`
|
|
27
|
+
- `HIPPOCAMP_DREAM_MODEL` (defaults to `auto`; override if you want a specific provider/model)
|
|
28
|
+
|
|
29
|
+
The GitHub Actions template at `assets/github-actions/hippocamp-dream.yml` is meant to be copied into the private Lagoon memory repo as `.github/workflows/hippocamp-dream.yml`. It runs on a schedule only, scans projects over the wake-up threshold, and creates or updates one PR per project. With `HIPPOCAMP_DREAM_AUTO_MERGE=true`, Dream squash-merges the PR only when the repository name is `lagoon` and the PR changes only the two curated project files.
|
|
30
|
+
|
|
31
|
+
## Railway
|
|
32
|
+
|
|
33
|
+
Railway can run Dream overnight even when your computer is offline. The included cron service starts once per day at `03:17 UTC`, clones the private Lagoon repo, creates or updates one Dream PR per candidate project, and exits. Set `HIPPOCAMP_DREAM_AUTO_MERGE=true` to enable the same Lagoon-only auto-merge policy.
|
|
34
|
+
|
|
35
|
+
[](https://railway.com/deploy/hippocamp)
|
|
36
|
+
|
|
37
|
+
Required template variables:
|
|
38
|
+
|
|
39
|
+
- `LAGOON_REPOSITORY`: private memory repo in `owner/repository` form
|
|
40
|
+
- `GITHUB_TOKEN`: fine-grained GitHub token limited to that repo, with read/write access to Contents and Pull requests
|
|
41
|
+
- `MANIFEST_BASE_URL`: Manifest/OpenAI-compatible base URL
|
|
42
|
+
- `MANIFEST_API_KEY`: API key for Dream model requests
|
|
43
|
+
|
|
44
|
+
Optional variables keep the CLI defaults: `HIPPOCAMP_DREAM_MODEL=auto`, `HIPPOCAMP_DREAM_THRESHOLD_CHARS=20000`, and `HIPPOCAMP_DREAM_TARGET_CHARS=15000`. `HIPPOCAMP_DREAM_AUTO_MERGE=true` enables a guarded squash merge for a repository named `lagoon`.
|
|
45
|
+
|
|
46
|
+
Railway is an optional deployment target. Local MCP reads and writes do not use Railway or require these hosted credentials.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# How It Works
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
## What It Stores
|
|
6
|
+
|
|
7
|
+
- durable preferences and working style
|
|
8
|
+
- current project state and open threads
|
|
9
|
+
- meaningful events and decisions
|
|
10
|
+
- references to commits, PRs, issues, reviews, and CI instead of duplicated GitHub facts
|
|
11
|
+
|
|
12
|
+
## Why Git
|
|
13
|
+
|
|
14
|
+
Git is a practical default for agent memory:
|
|
15
|
+
|
|
16
|
+
- Auditable: every memory change has a diff, author, timestamp, and commit history.
|
|
17
|
+
- Readable: memory stays as Markdown files that humans and agents can inspect without a special UI.
|
|
18
|
+
- Collaborative: multiple agents and humans can review, branch, merge, and roll back the same memory repo.
|
|
19
|
+
- Portable: a private remote lets the same memory follow you across machines and agent environments.
|
|
20
|
+
- Agent-native: coding agents are already connected to GitHub through MCP, CLIs, or local Git credentials, so Hippocamp does not need an external database, hosted dependency, or separate token setup.
|
|
21
|
+
|
|
22
|
+
## Memory Layout
|
|
23
|
+
|
|
24
|
+
Global memory lives in:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
~/.lagoon/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Project memory lives in:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
~/.lagoon/projects/<project-slug>/
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Suggested files:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
identity.md
|
|
40
|
+
how_i_work.md
|
|
41
|
+
preferences.md
|
|
42
|
+
open_loops.md
|
|
43
|
+
events/YYYY-MM-DD.md
|
|
44
|
+
events/YYYY-MM-DD.index.json
|
|
45
|
+
projects/<project-slug>/
|
|
46
|
+
project.md
|
|
47
|
+
current_state.md
|
|
48
|
+
open_threads.md
|
|
49
|
+
events/YYYY-MM-DD.md
|
|
50
|
+
events/YYYY-MM-DD.index.json
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## MCP Tools
|
|
54
|
+
|
|
55
|
+
The local MCP server exposes:
|
|
56
|
+
|
|
57
|
+
- `wake_up`
|
|
58
|
+
- `read_memory_file`
|
|
59
|
+
- `write_memory_file`
|
|
60
|
+
- `append_event`
|
|
61
|
+
- `list_memory_files`
|
|
62
|
+
- `search_memory`
|
|
63
|
+
- `sync_memory`
|
|
64
|
+
|
|
65
|
+
Typical agent flow:
|
|
66
|
+
|
|
67
|
+
1. Call `wake_up` at the start of a top-level task.
|
|
68
|
+
2. Read the returned global and project memory.
|
|
69
|
+
3. Use `search_memory` for task-specific recall after wake-up.
|
|
70
|
+
4. Use `append_event` for meaningful milestones, with a title and 1-8 concise cues.
|
|
71
|
+
5. Update curated files like `current_state.md` and `open_threads.md` before finishing.
|
|
72
|
+
|
|
73
|
+
Writes sync by default. If sync fails or is skipped, call `sync_memory`.
|
|
74
|
+
|
|
75
|
+
Event writes update a sibling `events/YYYY-MM-DD.index.json` file. The Markdown event remains the canonical memory; the sidecar is rebuildable. Search ranks indexed events and curated files with BM25 and typo tolerance ([MiniSearch](https://github.com/lucaong/minisearch)), weighting cues above titles and titles above bodies. It returns small coherent evidence blocks from indexed events. If no indexed result is strong enough, it returns no event result instead of scanning whole event logs.
|
|
76
|
+
|
|
77
|
+
## Memory Rules
|
|
78
|
+
|
|
79
|
+
- Keep memory concise.
|
|
80
|
+
- Prefer curated summaries over raw event history.
|
|
81
|
+
- Give events a short `Cues:` section; cues weigh the most in search ranking.
|
|
82
|
+
- Do not duplicate GitHub-owned facts such as commits, PRs, issues, reviews, or CI results.
|
|
83
|
+
- Store artifact references plus the missing rationale, preference, assumption, or follow-up context.
|
|
84
|
+
- Use project scope for project-specific state.
|
|
85
|
+
- Use global scope only for durable context that should follow you across projects.
|
|
86
|
+
|
|
87
|
+
Events are stamped automatically with agent/session provenance when available (`Agent:` / `Session:` lines). Installers set `HIPPOCAMP_AGENT`; session defaults to a process-lifetime id. No manual config is required.
|
|
88
|
+
|
|
89
|
+
Example event content:
|
|
90
|
+
|
|
91
|
+
```md
|
|
92
|
+
Agent: claude
|
|
93
|
+
Session: mcp-a1b2c3d4
|
|
94
|
+
|
|
95
|
+
Cues:
|
|
96
|
+
- local-first
|
|
97
|
+
- token-free
|
|
98
|
+
- install-story
|
|
99
|
+
|
|
100
|
+
References:
|
|
101
|
+
- commit: abc1234
|
|
102
|
+
- pr: #12
|
|
103
|
+
|
|
104
|
+
Decision:
|
|
105
|
+
Keep the local MCP path token-free and rely on normal git credentials.
|
|
106
|
+
|
|
107
|
+
Why:
|
|
108
|
+
This reduces onboarding friction for open-source users and avoids cloud auth concerns in the MVP.
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## What This Is Not
|
|
112
|
+
|
|
113
|
+
- Not a hosted cloud service
|
|
114
|
+
- Not a database
|
|
115
|
+
- Not a notes app
|
|
116
|
+
- Not a vector store
|
|
117
|
+
- Not a queue
|
|
118
|
+
|
|
119
|
+
The cloud/API version can be redesigned later. The MVP is intentionally local-first and Git-native.
|
package/docs/results.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Results
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
Every number below comes from a script in this repo, so you can rerun it. We report where Hippocamp falls short too.
|
|
6
|
+
|
|
7
|
+
## Finding the right memory
|
|
8
|
+
|
|
9
|
+
LongMemEval-S, all 470 answerable questions, top 5 results, no model involved (`npm run eval:retrieval`).
|
|
10
|
+
|
|
11
|
+
| Measure | Hippocamp | BM25 reference |
|
|
12
|
+
| --- | ---: | ---: |
|
|
13
|
+
| Right session in top 5 (Recall@5) | 90.5% | 91.4% |
|
|
14
|
+
| At least one right session in top 5 (Hit@5) | 96.0% | 96.8% |
|
|
15
|
+
| Returned text contains the answer turn | 87.9% | n/a |
|
|
16
|
+
| Text returned per query | 5.9k chars | 65.5k chars |
|
|
17
|
+
| Search latency, cold index | 42 ms | n/a |
|
|
18
|
+
|
|
19
|
+
BM25 returns whole sessions; Hippocamp returns the matching paragraphs of each result.
|
|
20
|
+
|
|
21
|
+
## Answering with it
|
|
22
|
+
|
|
23
|
+
100 LongMemEval-S questions, reader Claude Sonnet 5, grader gpt-4o with the official LongMemEval grading prompts (`npm run eval:qa`).
|
|
24
|
+
|
|
25
|
+
| What the reader gets | Text per question | Answer accuracy |
|
|
26
|
+
| --- | ---: | ---: |
|
|
27
|
+
| Hippocamp, top 5 | 6.7k chars | 71% |
|
|
28
|
+
| Hippocamp, top 10 | 12.9k chars | 77% |
|
|
29
|
+
| The correct sessions, given directly | 28.8k chars | 90% |
|
|
30
|
+
|
|
31
|
+
The same reader reaches 90% with the correct sessions, so the remaining gap is retrieval, not the reader.
|
|
32
|
+
|
|
33
|
+
## Real coding questions
|
|
34
|
+
|
|
35
|
+
53 questions about real work in a personal Lagoon across 13 projects (`npm run eval:lagoon`). This is our own test set, kept in the private Lagoon.
|
|
36
|
+
|
|
37
|
+
| Measure | Result |
|
|
38
|
+
| --- | ---: |
|
|
39
|
+
| Right event in top 5, 43 answerable questions | 42 / 43 |
|
|
40
|
+
| No results for 10 questions no memory is about | 10 / 10 |
|
|
41
|
+
|
|
42
|
+
## Why less text matters
|
|
43
|
+
|
|
44
|
+
Memory results share the agent's context with code, diffs, and tool output, and agents search several times per task. About 6k characters per search is roughly 1.5k tokens; whole sessions would be about 16k. Fewer tokens make each turn cheaper and faster, and keep the relevant lines from being buried. Less text is only useful when the answer is still in it, which is why we report answer accuracy next to text size.
|
|
45
|
+
|
|
46
|
+
## Limits
|
|
47
|
+
|
|
48
|
+
- Recall@5 is just under the BM25 reference, by less than 1 point, with 11 times less text.
|
|
49
|
+
- No embeddings or semantic search: a memory written in other words can be missed. On LongMemEval-S, 9 of 470 questions miss with no word in common with the answer.
|
|
50
|
+
- Answer accuracy is measured on 100 questions, about plus or minus 4 points.
|
|
51
|
+
- Self-reported scores from other memory systems use their own readers and graders, so they are not directly comparable with these.
|
|
52
|
+
|
|
53
|
+
## Reproduce
|
|
54
|
+
|
|
55
|
+
`npm run eval:retrieval` measures search on LongMemEval-S without any model: Recall@5, Hit@5, MRR, snippet evidence (the returned text comes from an answer turn), returned context size, and latency, next to an in-process BM25 reference. Run it with `--help` for the one-time data download.
|
|
56
|
+
|
|
57
|
+
`npm run eval:lagoon` runs the same kind of check, read-only, against your real Lagoon. It reads cases from `<Lagoon root>/evals/retrieval-cases.json`: a project, a query, and the event ids that answer it. An empty list means no memory should be returned. Keep the cases in Lagoon, not in this repo, because they quote private work.
|
|
58
|
+
|
|
59
|
+
`npm run eval:qa` measures answer accuracy on the same LongMemEval-S data with the official LongMemEval reader and grader prompts. It compares Hippocamp top 5 (`hippocamp`), top 10 (`hippocamp-k10`), and the labeled answer sessions (`oracle`). It calls an OpenAI-compatible endpoint from `MANIFEST_BASE_URL` and `MANIFEST_API_KEY`; set `QA_READER_MODEL` and `QA_GRADER_MODEL` to change models. Results are cached in `.context/qa-results.jsonl`, and `--dry-run` shows the jobs and token estimates without any API call.
|
package/docs/setup.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Setup
|
|
2
|
+
|
|
3
|
+
[← Back to README](../README.md)
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx hippocamp@latest install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
To install into one agent only:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx hippocamp@latest install-claude
|
|
15
|
+
npx hippocamp@latest install-codex
|
|
16
|
+
npx hippocamp@latest install-grok
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Upgrade with the same command: `npx hippocamp@latest upgrade`. Installers default to `~/.lagoon` as the memory repo; pass `--global-root /absolute/path/to/lagoon` to use another clone.
|
|
20
|
+
|
|
21
|
+
Installed through npx, the installer copies the package to `~/.hippocamp/<version>/` and registers that copy, so the server starts without npx or network and keeps working if npm cleans its cache. From a source checkout, `npm install` then `npm run install:claude` (or `install:codex`, `install:grok`) registers the checkout directly.
|
|
22
|
+
|
|
23
|
+
The Codex installer refreshes `~/.codex/AGENTS.md`. The Claude installer refreshes `~/.claude/CLAUDE.md`. The Grok installer refreshes `~/.grok/rules/hippocamp.md` and registers the MCP server with `HIPPOCAMP_AGENT=grok`.
|
|
24
|
+
Managed instruction blocks tell the agent to call `wake_up` at the start of new top-level coding tasks before repo exploration or edits.
|
|
25
|
+
|
|
26
|
+
## Lagoon Repo
|
|
27
|
+
|
|
28
|
+
Hippocamp keeps memory in a local Git repo. The installer creates `~/.lagoon` if it is missing. To use memory you already have on another machine, clone it first:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
git clone git@github.com:YOUR_USER/lagoon.git ~/.lagoon
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The repo should usually be private. Pushing memory to a private remote keeps it available across machines and agent environments while still using normal Git access controls. Hippocamp does not need a GitHub token for local MCP mode; it uses your normal local Git credentials.
|
|
35
|
+
|
|
36
|
+
Memory works locally without a remote. To back it up privately and share it across machines, create a private remote:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
gh repo create lagoon --private --source ~/.lagoon --remote origin --push
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If push auth is not configured yet, use your preferred GitHub setup. With GitHub CLI:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
gh auth login
|
|
46
|
+
gh auth setup-git
|
|
47
|
+
cd ~/.lagoon
|
|
48
|
+
git push --dry-run
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Once `git push --dry-run` works from `~/.lagoon`, Hippocamp can sync memory.
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
Environment variables are optional for local use:
|
|
56
|
+
|
|
57
|
+
- `HIPPOCAMP_GLOBAL_ROOT`: local Lagoon clone path. Default: `~/.lagoon`
|
|
58
|
+
- `HIPPOCAMP_PROJECT_ROOT`: project root used to infer the current project slug. Default: current working directory
|
|
59
|
+
- Git worktrees, including Conductor workspaces, resolve to the main repository name so every workspace shares the same project memory.
|
|
60
|
+
- `MANIFEST_BASE_URL`: OpenAI-compatible base URL used by `hippocamp dream --write`
|
|
61
|
+
- `MANIFEST_API_KEY`: API key used by `hippocamp dream --write`
|
|
62
|
+
- `HIPPOCAMP_DREAM_MODEL`: model used by Dream. Default: `auto`
|
|
63
|
+
- `HIPPOCAMP_DREAM_THRESHOLD_CHARS`: wake-up size required before Dream proposes compaction. Default: `20000`
|
|
64
|
+
- `HIPPOCAMP_DREAM_TARGET_CHARS`: target combined size for `current_state.md` and `open_threads.md`. Default: `15000`
|
|
65
|
+
- `HIPPOCAMP_DREAM_AUTO_MERGE`: set to `true` for a guarded squash merge in a repository named `lagoon`
|
package/package.json
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
1
|
const fsSync = require("node:fs");
|
|
2
2
|
const fs = require("node:fs/promises");
|
|
3
|
+
const os = require("node:os");
|
|
3
4
|
const path = require("node:path");
|
|
4
5
|
const { execFile } = require("node:child_process");
|
|
5
6
|
const { promisify } = require("node:util");
|
|
6
7
|
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
|
|
9
|
-
// How an agent starts the MCP server. Run through npx, the package
|
|
10
|
-
//
|
|
11
|
-
|
|
10
|
+
// How an agent starts the MCP server. Run through npx, the package sits in npm's cache, which
|
|
11
|
+
// npm may clean. Copy it with its dependencies to ~/.hippocamp/<version> and start it from
|
|
12
|
+
// there: no npx or network at start, so it also works offline.
|
|
13
|
+
function mcpServerCommand(repoRoot, home = os.homedir()) {
|
|
12
14
|
if (!repoRoot.split(path.sep).includes("_npx")) {
|
|
13
15
|
return [process.execPath, path.join(repoRoot, "scripts", "hippocamp-mcp.cjs")];
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
const { version } = require(path.join(repoRoot, "package.json"));
|
|
17
|
-
const
|
|
19
|
+
const target = path.join(home, ".hippocamp", version, "node_modules");
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
// Copy into a staging folder first, so a failed copy never removes a working install.
|
|
22
|
+
const staging = `${target}.staging-${process.pid}`;
|
|
23
|
+
fsSync.rmSync(staging, { recursive: true, force: true });
|
|
24
|
+
fsSync.cpSync(path.dirname(repoRoot), staging, { recursive: true, dereference: true });
|
|
25
|
+
fsSync.rmSync(target, { recursive: true, force: true });
|
|
26
|
+
fsSync.renameSync(staging, target);
|
|
27
|
+
|
|
28
|
+
return [process.execPath, path.join(target, path.basename(repoRoot), "scripts", "hippocamp-mcp.cjs")];
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
async function git(args, cwd) {
|
|
@@ -36,13 +36,30 @@ async function tempDir(t, name) {
|
|
|
36
36
|
return root;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
test("mcpServerCommand
|
|
39
|
+
test("mcpServerCommand copies npx installs to a stable folder and keeps source checkouts direct", async (t) => {
|
|
40
40
|
const root = await tempDir(t, "npx");
|
|
41
|
-
const
|
|
42
|
-
|
|
41
|
+
const modules = path.join(root, "_npx", "abc", "node_modules");
|
|
42
|
+
const npxRoot = path.join(modules, "hippocamp");
|
|
43
|
+
const home = path.join(root, "home");
|
|
44
|
+
await fs.mkdir(path.join(npxRoot, "scripts"), { recursive: true });
|
|
45
|
+
await fs.mkdir(path.join(modules, "minisearch"), { recursive: true });
|
|
43
46
|
await fs.writeFile(path.join(npxRoot, "package.json"), '{ "version": "9.9.9" }');
|
|
47
|
+
await fs.writeFile(path.join(npxRoot, "scripts", "hippocamp-mcp.cjs"), "// server\n");
|
|
48
|
+
await fs.writeFile(path.join(modules, "minisearch", "index.js"), "// dependency\n");
|
|
49
|
+
|
|
50
|
+
const command = mcpServerCommand(npxRoot, home);
|
|
51
|
+
const stable = path.join(home, ".hippocamp", "9.9.9", "node_modules");
|
|
52
|
+
|
|
53
|
+
assert.deepEqual(command, [process.execPath, path.join(stable, "hippocamp", "scripts", "hippocamp-mcp.cjs")]);
|
|
54
|
+
await fs.stat(command[1]);
|
|
55
|
+
await fs.stat(path.join(stable, "minisearch", "index.js"));
|
|
56
|
+
|
|
57
|
+
// A reinstall of the same version replaces the copy: no stale files, no staging left behind.
|
|
58
|
+
await fs.writeFile(path.join(stable, "stale.js"), "// old\n");
|
|
59
|
+
mcpServerCommand(npxRoot, home);
|
|
60
|
+
await assert.rejects(fs.stat(path.join(stable, "stale.js")));
|
|
61
|
+
assert.deepEqual(await fs.readdir(path.join(home, ".hippocamp", "9.9.9")), ["node_modules"]);
|
|
44
62
|
|
|
45
|
-
assert.deepEqual(mcpServerCommand(npxRoot).slice(1), ["-y", "--prefer-offline", "hippocamp@9.9.9", "mcp"]);
|
|
46
63
|
const source = path.join(root, "src", "hippocamp");
|
|
47
64
|
assert.deepEqual(mcpServerCommand(source), [process.execPath, path.join(source, "scripts", "hippocamp-mcp.cjs")]);
|
|
48
65
|
});
|
|
@@ -28,3 +28,15 @@ test("published files contain no local home directory paths", () => {
|
|
|
28
28
|
|
|
29
29
|
assert.deepEqual(matches, []);
|
|
30
30
|
});
|
|
31
|
+
|
|
32
|
+
test("the README demo GIF stays out of the npm package", () => {
|
|
33
|
+
// The README loads it from GitHub; .npmignore keeps the 600 kB file out of every install.
|
|
34
|
+
const output = execFileSync("npm", ["pack", "--dry-run", "--json"], {
|
|
35
|
+
cwd: path.join(__dirname, ".."),
|
|
36
|
+
encoding: "utf8",
|
|
37
|
+
shell: process.platform === "win32",
|
|
38
|
+
});
|
|
39
|
+
const [pack] = JSON.parse(output);
|
|
40
|
+
|
|
41
|
+
assert.ok(!pack.files.some((file) => file.path.endsWith(".gif")));
|
|
42
|
+
});
|