Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4a6ad09
fix: remote recall calls setup.sql's gitmem_scar_search (GIT-114)
Sep 23, 2026
65cbba4
test: drift analyzer checks RPC names against v1.8.0's setup.sql (GIT…
Sep 23, 2026
ea7c6e8
test: venue flow recalls with GITMEM_SEARCH_MODE=remote and from a co…
Sep 23, 2026
b603782
fix: hooks need only bash and node; missing tools fail visibly (GIT-116)
Sep 23, 2026
63bdba5
ci: hook tests on stock macOS (no Homebrew, no timeout, no jq); publi…
Sep 23, 2026
b4aa396
fix: a thread counts as synced only when the store confirms its row (…
Sep 23, 2026
6c2124b
test: venue flow closes with a thread the store rejects (FK) and chec…
Sep 23, 2026
f8cfe3b
ci: macOS hook leg asserts no timeout/gtimeout only; macOS 15 ships j…
Sep 23, 2026
c903fe5
fix: a failed index reload keeps what recall has, says so, and retrie…
Sep 23, 2026
848f8c9
test: venue flow fails the index reads through a proxy and checks rec…
Sep 23, 2026
e94f711
fix: archive_learning resolves prefixes by UUID range; 0-row PATCH is…
Sep 23, 2026
21d64d5
fix: install-hooks/uninstall-hooks merge per event and keep foreign h…
Sep 23, 2026
583f27f
Merge bugfix/GIT-114-remote-recall-rpc into release/D
Sep 23, 2026
4f6e1ff
Merge bugfix/GIT-116-hook-portability into release/D
Sep 23, 2026
a611da5
Merge bugfix/GIT-117-thread-sync-truth into release/D
Sep 23, 2026
e4740e4
Merge bugfix/GIT-118-failed-reload-keeps-index into release/D
Sep 23, 2026
8647d1a
Merge bugfix/GIT-119-archive-and-patch-rows into release/D
Sep 23, 2026
96e5476
Merge bugfix/GIT-120-hooks-merge into release/D
Sep 23, 2026
c1cf1b1
test: thread-sync mocks use the GIT-119 directPatch shape (release/D …
Sep 23, 2026
5cb3351
docs: CHANGELOG [Unreleased] for Release D (GIT-114, 116-120)
Sep 23, 2026
d590566
fix: init writes the store where the server reads it; repo keeps conf…
Sep 23, 2026
ba44762
Merge bugfix/GIT-115-store-root into release/D
Sep 23, 2026
0624096
docs: CHANGELOG [Unreleased] adds GIT-115
Sep 23, 2026
f180c20
chore: bump version to v1.11.1
Sep 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,17 @@ jobs:
- name: E2E — session identity survives MCP restart
run: npx vitest run --config vitest.e2e.config.ts tests/e2e/git-89-session-identity.test.ts

# GIT-120: install-hooks / uninstall-hooks keep foreign hooks. Runs the
# real CLI in a scratch repo; no server, Docker or credentials.
- name: E2E — hooks install/uninstall keep foreign hooks
run: npx vitest run --config vitest.e2e.config.ts tests/e2e/git-120-hooks-merge.test.ts

# GIT-115: a fresh install in a clean HOME — init, the SessionStart hook,
# the built server — plus the CLI/wizard suite. Both run the real CLI in
# scratch directories with a scratch HOME; no Docker or credentials.
- name: E2E — fresh install reads what init wrote; CLI and wizard
run: npx vitest run --config vitest.e2e.config.ts tests/e2e/git-115-fresh-install.test.ts tests/e2e/cli-fresh-install.test.ts

- name: Dependency audit (no unused deps)
run: npx depcheck --ignores="@types/*" --json | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8')); const unused=d.dependencies||[]; if(unused.length){console.error('Unused dependencies:',unused);process.exit(1)}"

Expand All @@ -73,6 +84,31 @@ jobs:
- name: Hook tests
run: bash hooks/tests/test-hooks.sh

# GIT-116: the same suite on stock macOS. The runner has Homebrew (and may
# have coreutils' timeout), so the suite runs with the system directories
# plus a directory holding only node — what a Mac without Homebrew has.
hooks-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Hook tests on a stock macOS PATH (no Homebrew, no timeout)
run: |
mkdir -p "$RUNNER_TEMP/nodeonly"
ln -s "$(command -v node)" "$RUNNER_TEMP/nodeonly/node"
STOCK="/usr/bin:/bin:/usr/sbin:/sbin:$RUNNER_TEMP/nodeonly"
for t in timeout gtimeout; do
if PATH="$STOCK" command -v "$t" >/dev/null; then echo "::error::$t is on the stock PATH"; exit 1; fi
done
# macOS 15+ ships /usr/bin/jq; older macOS does not. The jq-less path
# is covered by hooks-without-jq (Linux); report which this runner is.
PATH="$STOCK" command -v jq >/dev/null && echo "stock jq present: $(PATH="$STOCK" jq --version)" || echo "stock jq absent"
env -i HOME="$HOME" PATH="$STOCK" /bin/bash hooks/tests/test-hooks.sh

# GIT-83: the release gate. Customers never run SQL after setup (standing
# rule, 2026-09-21), so a release must work on a store still on the v1.8.0
# setup.sql — the venue is put on exactly that schema first, then the
Expand Down Expand Up @@ -164,7 +200,7 @@ jobs:
retention-days: 14

publish:
needs: [build, hooks-without-jq, venue-gate]
needs: [build, hooks-without-jq, hooks-macos, venue-gate]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
permissions:
Expand Down
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.11.1] - 2026-09-22

**Just upgrade the package. Nothing to change on your database.** Every fix below works on a project
still on the `setup.sql` you first ran, back to 1.8.0, and was checked against a project on exactly
that schema and on the current one.

**Recall works when it has to ask your database.** When the local index was not ready, or with
`GITMEM_SEARCH_MODE=remote`, recall called a database function that `setup.sql` never created. It
got an error and returned no scars. It now calls `gitmem_scar_search`, which every project has, and
falls back to the older function only where that exists. (GIT-114)

**A fresh install now finds its starter lessons.** `gitmem init` put your memory in the repo's
`.gitmem/`, but gitmem reads `~/.gitmem`, so a new install started with none of its starter lessons
and was told its memory was "NOT being read". `init` now puts your memory where gitmem reads it
(`GITMEM_DIR`, else `GITMEM_HOME/.gitmem`, else `~/.gitmem`). The repo's `.gitmem/` keeps only
`config.json`, with the project name, and the hook scripts. A repo set up that way no longer
triggers the warning. `gitmem uninstall` removes the repo's files and keeps your memory, which every
project shares, unless you pass `--all`. The session-start hook now finds the project name without
jq. (GIT-115)

If an older `init` put lessons of your own in a repo's `.gitmem/`, session_start still points them
out. `npx gitmem-mcp migrate-root` copies them into `~/.gitmem`.

**`install-hooks` and `uninstall-hooks` keep your own hooks.** They used to replace or delete every
hook in `.claude/settings.json` or `.cursor/hooks.json`. Now they add or remove only gitmem's hooks,
back the file up first, and print what changed. A file that is not valid JSON is left alone instead
of being replaced. `gitmem init` and `gitmem uninstall` use the same matching, so a hook of yours
that shares a matcher with gitmem's, or lives in a folder named gitmem, is no longer removed or
mistaken for gitmem's. (GIT-120, GIT-115)

### Fixed

- **Hooks work on a stock Mac.** The prompt hook ran `timeout`, which macOS does not have, so it
never returned scars there. The time limit now lives inside gitmem. When gitmem or node cannot be
found, the hook says so instead of staying silent. (GIT-116)
- **A thread that failed to sync is no longer marked synced.** `session_close` reports PARTIAL and
names the thread ids that did not reach your database. Those threads stay in `threads.json` and
the next session tries again. (GIT-117)
- **A failed index reload no longer empties recall.** If reloading scars from your database fails,
gitmem keeps the index it had, falls back to the on-disk cache, and retries in the background.
`create_learning` says when the new learning is saved but not yet searchable. (GIT-118)
- **`archive_learning` accepts a short id.** Archiving by id prefix failed on every project with
"operator does not exist". Archiving an id that does not exist used to report success; it now says
nothing was archived. (GIT-119)
- **Updates that change no row are reported as not saved.** This covers thread resolve, touch and
auto-archive, session embeddings, superseded sessions, relevance data and transcript paths. Each
one used to count as success. (GIT-119)

## [1.11.0] - 2026-09-21

**Just upgrade the package. Nothing to change on your database.** Every fix in this release works on
Expand Down
34 changes: 34 additions & 0 deletions bin/gitmem-root.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
* GIT-115: where the CLI puts things, split the same way the server reads them.
*
* The store (learnings, threads, sessions, decisions, the closing payload and
* its template) belongs in the root the server reads. That is resolved exactly
* as src/services/gitmem-dir.ts does, without a running session: GITMEM_DIR,
* then GITMEM_HOME/.gitmem, then ~/.gitmem. `init` used to seed <cwd>/.gitmem,
* which the server has not read since GIT-91, so a fresh install saw no
* starter lessons and was told its store was "NOT being read".
*
* The repo keeps its own .gitmem/ for two things only: config.json with the
* project name (the SessionStart hook reads it from the repo) and the hook
* scripts (committed settings refer to them by relative path).
*/

import { join } from "path";
import { homedir } from "os";

/** The root the server reads. */
export function storeRoot() {
if (process.env.GITMEM_DIR) return process.env.GITMEM_DIR;
return join(process.env.GITMEM_HOME || homedir(), ".gitmem");
}

/** The repo's .gitmem/: config.json and hooks/ only. */
export function repoGitmemDir(cwd = process.cwd()) {
return join(cwd, ".gitmem");
}

/** A path shortened to ~ for display. */
export function displayPath(p) {
const home = homedir();
return p === home || p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
}
Loading
Loading