hippocamp 0.2.8 → 0.2.9

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.
@@ -106,14 +106,14 @@ jobs:
106
106
  --target-chars "$HIPPOCAMP_DREAM_TARGET_CHARS" \
107
107
  > "/tmp/hippocamp-dream-${project}.json"
108
108
 
109
- if git diff --quiet -- "projects/${project}/current_state.md" "projects/${project}/open_threads.md"; then
109
+ git add -- "projects/${project}/current_state.md" "projects/${project}/open_threads.md"
110
+ if git diff --cached --quiet; then
110
111
  echo "No Dream diff for ${project}"
111
112
  git checkout main
112
113
  git reset --hard origin/main
113
114
  continue
114
115
  fi
115
116
 
116
- git add "projects/${project}/current_state.md" "projects/${project}/open_threads.md"
117
117
  git commit -m "Dream update ${project} memory"
118
118
  git push --force-with-lease origin "$branch"
119
119
 
@@ -113,12 +113,12 @@ while IFS= read -r project; do
113
113
  continue
114
114
  fi
115
115
 
116
- if git diff --quiet -- "projects/${project}/current_state.md" "projects/${project}/open_threads.md"; then
116
+ git add -- "projects/${project}/current_state.md" "projects/${project}/open_threads.md"
117
+ if git diff --cached --quiet; then
117
118
  echo "No Dream diff for ${project}"
118
119
  continue
119
120
  fi
120
121
 
121
- git add "projects/${project}/current_state.md" "projects/${project}/open_threads.md"
122
122
  git commit -m "Dream update ${project} memory"
123
123
 
124
124
  if git ls-remote --exit-code --heads origin "refs/heads/${branch}" >/dev/null 2>&1; then
package/docs/dream.md CHANGED
@@ -17,6 +17,8 @@ Recent events use at most half of the evidence budget. The remaining budget supp
17
17
 
18
18
  Dream promotes clear decisions, explicit corrections, and reusable project lessons into `current_state.md`, with source references. It keeps remaining actions in `open_threads.md`, preserves unresolved blockers, and removes completed actions only when evidence supports it. A merged PR does not prove its deployment or follow-up work is done. Uncertain lessons remain in events for normal search.
19
19
 
20
+ Projects with indexed events can start without curated summaries. Dream creates the two files, and the scheduled runners include these new files in the project's PR.
21
+
20
22
  Corrections apply across both files: a shipped feature should not remain an implementation task, while unverified deployment stays open. Extra compaction passes must keep source paths and timestamps for retained knowledge and aim below the size limit to leave room for length estimates. Dream writes the review comment itself; the model does not set review progress.
21
23
 
22
24
  The snapshot may grow within `--target-chars` when new evidence adds useful knowledge. If it exceeds the target, Dream compacts the complete result again. It never crops a wake-up file. After three passes, it fails without writing either file or advancing the event comment if the snapshot still does not fit. A project below the threshold with no new indexed evidence is skipped without a model call.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hippocamp",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "private": false,
5
5
  "description": "Local Git-backed memory for AI agents.",
6
6
  "license": "MIT",
