Skip to content

Release D: GIT-113 Phase 1 (GIT-114 to GIT-120) - #56

Merged
nTEG-dev merged 24 commits into
mainfrom
release/D
Sep 23, 2026
Merged

nTEG-dev merged 24 commits into
mainfrom
release/D

Conversation

@nTEG-dev

@nTEG-dev nTEG-dev commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Release D (GIT-113 Phase 1): all seven items stacked. Chris decides the release.

Just upgrade the package. Nothing to change on your database. schema/ and supabase/ are unchanged from main.

No version bump, tag, merge or publish. The CHANGELOG entry is under [Unreleased].

Stacked

Item PR What it fixes
GIT-114 #50 Remote and cold-start recall call gitmem_scar_search. The drift analyzer now covers RPC names.
GIT-116 #51 Hooks no longer need timeout. The prompt-hook time limit moved into quick-retrieve. macOS CI leg added.
GIT-117 #52 Thread sync is marked synced only on a confirmed row. PARTIAL names failed ids and carries them forward.
GIT-118 #53 A failed index reload keeps the index and hook cache, falls back to disk, and retries with backoff.
GIT-119 #54 archive_learning resolves prefixes by UUID range. All 8 directPatch callers treat 0 rows as not durable.
GIT-120 #55 install/uninstall-hooks merge per event, keep foreign hooks, back up first, and print changes.
GIT-115 #57 init seeds the store the server reads; the repo keeps config.json and hooks (Chris's ruling). The wizard and uninstall use GIT-120 matching.

Stack integration

Gate results (release/D @ 0624096, rerun after adding GIT-115)

Check Result
tsc clean
Unit 1401/1401
CLI/server e2e: git-89, git-120, git-115 fresh install, cli-fresh-install 54/54
Hook suite, stock macOS PATH (no Homebrew, no timeout), scratch HOME 54/54
Hook suite, slim PATH (no jq, no ps), scratch HOME 54/54
Drift analyzer incl. RPCs 14/14, no unallowed findings
Venue v1.8.0: flow / egress / projects PASS / PASS / PASS
Venue current setup.sql applied twice: flow PASS
EXPECTED_FAILURES empty

Every item's probe passes in both venue flows: remote recall, cold recall, thread-sync PARTIAL, reload failure, archive by prefix, and archive of a nonexistent id. The only non-2xx responses are the two designed probes for production-only optional columns (task_observations, archived_at).

Hook suites ran with a scratch HOME. With the real HOME on the dev machine, two "not detected" tests fail on every branch because the user-level Claude config registers gitmem. CI runners have no such file.

Venue: the disposable vujndhcumgroztfkkdus, left on v1.8.0. A first local attempt failed before any test ran, because PostgREST's schema cache was stale after the reset. The local script now sends the same NOTIFY pgrst the CI script sends, and the whole sequence was rerun from the v1.8.0 reset.

🤖 Generated with Claude Code

Claude and others added 23 commits September 22, 2026 20:12
When the local vector index isn't ready (every server start until warm-up,
a new or empty store, a failed load, GITMEM_SEARCH_MODE=remote), recall
called match_gitmem_learnings_weighted. No setup.sql defines it: GIT-93
took the name from nTEG's store. Every customer's cold recall got
PGRST202 and returned nothing.

- Default prefix: call gitmem_scar_search with similarity_threshold. If
  the store answers PGRST202 (no such function, e.g. nTEG's), fall back to
  match_gitmem_learnings_weighted with match_threshold. The first that
  exists is remembered for the process; any other error is not retried.
- Non-default prefix: only match_<table>_weighted. gitmem_scar_search reads
  gitmem_learnings by name and would search the wrong table.
- gitmem_scar_search doesn't return learning_type, applies_when,
  why_this_matters and the other fields recall renders. They are fetched
  for the matched ids in one select; if that fails, the rows are returned
  without them.
- Remove the dead semanticSearch (match_gitmem_learnings, no callers).

scar-search-rpc-name.test.ts: "tracks the prefix" asserted
match_gitmem_learnings_weighted for the default prefix, the bug itself;
it now asserts gitmem_scar_search. The semanticSearch test goes with the
function. +4 tests: PGRST202 fallback, no retry on a real error, the
follow-up select, and tolerating its failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-114)

scanRpcNames resolves "/rpc/<name>" in string and template literals
(getTableName expanded) and, where the URL is built from a variable, the
name properties of the candidate objects in the same file. Fixed https://
URLs (licensing) are classified external. Every store RPC must be defined
by setup.sql or allowlisted by ticket; stale entries and unresolved
dynamic names fail. Planted-drift self-test included. +5 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ld server (GIT-114)

Both must return a seeded scar through the store's RPC. On 1.11.0 the RPC
404s (PGRST202) and both probes find nothing: exit 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
auto-retrieve-hook.sh ran `timeout 2.5 node …`. macOS ships no timeout,
the error went to /dev/null, and the hook exited as "nothing relevant", so
auto-retrieve never ran on a stock Mac.

- quick-retrieve bounds its own run (armDeadline, 2.5 s, unref'd); the
  hook calls node directly.
- Hooks copied into <repo>/.gitmem/hooks looked for a package named
  "gitmem"; it is gitmem-mcp. The lookup now tries the gitmem-mcp bin,
  npx's cache (how most installs run it) and the global node_modules.
- A missing node or quick-retrieve module is a visible non-blocking hook
  error (exit 1, reason on stderr), not a silent empty result. The node
  check runs before prompt extraction, which needed node too.
- session-start.sh: debug log per user in $TMPDIR and never fatal under
  set -e; no pgrep skips the 7 s readiness gate instead of stalling;
  `which` -> `command -v`; no GNU-only `date +%N`.
- resolve-root.sh: pid liveness via kill -0, then /proc (Linux) or ps
  (macOS), not kill's English error text; host from bash's $HOSTNAME.
- credential-guard.sh: the path is JSON-escaped. A quote in it made the
  block output invalid JSON, which Claude Code treats as allow.

test-hooks.sh: +7 portability tests using PATHs with the tool removed
(53/53; main fails 6). Unit: +4 (deadline, unref, no timeout call left).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…sh depends on it (GIT-116)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…GIT-117)

syncThreadsToSupabase called create/resolve/touch helpers that caught their
own failures and returned null/false/void, which the loop never read. So a
thread whose write the store rejected was counted as synced: session_close
reported COMPLETE, pruned threads.json and deleted the session files, and
a local-only thread was dropped as stale 24 h later.

- The loop uses throwing variants: a create counts when directUpsert
  returns the row; a resolve or touch counts only when the PATCH returns a
  row (0 rows = failed); a thread the store doesn't have is a failure, not
  a no-op. touchThreadsInSupabase reports per-thread outcomes.
- Partial sync: the close says PARTIAL (session saved, threads not) and
  names the failed ids; threads.json keeps them marked sync_pending.
- session_start keeps sync_pending threads whatever their age and prefers
  their local state over the store's (so an unwritten resolution survives)
  until a later close writes them; a full sync clears the flag.

thread-sync-result.test.ts: the GIT-70 tests only failed the lookup, the
one call that propagated. +6 tests fail the writes; 5 fail on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ks PARTIAL + carry-forward (GIT-117)

On 1.11.0 the store rejects the thread and the close says COMPLETE with
no pending marker: exit 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…q (GIT-116)

macos-latest (macOS 15) has /usr/bin/jq as a system tool, so asserting its
absence failed the job. The jq-less path stays covered by hooks-without-jq
on Linux; the macOS leg now reports whether stock jq is present.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s (GIT-118)

loadScarsFromSupabase returned [] on any error with no flag. flushCache
(fired after every create_learning, and every 15 min) reinitialised the
index and overwrote hook-scars.json with it, logged "N -> 0", and reported
success. A failed init was cached as ensureInitialized's promise, so it
was never retried.

- loadScarsFromSupabase reports ok/error; on failure it offers the last
  disk vector cache (stale) instead of [], and never as "the store is empty".
- A failed reload keeps the index and the hook cache (an empty index takes
  the stale disk copy) and returns success: false with the cause.
- A failed load retries with backoff (30 s, doubling, 10 min cap);
  ensureInitialized forgets a failed init; an init after a stale start
  replaces the stale rows (initialize() is a no-op once initialized).