@@ -0,0 +1,86 @@
1
+ const assert = require("node:assert/strict");
2
+ const { execFile } = require("node:child_process");
3
+ const fs = require("node:fs/promises");
4
+ const os = require("node:os");
5
+ const path = require("node:path");
6
+ const { promisify } = require("node:util");
7
+ const test = require("node:test");
8
+ const memory = require("../scripts/hippocamp-memory.cjs");
9
+
10
+ const exec = promisify(execFile);
11
+
12
+ test("Dream runner publishes first summaries and skips an unchanged repeat", async (t) => {
13
+ const root = await fs.mkdtemp(path.join(os.tmpdir(), "hippocamp-runner-test-"));
14
+ const seed = path.join(root, "seed");
15
+ const remote = path.join(root, "remote.git");
16
+ const bin = path.join(root, "bin");
17
+ const calls = path.join(root, "prs");
18
+ const preload = path.join(root, "model.cjs");
19
+ const previousRoot = process.env.HIPPOCAMP_GLOBAL_ROOT;
20
+ t.after(async () => {
21
+ if (previousRoot === undefined) delete process.env.HIPPOCAMP_GLOBAL_ROOT;
22
+ else process.env.HIPPOCAMP_GLOBAL_ROOT = previousRoot;
23
+ await fs.rm(root, { recursive: true, force: true });
24
+ });
25
+ await fs.mkdir(seed);
26
+ await fs.mkdir(bin);
27
+ process.env.HIPPOCAMP_GLOBAL_ROOT = seed;
28
+ await memory.appendEvent({ projectRoot: "/tmp/new-project", scope: "project", sync: false, title: "Confirmed decision", keywords: ["constraint"], content: "Keep production access read-only." });
29
+ const env = {
30
+ ...process.env,
31
+ PATH: `${bin}${path.delimiter}${process.env.PATH}`,
32
+ GIT_CONFIG_GLOBAL: path.join(root, "gitconfig"),
33
+ GIT_CONFIG_NOSYSTEM: "1",
34
+ GITHUB_TOKEN: "fixture",
35
+ LAGOON_REPOSITORY: "fixture/lagoon",
36
+ MANIFEST_API_KEY: "fixture",
37
+ MANIFEST_BASE_URL: "https://model.invalid",
38
+ HIPPOCAMP_DREAM_AUTO_MERGE: "false",
39
+ NODE_OPTIONS: `--require=${preload}`,
40
+ FIXTURE_REMOTE: remote,
41
+ FIXTURE_CALLS: calls,
42
+ };
43
+ await fs.writeFile(env.GIT_CONFIG_GLOBAL, "[user]\nname = Fixture\nemail = fixture@localhost\n");
44
+ await fs.writeFile(preload, `
45
+ global.fetch = async () => {
46
+ const fs = require("node:fs");
47
+ const path = require("node:path");
48
+ fs.writeFileSync(path.join(process.env.HIPPOCAMP_GLOBAL_ROOT, "unrelated.md"), "Do not publish this file");
49
+ return { ok: true, status: 200, text: async () => JSON.stringify({ output_text: JSON.stringify({
50
+ current_state_md: "# Current State\\n\\n- Keep production access read-only.",
51
+ open_threads_md: "# Open Threads\\n\\n- Verify deployment."
52
+ }) }) };
53
+ };
54
+ `);
55
+ await fs.writeFile(path.join(bin, "gh"), `#!${process.execPath}
56
+ const fs = require("node:fs");
57
+ const { execFileSync } = require("node:child_process");
58
+ const args = process.argv.slice(2);
59
+ if (args[0] === "auth") process.exit(0);
60
+ if (args[0] === "repo" && args[1] === "clone") {
61
+ execFileSync("git", ["clone", process.env.FIXTURE_REMOTE, args[3]], { stdio: "inherit" });
62
+ } else if (args[0] === "pr" && args[1] === "list") {
63
+ if (fs.existsSync(process.env.FIXTURE_CALLS)) console.log("1");
64
+ } else if (args[0] === "pr" && args[1] === "create") {
65
+ fs.appendFileSync(process.env.FIXTURE_CALLS, "created\\n");
66
+ } else {
67
+ throw new Error("Unexpected gh operation: " + args.join(" "));
68
+ }
69
+ `, { mode: 0o755 });
70
+ const git = (args, cwd = seed) => exec("git", args, { cwd, env });
71
+ await git(["init", "-b", "main"]);
72
+ await git(["add", "."]);
73
+ await git(["commit", "-m", "Seed indexed evidence without curated files"]);
74
+ const before = (await git(["rev-parse", "HEAD"])).stdout.trim();
75
+ await git(["clone", "--bare", seed, remote]);
76
+ const runner = path.resolve(__dirname, "../assets/railway/run-dream.sh");
77
+ await exec("bash", [runner], { env });
78
+ const changed = (await git(["diff", "--name-only", before, "refs/heads/dream/new-project"], remote)).stdout.trim().split("\n");
79
+ assert.deepEqual(changed, ["projects/new-project/current_state.md", "projects/new-project/open_threads.md"]);
80
+ assert.match((await git(["show", "dream/new-project:projects/new-project/current_state.md"], remote)).stdout, /hippocamp-dream-through:/);
81
+ assert.equal(await fs.readFile(calls, "utf8"), "created\n");
82
+ // Simulate accepting the reviewed PR, then repeat against the same evidence.
83
+ await git(["update-ref", "refs/heads/main", "refs/heads/dream/new-project"], remote);
84
+ await exec("bash", [runner], { env });
85
+ assert.equal(await fs.readFile(calls, "utf8"), "created\n");
86
+ });