- create_learning awaits a refresh and its display says when the recall
  index could not be refreshed (index_refreshed: false). The post-write
  refresh uses the per-row delta (GIT-98), not the disk-bypassing flush.

+7 startup tests (6 fail on main), +2 create_learning display tests. Three
test files mocked startup.js without refreshIndexAfterWrite; the missing
export threw inside create_learning's try and every call reported "NOT
refreshed" unseen. Their mocks now provide it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…all survives (GIT-118)

A forwarding proxy in front of the venue fails GET gitmem_learnings* on
demand while writes pass. On 1.11.0: "Flushed and reloaded: 5 -> 0",
create_learning silent, recall empty; exit 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… not durable (GIT-119)

archive_learning sent id=like.<prefix>% against a uuid column, which Postgres
rejects (42883), so prefix archiving failed on every store. The prefix now
resolves from the local index, or from the store as a UUID range when cold.

directPatch returns { count, rows }. PostgREST answers 200 [] when a PATCH
matched nothing; all 8 callers now treat count 0 as not durable (archive of a
nonexistent id, session embedding, superseded mark, thread resolve/touch/
auto-archive, relevance update, transcript path).

Tests: +12 (9 parameterized zero-row cases, 3 archive). Venue driver gains a
prefix archive of a row the index has not seen, and a nonexistent full UUID.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ooks (GIT-120)

install-hooks replaced settings.hooks wholesale and uninstall-hooks deleted
the key, so users' own hooks were lost. Both now work per event and per
command: only commands gitmem installed are replaced or removed, a matcher
group keeps its other commands, events install-hooks does not manage stay
untouched, the file is backed up before every write, and the output lists
what changed. An unparseable file is no longer overwritten "fresh".

gitmem detection matches gitmem's script paths instead of any "gitmem"
substring, which made a foreign hook under a gitmem-named directory look
like an existing install.

Tests: new CLI e2e (9) with foreign hooks present, wired into CI. Two
cli-fresh-install assertions that pinned the old wipe are updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	tests/e2e/blank-supabase.mjs
# Conflicts:
#	tests/e2e/blank-supabase.mjs
# Conflicts:
#	src/services/thread-supabase.ts
…stack)

GIT-117's tests mocked directPatch as an array. After GIT-119 it returns
{ count, rows }; with the old mock a 0-row PATCH read as success.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ig + hooks (GIT-115)

init seeded <cwd>/.gitmem while the server reads ~/.gitmem (GIT-91), so a
fresh install had no starter lessons and was told its store was "NOT being
read". The wizard and init-scars now seed learnings, threads, collections
and the closing payload + template into the root the server reads
(GITMEM_DIR, then GITMEM_HOME/.gitmem, then ~/.gitmem). The repo's .gitmem/
keeps only config.json (project name) and the hook scripts.

- A repo .gitmem/ with only config.json and hooks, or only what an older init
  seeded, is not a stranded store. Real records (a user's learning, thread or
  decision) now count, which main did not check at all.
- The SessionStart hook reads the repo's project name without jq (node).
- uninstall removes the repo's config.json and hooks; the shared store is
  kept unless --all or the user says delete. Hook removal and the wizard's
  hook merge use the GIT-120 module (bin/hooks-merge.js, identical to #55).
- telemetry commands use the server's root.

Tests: fresh-install e2e in a clean HOME (9, all fail on main), unit +5,
hook suite +1. cli-fresh-install made hermetic (scratch HOME) and repaired;
both e2e files wired into CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	.github/workflows/ci.yml
#	bin/gitmem.js
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nTEG-dev nTEG-dev changed the title Release D: GIT-113 Phase 1 (GIT-114, 116-120) Release D: GIT-113 Phase 1 (GIT-114 to GIT-120) Sep 23, 2026
CHANGELOG [Unreleased] -> [1.11.1] - 2026-09-22, fresh [Unreleased] above.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nTEG-dev
nTEG-dev marked this pull request as ready for review September 23, 2026 04:44
@nTEG-dev
nTEG-dev merged commit 60a990e into main Sep 23, 2026
7 checks passed
@nTEG-dev
nTEG-dev deleted the release/D branch September 23, 2026 05:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant