Compare commits

..

212 Commits

Author SHA1 Message Date
airkjw 4e08d70015 build: rebuild plugin artifacts for v13.3.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 15:16:44 +09:00
airkjw 59f30a5f3e chore: merge upstream v13.3.0 + keep local fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 15:15:06 +09:00
Alex Newman c3d2af7c14 docs: regenerate CHANGELOG for v13.3.0 2026-05-21 03:26:17 -07:00
Alex Newman e4ec654355 chore: bump version to 13.3.0 2026-05-21 03:25:35 -07:00
Alex Newman b0e896e286 fix: stop codex transcript replay after hooks migration (#2365) 2026-05-21 01:48:59 -07:00
Alex Newman e53d1530ff fix(mcp): drop root .mcp.json so plugin's mcp-search isn't duplicated (#2411)
The repo shipped both a root-level .mcp.json and plugin/.mcp.json with
identical mcp-search launchers — kept in sync by a build-time guard and
a test. The root file was a holdover from when devs working inside the
repo could load mem-search without installing the plugin. With the
plugin universally installed, every plugin user now sees `/doctor` warn:

    Plugin (claude-mem @ plugin:claude-mem:mcp-search): MCP server
    "mcp-search" skipped — same command/URL as already-configured
    "mcp-search"

…because Claude Code dedupes by command and skips the plugin's
namespaced registration. The duplicate is functionally harmless but
suppresses the canonical `plugin:claude-mem:mcp-search` entry.

This removes the root .mcp.json entirely and re-points everything that
referenced it at the bundled plugin copy:

- .mcp.json: deleted
- .codex-plugin/plugin.json: mcpServers → ./plugin/.mcp.json
- package.json: drop .mcp.json from files
- scripts/build-hooks.js: drop root-file requirement + sync check
- scripts/sync-marketplace.cjs: drop syncManagedFiles entry
- src/npx-cli/commands/install.ts: drop from allowedTopLevelEntries
- tests/infrastructure/plugin-distribution.test.ts: drop two tests
  enforcing the now-removed root file

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 01:48:50 -07:00
Alex Newman ea057feeec feat(skills): add oh-my-issues for root-cause issue clustering (#2409)
* feat(skills): add oh-my-issues for root-cause issue clustering

Codifies the consolidation method that turned ~100 open issues into 6
plan-master issues during the v13.0.1 cycle. Three modes: cluster pass
(initial reduction), triage (route a new bug into an existing master),
bundle (ship a PR that closes the cluster atomically).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skills): correct oh-my-issues cluster-pass instructions

- Cluster pass step 1: drop the misleading single-call pattern;
  point to a paginated list + per-issue comment fetch since
  `gh issue list --json comments` returns only counts and
  `--limit` silently truncates large backlogs.
- GitHub CLI primitives: replace the buggy snippet with a
  total-count check, paginated listing, per-issue comment loop,
  and REST API fallback for repos with >1000 open issues.
- make-plan: add See Also section linking oh-my-issues so the
  planning skill knows about its issue-side sibling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skills): use search API for issue counts and tag fenced block

- repos/{owner}/{repo}.open_issues_count includes PRs. Switch to the
  search/issues API which differentiates issues from PRs so the
  cluster-pass count is accurate.
- Add `text` language tag to the standardized redirect comment
  fenced block (MD040).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 01:48:41 -07:00
Alex Newman 09e74bbf76 feat(skills): add weekly-digests skill for serial timeline narrative (#2399)
* feat(skills): add weekly-digests skill for serial timeline narrative

Generate a chapter-per-ISO-week narrative digest of a project's full
claude-mem history. Splits the timeline by ISO week, then runs
consecutive (non-parallel) subagents — each receiving the prior week's
carry-forward block — to produce a coherent multi-chapter serial.

Encodes the pipeline discipline that emerged from running it end-to-end:
narrative budget scaled to obs count, carry-forward capped and pruned,
register evolution tracked explicitly, components as characters,
silence as story, no false ending in the final chapter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skills/weekly-digests): degeneralize from hardcoded 30-week assumption

The skill was overfit to a single run that happened to span 30 ISO weeks.
On a 2-week project the prompt template would tell the subagent it was
writing chapter N of a "30-part serial narrative" — which lies.

Changes:
- Frontmatter and opening prose no longer claim a fixed chapter count.
- Subagent prompt template uses "chapter N of TOTAL" wording that scales
  to any N including 1.
- Added explicit N=1 handling: apply first-and-final treatment together.
- Genericized component-as-character and meta-recursion examples — they
  no longer import claude-mem's specific cast as if mandatory.
- Filename zero-pad width now derived from N (works past 99 weeks).
- Examples section shows long-project, short-project, and N=1 flows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 01:48:33 -07:00
Alex Newman bd96308a51 feat(skills): add design-is for Dieter Rams design audits (#2483)
Audits a design against Rams' ten principles with evidence-cited
scores (0-3 per principle), produces a NEW/REFINE/REDESIGN verdict,
and hands off a ready-to-run /make-plan prompt for the chosen outcome.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 01:48:21 -07:00
airkjw b4278e67f9 build: rebuild plugin artifacts for v13.2.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:33:10 +09:00
airkjw e6e49aed8e chore: merge upstream v13.2.0 + keep local fixes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:32:28 +09:00
Alex Newman 37d24944af feat(skills): wowerpoint share-link upload step (#2445)
* feat(skills): wowerpoint share-link upload step

After the kawaii NotebookLM PDF lands on disk, the subagent now also POSTs
it to the WOWerpoint Server (if configured) and reports back a share URL.
The PDF is still the backup; the share URL is the primary deliverable.

Gated on three env vars (WOWERPOINT_API_BASE, WOWERPOINT_VIEWER_BASE,
WOWERPOINT_UPLOAD_TOKEN) — if any are missing the skill skips the upload
silently and behaves exactly as before.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skills): address CodeRabbit + Greptile findings on wowerpoint

- Drop the ~/.wowerpoint.env reference: the subagent inherits the parent's
  environment and never sources a dotenv file, so storing vars there would
  silently disable the upload step. Documented only the shell-export path.
- Switch jq parsing to `.id // empty` so a missing key yields an empty
  string instead of the literal "null", letting the [-z "$DECK_ID"] guard
  fire correctly on error responses.
- Capture the full JSON response so a non-empty .error field is surfaced as
  a warning rather than emitting an invalid …/d/null share URL.
- Add TITLE to the subagent template's Inputs block so the parent agent
  knows it must supply a title slot the curl command depends on.
- Make step 6 itself guard on the env vars instead of relying on prose, so
  the snippet works in isolation if a future agent skips the surrounding
  instructions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skills): gate the top-level upload snippet on env vars too

CodeRabbit pointed out the prose snippet at the top of the Share-link
section uploaded unconditionally, while the subagent step 6 version had the
env-var guard. Anyone copying the standalone snippet would have skipped
"silently" by failing the curl request. Wrapping both in the same guard
keeps the two snippets in sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skills): cap wowerpoint upload curls at 30 s

Greptile flagged that a bare curl on an unreachable WOWERPOINT_API_BASE can
sit on the OS TCP timeout (75–130 s) before returning, stalling the
background subagent and delaying the completion notification. Adding
--connect-timeout 10 --max-time 30 to both upload snippets bounds the
hang and lets the share-link step fail fast.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(skills): wowerpoint slug example reflects 3-word IDs

Server now mints adjective-noun-creature slugs (e.g. quirky-compass-hawk)
instead of base64url. The curl/jq snippets are unchanged — they already
parse .id as opaque — but the prose was stale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(skills): wowerpoint slug example reflects title-aware IDs

Server now slugifies the title and appends a creature suffix
(tokenrouter-quest-hawk) instead of three random words. Falls back to a
3-word slug when the title is empty or non-ASCII. The curl/jq snippets
are unchanged — they parse .id as opaque — but the prose was stale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:38:37 -07:00
Alex Newman a81deb495a docs: regenerate CHANGELOG for v13.2.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:44:19 -07:00
Alex Newman f1b73a46da chore: bump version to 13.2.0 2026-05-11 18:41:25 -07:00
Alex Newman 19b1ee17da feat(skills): add wowerpoint for one-doc kawaii NotebookLM slide decks (#2430)
* feat(skills): add wowerpoint for one-doc kawaii NotebookLM slide decks

A skill that turns one source document into a kawaii NotebookLM slide-deck
PDF. Wraps the `notebooklm` CLI with the kawaii-prompt + `--format detailed`
defaults, and the spawn-subagent-and-end-turn pattern so generation (~10 min)
never blocks the main conversation.

Single-source-per-deck is enforced by the workflow shape: step 1 is "confirm
or write the source doc"; step 3 adds exactly one source. If the doc is
non-existent or thin, write it first using mem-search and sequential
thinking — don't paper over a weak source by stacking more sources.

Slide-deck only — videos and podcasts from the same engine are noticeably
worse and out of scope; refer the user to the `notebooklm` CLI directly if
they want those.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(skills): address CodeRabbit findings on wowerpoint

- Document `jq` as a required workflow dependency in setup
- Add `text` language identifier to three unlabeled fenced code blocks
  (MD040 lint compliance)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 18:27:57 -07:00
Alex Newman 79593c1c1c docs: regenerate CHANGELOG for v13.1.0 2026-05-11 00:30:54 -07:00
Alex Newman 8bd19b5c5a chore: bump version to 13.1.0 2026-05-11 00:28:52 -07:00
Alex Newman e7bbb2a9aa server-beta: Phases 4–13 — event pipeline, generation, MCP, compat, Docker, team audit, observability (#2383)
* feat(server-beta): Phase 4 — Postgres event-to-generation-job pipeline

Adds POST /v1/events, /v1/events/batch, GET /v1/jobs/:id, GET /v1/events/:id,
and POST /v1/memories on the server-beta runtime, backed by Postgres.

- Event row + outbox generation-job row insert in one withPostgresTransaction.
- BullMQ enqueue happens after commit; enqueue failure leaves the row queued
  for Phase 3 startup reconciliation.
- ?generate=false skips the outbox; ?wait=true returns queue status only,
  never observation IDs (provider generation is Phase 5).
- Batch pre-validates all event projectIds against api-key scope before any
  write; mixed-project batches reject 403 with zero side effects.
- /v1/memories is a direct insert alias — no generator, no outbox.
- Cross-tenant /v1/jobs/:id returns 404 to avoid leaking row existence.
- New PostgresAuthMiddleware reads api_keys by SHA-256 hash; populates
  req.authContext.teamId/projectId; legacy ServerV1Routes (SQLite, used by
  worker runtime) is left untouched.
- Tests: unit suite hardened with stubbed pool.query so route registration
  is safe; integration tests skip cleanly without CLAUDE_MEM_TEST_POSTGRES_URL.

Verification: 87 pass / 1 skip / 0 fail. No new typecheck errors. Required
greps for WorkerService and MemoryItemsRepository in src/server/routes/v1
and src/server/runtime return no hits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 5 — provider observation generator

Adds independent provider generation under src/server/generation/ with no
worker coupling. Server beta can now generate observations end-to-end:
event -> outbox -> BullMQ -> provider -> parser -> persisted observation.

- ProviderObservationGenerator orchestrates: lock outbox (queued -> processing),
  reload agent_event from Postgres (BullMQ payload is advisory only), call
  provider, hand raw text to processGeneratedResponse, route errors via
  markGenerationFailed with retryable flag from ServerClassifiedProviderError.
- processGeneratedResponse parses with parseAgentXml, persists via
  PostgresObservationRepository with deterministic
  generation_key = generation:v1:{job_id}:{index}:{fingerprint},
  links via PostgresObservationSourcesRepository, advances outbox status,
  appends observation_generation_job_events, audits — all in one
  withPostgresTransaction. Idempotent on retry via UNIQUE constraints.
- Three provider adapters under src/server/generation/providers/:
  Claude, Gemini, OpenRouter. Self-contained — no imports from
  src/services/worker/*. Worker providers unchanged.
- Shared error classification + prompt builder under providers/shared/.
  Prompt builder strips <private> at the edge; fully-private batches
  emit <skip_summary /> without billing the provider.
- ActiveServerBetaGenerationWorkerManager wires BullMQ Worker via
  ServerJobQueue.start(...) with concurrency 1 + autorun:false +
  worker.on('error') per BullMQ docs.
- New GET /v1/events/:id/observations on ServerV1PostgresRoutes returns
  observations linked via observation_sources, team/project scoped.

Verification: 104 pass / 4 skip / 0 fail. No typecheck regressions.
Anti-pattern greps clean for services/worker imports under src/server,
WorkerRef/ActiveSession/SessionStore in src/server/generation.

Deferred: ModeManager loading uses a stable fallback observation type
list; summary and reindex queue lanes are not yet wired.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 6 — independent server session semantics

server_sessions is now the canonical Server beta session model. Sessions
are independent of legacy worker ActiveSession state.

- PostgresServerSessionRepository extended: findByExternalIdForScope,
  endSession (idempotent via COALESCE(ended_at, now())),
  markGenerationStarted/Completed/Failed, listUnprocessedEvents (filters
  agent_events with completed agent_event jobs).
- ServerSessionRuntimeRepository wraps the repo; every method requires
  explicit team_id + project_id and validates scope via assertProjectOwnership.
- SessionGenerationPolicy supports per-event (default), debounce
  (BullMQ delayed-job replace via getJob+remove+add), and end-of-session.
  Configured via CLAUDE_MEM_SERVER_SESSION_POLICY and
  CLAUDE_MEM_SERVER_SESSION_DEBOUNCE_MS env vars; per-team override hooks
  are exposed on ServerV1PostgresRoutesOptions for future settings layer.
- POST /v1/sessions/start (find-or-create on (project_id, external_session_id),
  GET /v1/sessions/:id (scoped 404), POST /v1/sessions/:id/end
  (transactional: end + create summary outbox via UNIQUE collapse +
  enqueue post-commit). Re-ending is fully idempotent.
- processSessionSummaryResponse persists summary as kind='summary'
  observation with the same idempotency model
  (generation_key + observation_sources UNIQUE).
- ProviderObservationGenerator dispatches on source_type:
  agent_event -> processGeneratedResponse, session_summary ->
  processSessionSummaryResponse; loadEvents handles session-summary
  by loading unprocessed events.
- ActiveServerBetaGenerationWorkerManager wires summary BullMQ lane
  alongside event lane (concurrency=1, autorun=false, error listener
  attached per BullMQ docs).

Verification: 110 pass / 6 skip / 0 fail. Net typecheck error count
unchanged at 24 (pre-existing, none in Phase 6 files). Anti-pattern
greps clean for ActiveSession/SessionStore in src/server/runtime,
no worker imports anywhere in src/server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 7 — hook routing without worker dependency

Hooks can now talk directly to server-beta when CLAUDE_MEM_RUNTIME=server-beta
is selected, with a clean worker fallback when server-beta is unhealthy.

- src/services/hooks/server-beta-client.ts — typed HTTP client for
  /v1/sessions/start, /v1/events, /v1/sessions/:id/end. Throws
  ServerBetaClientError with kind classification (missing_api_key,
  transport, timeout, http_error, invalid_response) and isFallbackEligible
  helper. Zero imports from services/worker/.
- src/services/hooks/runtime-selector.ts — reads CLAUDE_MEM_RUNTIME from
  settings, returns worker or server-beta context, logs
  [server-beta-fallback] reason=<code> on every config-time fallback.
- src/services/hooks/server-beta-bootstrap.ts — Postgres-backed API key
  bootstrap. Find-or-creates local-hook-team + local-hook-project,
  generates cmem_<random> key (SHA-256 hashed), inserts into api_keys
  with scopes events:write/sessions:write/observations:read/jobs:read.
  Settings file written with chmod 0600. rotateServerBetaApiKey() wired
  to a new `claude-mem server keys rotate` command.
- src/cli/handlers/{observation,session-init,summarize}.ts — every hook
  handler tries server-beta first when configured, falls through to the
  existing worker path on transport/5xx/429/missing-key. One WARN line
  per fallback. Hook JSON output shape unchanged.
- src/shared/SettingsDefaultsManager.ts — three new keys with defaults:
  CLAUDE_MEM_SERVER_BETA_URL, CLAUDE_MEM_SERVER_BETA_API_KEY,
  CLAUDE_MEM_SERVER_BETA_PROJECT_ID.
- src/npx-cli/commands/install.ts — when installer selects server-beta
  runtime and CLAUDE_MEM_SERVER_DATABASE_URL is set, bootstraps a local
  API key automatically. Warns and continues if the DB URL is missing.

plugin/scripts/*.cjs bundles rebuilt via npm run build to pick up the
new hook handler code path. No plaintext keys in the bundle (verified).

Verification: 16 hook unit tests pass; 275 server/storage/services tests
pass with 7 pre-existing failures (verified independent of this change
via git stash --include-untracked). Build clean. No new typecheck
errors in Phase 7 files.

Anti-pattern guards verified:
- /api/sessions/observations only reached via explicit fallback path
- server-beta runtime never starts the worker process
- API keys live only in ~/.claude-mem/settings.json (chmod 0600), never
  in the bundle (grep confirmed)
- Worker fallback preserved, observable via single WARN line per call

Deferred: semantic context injection (UserPromptSubmit hook) stays
worker-only; server-beta does not yet expose /v1/context/semantic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 8 — MCP backed by server-beta core

MCP tools now route through server-beta in server-beta mode while keeping
worker-mode search/timeline/get_observations tools fully working.

- src/servers/mcp-server.ts — five new observation_* tools registered:
  observation_add, observation_record_event, observation_search,
  observation_context, observation_generation_status. Three memory_*
  compatibility aliases delegate to the canonical handlers. Worker
  auto-start is gated when selectRuntime() === 'server-beta' so MCP
  in server-beta mode never spawns the worker.
- src/services/hooks/server-beta-client.ts — addObservation,
  searchObservations, contextObservations, getJobStatus added so MCP
  shares one transport with hooks (Phase 7).
- src/server/routes/v1/ServerV1PostgresRoutes.ts — POST /v1/search and
  POST /v1/context REST cores backed by PostgresObservationRepository
  full-text search (GIN tsvector from Phase 1).
- Existing memory_search/timeline/get_observations tools call
  callWorkerAPI unchanged in worker mode; worker tests unaffected.

Verification: 39 pass / 4 skip / 0 fail on targeted suite. Pre-existing
7 baseline failures verified independent (git stash). No new typecheck
errors. WorkerService grep clean across src/servers/mcp-server.ts and
src/server/.

Anti-pattern guards verified:
- No duplicate generation logic in MCP — observation_record_event hits
  /v1/events which owns event+outbox+enqueue inside one tx
- WorkerService not imported anywhere under MCP server-beta path
- No hardcoded worker URLs — all transport via Phase 7 ServerBetaClient
- memory_* aliases retained, single handler per pair

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 9 — compatibility adapters without coupling

Legacy /api/sessions/observations and /api/sessions/summarize endpoints
keep working on server-beta runtime by translating to AgentEvent and
session-end calls — no worker code, no route duplication.

- src/server/services/IngestEventsService.ts — shared event-ingest path
  used by both /v1/events and the compat adapter. Owns transactional
  event row + outbox row + lifecycle log + post-commit BullMQ enqueue,
  honors Phase 6 SessionGenerationPolicy.
- src/server/services/EndSessionService.ts — shared session-end path
  used by both /v1/sessions/:id/end and the compat adapter. Idempotent
  ended_at + summary outbox + deterministic summary job id.
- src/server/compat/SessionsObservationsAdapter.ts — translates legacy
  POST /api/sessions/observations payload (Claude Code transcript shape)
  -> AgentEvent (source_adapter='claude-code-compat',
  event_type='tool_use') -> IngestEventsService.ingestOne. Resolves
  contentSessionId to server_sessions via find-or-create.
- src/server/compat/SessionsSummarizeAdapter.ts — translates legacy
  POST /api/sessions/summarize -> EndSessionService.end. Preserves the
  legacy agentId -> {status:'skipped', reason:'subagent_context'}
  behavior so existing clients see the same response shape.
- src/server/routes/v1/ServerV1PostgresRoutes.ts — refactored to
  delegate to the new shared services (-203 LoC net) so /v1 and
  /api compat both call the SAME canonical code path.
- src/server/runtime/ServerBetaService.ts — registers both compat
  adapters alongside ServerV1PostgresRoutes, sharing service instances.
- docs/server-beta-parity-map.md — full enumeration of legacy /api/*
  routes labeled native, adapter, or unsupported (with reasons).
  Viewer read-path adapters explicitly listed as unsupported pending
  a future viewer-rewrite phase.

Verification: 7 compat tests pass, 6 v1-routes tests still pass
(refactor preserved behavior), 4 session-routes tests pass. Pre-
existing 16 baseline failures verified independent via git stash.
Zero new typecheck errors.

Anti-pattern guards verified:
- No services/worker/http/routes or WorkerService imports under
  src/server/compat or src/server/runtime
- Compat adapters are thin translators with names ending in *Adapter
  and a top-of-file comment noting they are legacy compatibility
- /v1/* remains the canonical Server beta API; compat adapters
  call shared services rather than acting as a parallel API

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 10 — Docker stack and deployable runtime

Server beta now ships as a Docker stack with no worker process anywhere
and a separate horizontal generation worker for scaling.

- src/server/runtime/create-server-beta-service.ts — validateServerBetaEnv()
  fails fast on missing CLAUDE_MEM_SERVER_DATABASE_URL, requires
  CLAUDE_MEM_QUEUE_ENGINE=bullmq in Docker, rejects
  CLAUDE_MEM_AUTH_MODE=local-dev and CLAUDE_MEM_ALLOW_LOCAL_DEV_BYPASS
  inside containers (detected via /.dockerenv or CLAUDE_MEM_DOCKER=1).
  Adds CLAUDE_MEM_GENERATION_DISABLED so the HTTP service can run
  generator-free.
- src/server/runtime/ServerBetaService.ts — runServerBetaGenerationWorker
  for the dedicated consumer process; runServerBetaApiKeyCli is a new
  Postgres-backed `server api-key` command (the legacy worker CLI wrote
  to SQLite and was invisible to the Postgres runtime); getQueueHealth
  shim feeds /api/health a consistent ObservationQueueHealth shape.
- src/npx-cli/commands/{runtime,server}.ts — `claude-mem server worker
  start` subcommand that boots only the BullMQ consumer.
- docker/claude-mem/{Dockerfile,entrypoint.sh} — entrypoint forces
  CLAUDE_MEM_DOCKER=1 + CLAUDE_MEM_RUNTIME=server-beta and exposes
  three modes: server (HTTP only, generation disabled), worker (BullMQ
  consumer), shell. Worker bundle is no longer the default CMD.
- docker-compose.yml — full stack: postgres + valkey + claude-mem-server
  (HTTP-only) + claude-mem-worker (generation consumer). Wires
  service-to-service env vars.
- scripts/e2e-server-beta-docker.sh + docker/e2e/server-beta-e2e.mjs —
  E2E now hits /v1/sessions/start, /v1/events?wait=true, /v1/jobs/:id;
  asserts no worker-service.cjs process anywhere in the stack;
  one-shot docker compose run --rm verifies local-dev auth is
  rejected with the expected stderr; restart-and-verify confirms
  Postgres durability and BullMQ retry idempotency.
- docs/server.md — full Phase 10 doc: stack diagram, env table,
  worker mode, auth-in-Docker policy.
- docs/api.md — event generation semantics (wait=true, generationJob).

Verification: full Docker E2E PASSED on live daemon
(phase1 + phase2 + restart-and-verify + revoked-key + no-worker-
process + local-dev-rejected). Unit tests 292 pass / 9 skip / 7 fail
(7 fails pre-existing baseline). Zero new typecheck errors.

Anti-pattern guards verified:
- entrypoint never execs worker-service.cjs; E2E greps prove no
  worker process anywhere in the stack
- validateServerBetaEnv refuses local-dev auth in Docker with explicit
  remediation message; ALLOW_LOCAL_DEV_BYPASS rejected the same way
- Docker requires CLAUDE_MEM_QUEUE_ENGINE=bullmq; in-process queue
  rejected at startup
- claude-mem worker / worker-service / WorkerService greps clean
  in docker/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 11 — team-aware generation with audit chain

Generation jobs now carry team_id/project_id/api_key_id/actor_id/
source_adapter from enqueue through execution; the outbox is reloaded
from Postgres before any side effect so BullMQ payload can never act
as auth authority.

- src/server/jobs/types.ts — ServerGenerationJobPayloadSchema (Zod
  discriminated union) requires team_id, project_id, generation_job_id,
  source_adapter, api_key_id, actor_id (nullable), source_type, source_id,
  plus event_id / server_session_id per kind. assertServerGenerationJobPayload
  is called at enqueue (outbox.ts) and again at execution boundary.
- src/server/services/{IngestEventsService,EndSessionService}.ts +
  SessionGenerationPolicy.ts — thread identity context (apiKeyId, actorId,
  sourceAdapter) into both event and summary BullMQ payloads.
- src/server/generation/ProviderObservationGenerator.ts —
  loadCanonicalOutbox loads the outbox row WITHOUT scope filter, then
  compares candidate.team_id/project_id to payload.team_id/project_id;
  mismatch -> ServerGenerationScopeViolationError (non-retryable),
  failed status, generation_job.scope_violation audit. isApiKeyRevoked
  checks api_keys (revoked_at, expires_at, row missing) before any
  provider call; revoked -> generation_job.revoked_key audit + non-
  retryable failure. generation_job.processing audit emitted on lock.
- src/server/generation/processGeneratedResponse.ts — generated
  observations carry team_id/project_id/server_session_id from the
  reloaded source row (not job payload). observation_sources.metadata
  records source_adapter, actor_id, api_key_id for traceability.
  observation.created audit per observation; generation_job.completed
  audit per terminal transition. All audit rows reference the same
  generation_job_id in details.
- src/server/routes/v1/ServerV1PostgresRoutes.ts — GET /v1/teams/:id/jobs
  and GET /v1/projects/:id/jobs with SQL-layer scoping (WHERE team_id=$1
  [AND project_id=$2] [AND status=$3]); cross-tenant returns 404 to
  avoid leaking row existence. Pagination via status/limit/offset.
  audit_log rows for event.received, event.batch_received, observation.read.
- src/server/compat/{SessionsObservationsAdapter,SessionsSummarizeAdapter}.ts —
  propagate apiKeyId and sourceAdapter='claude-code-compat'.

Verification: 162 pass / 10 skip / 0 fail. Pre-existing failures in
tests/services/queue and tests/services/worker confirmed independent
via git stash. Zero new typecheck errors in server-beta files.
Required greps:
  rg "team_id.*req\.body|project_id.*req\.body" src/server -> 0 matches
Audit chain integration test passes — generation_job.processing,
observation.created, and generation_job.completed audit rows all
share the same generation_job_id reference.

Anti-pattern guards verified:
- BullMQ payload never acts as auth authority — Postgres outbox
  reload with mismatch check happens before every side effect
- team_id / project_id never derived from request body for scope
  decisions; always req.authContext.teamId / projectId
- Application-layer team/project filtering forbidden — listJobsForScope
  pushes scope into the SQL WHERE clause
- Project-scoped key on cross-project /v1/teams/:id/jobs returns 404
- Revoked api keys cause non-retryable failure with audit before
  any provider call

Deferred: a redundant generation_job.queued audit_log row (already
covered by observation_generation_job_events lifecycle log per Phase 1
schema split). Compat adapters set actor_id=null but propagate
api_key_id which is the canonical reference downstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): Phase 12 — observability and operations

Operators can now inspect, retry, and cancel generation jobs from the
CLI; queue lane metrics flow into /api/health and /v1/info; every
request gets a stable request_id that flows through HTTP -> audit ->
outbox -> generator -> completion log.

- src/server/middleware/request-id.ts — honors safe inbound X-Request-Id,
  mints uuid v4 otherwise. Set on req.requestId and echoed via response
  header so external traces can correlate.
- src/server/jobs/ServerJobQueue.ts — QueueEvents wired with completed,
  failed, progress, stalled, error listeners; lifecycle counters
  exposed via observe() API. Logs emitted as
  [generation] job=<id> source_type=<...> duration=<ms> attempts=<N>
  reason=<message>. Stalled and error counters survive worker restart.
- src/server/jobs/types.ts — ServerGenerationJob payload schema
  extended with optional request_id; flows through from HTTP into
  every BullMQ job.
- src/server/queue/ObservationQueueEngine.ts — health snapshot now
  carries per-lane (event, summary) counts via
  ObservationQueueHealthLaneSnapshot.
- src/server/runtime/{ActiveServerBetaQueueManager,
  ActiveServerBetaGenerationWorkerManager,ServerBetaService}.ts —
  per-lane getJobCounts feed /api/health and /v1/info; stalled events
  audit through audit_log with action generation_job.stalled.
- src/server/routes/v1/ServerV1PostgresRoutes.ts —
  GET /v1/jobs (status/source_type/since/limit/offset, scope from
  api-key, payload stripped unless ?include=payload AND admin scope),
  POST /v1/jobs/:id/retry (idempotent; queued -> no-op; audit
  generation_job.retried_by_operator), POST /v1/jobs/:id/cancel
  (terminal -> no-op; audit generation_job.cancelled_by_operator;
  generator reload-before-side-effects already prevents double work).
- src/server/services/IngestEventsService.ts +
  SessionGenerationPolicy.ts + ProviderObservationGenerator.ts —
  request_id propagated end to end. Generator extracts request_id
  from BullMQ payload and includes it in lock/processing/completion
  logs and audit details.
- src/npx-cli/commands/server-jobs.ts +
  src/npx-cli/commands/server.ts — `claude-mem server jobs
  status|failed|retry|cancel`. status compares Postgres outbox counts
  to BullMQ queue counts and surfaces divergence. failed prints
  attempts + last_error message. --team and --project filters.

Verification: 350 pass / 12 skip / 7 fail (pre-existing baseline,
verified independent via git stash). 18 new tests added (request-id
middleware, server-jobs CLI seams, jobs list/retry/cancel routes
Postgres-gated). Zero new typecheck errors.

Anti-pattern guards verified:
- agent_events.payload only emitted in /v1/jobs response inside the
  admin-gated branch (?include=payload + admin scope) — returns 403
  otherwise
- jobs retry on a queued row is a no-op (no double BullMQ enqueue,
  no double UPDATE)
- Every operator action writes to audit_log with the
  *_by_operator action and request_id correlation in details
- Stalled events audit through generation_job.stalled

Sample correlated trace (one request_id end to end):
  HTTP middleware: req.requestId = 'req-abc'
  audit event.received: details.requestId = 'req-abc'
  BullMQ payload: { request_id: 'req-abc', generation_job_id: 'gj_x' }
  generator lock log: [generation] job locked { jobId, requestId }
  audit generation_job.processing: details.requestId = 'req-abc'
  completion log: [generation] job=evt_... duration=1230ms

Deferred: live /api/health round-trip integration test (needs
Redis); stalled event live integration test (needs Redis); storing
request_id on the observations row itself (spec did not require).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(server-beta): add Phase 13 release readiness report

Captures the final verification gate: tests (1749 pass, 45 fail all
pre-existing baseline, zero regressions), required greps clean,
Docker E2E green end-to-end, all 7 exit criteria met, build clean,
typecheck unchanged from main. Documents deferred items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build(server-beta): rebuild server-beta-service bundle

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server-beta): address Greptile review on PR #2383

- ProviderObservationGenerator.lockOutbox: skip duplicate worker run when
  another lock is active instead of returning the row, which previously let
  two BullMQ workers issue the (paid, rate-limited) external provider call
  before the persistence-layer terminal-status guard collapsed the duplicate.
  Reconciliation still recovers from a stale lock on startup or next retry.
- docker-compose.yml: require POSTGRES_USER/PASSWORD/DB env vars (no
  defaults). Stack refuses to start without explicit secrets. Added a header
  warning that the file must not be deployed unmodified.
- e2e-server-beta-docker.sh: export ephemeral test creds for the new
  required env vars so the Docker E2E driver still runs unattended.
- ServerBetaService api-key list: bound query with LIMIT/OFFSET (default 100,
  max 500) and add optional --team filter to prevent unintentional
  cross-tenant key metadata disclosure on shared admin hosts.
- SessionGenerationPolicy: fix dead `??` fallback for NaN parseInt result;
  use `||` so DEFAULT_DEBOUNCE_MS actually applies.
- ServerV1PostgresRoutes: `?wait=true` now actually waits — polls the outbox
  row until terminal status (timeout 30s, 100ms interval) on both
  /v1/events and /v1/events/batch. Returns `waitTimedOut: true` if the cap
  is hit so callers can re-poll the status endpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server-beta): address CodeRabbit + Greptile second review on PR #2383

P1 fixes
- Operator retry endpoint was re-publishing the Postgres outbox metadata
  column as the BullMQ payload; the worker's
  assertServerGenerationJobPayload always rejected it, leaving the row
  stuck in queued until startup reconciliation. Persist the BullMQ payload
  on the outbox row at create-time inside IngestEventsService and
  EndSessionService, then re-enqueue that canonical payload on retry.

Major fixes
- prompt-builder: escape server_session_id when interpolating into the
  XML prompt; previously a session id containing `<`, `&`, or quotes
  could inject XML into the provider input.
- ServerJobQueue: route both worker.on('stalled') and the QueueEvents
  'stalled' subscriber through a single notifyStalled helper that
  dedupes by jobId for 30s, so counters.stalled increments once per
  stall. QueueEvents 'error' now routes through notifyQueueError so
  it increments counters.errored and runs onError listeners — keeping
  observability symmetric across both sources.
- ServerV1PostgresRoutes: convert PostgresObservationRepository from
  three dynamic imports to a single static import for consistency.
- mcp-server / ServerBetaClient: actually forward the
  observation_record_event tool's `generate` flag through to the
  /v1/events endpoint as `?generate=false` instead of voiding it.
- server-sessions.markGenerationFailed: guard jsonb_set against a null
  error payload so the failure path can't null out metadata before the
  generation_status='failed' write commits.

Minor fixes
- server-sessions.endSession: keep updated_at stable on repeated calls
  so the documented idempotency contract holds.
- SettingsDefaultsManager + ServerBetaService.getServerBetaPort: derive
  the server-beta default port from UID (37877 + uid%100), matching the
  worker port pattern, so two users on the same host don't collide.
  Docker stacks always pass CLAUDE_MEM_SERVER_PORT explicitly so the
  containerized deployment is unaffected.
- server-session-runtime test: close the pg.Pool in afterAll.
- server-beta-release-readiness.md: escape pipes inside table inline
  code, add `text` language tag to the fenced log block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server-beta): address Greptile + CodeRabbit third review on PR #2383

P1 fixes
- SessionsObservationsAdapter.resolveServerSession: catch unique-violation
  (23505) on concurrent compat inserts and re-fetch instead of returning
  500. Two compat callers carrying the same contentSessionId can both
  observe `existing===null` and race on the (project_id,
  external_session_id) unique constraint; the second now resolves to the
  raced row instead of dropping the event.
- /v1/events/batch: pass `sourceAdapter: null` to ingestBatch so each
  event's BullMQ payload (and persisted outbox payload column) reflects
  its own event.sourceAdapter via buildEventBullmqPayload's fallback,
  rather than stamping the whole batch with the first event's adapter.

Minor
- server-session-runtime test afterEach: wrap DROP SCHEMA in try/finally
  so client.release() always runs even if the drop throws.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(test): drop `pool as never` cast — pg.Pool already matches PostgresPool

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server-beta): retry of completed job now 409s instead of duplicating

retryGenerationJob previously fell through to the reset+re-enqueue path
when called on a job in `completed` status. The observations index
dedupes on (generation_job_id, parsed_observation_index, content) but
LLM output is non-deterministic, so a second provider run almost always
produced a different content string and bypassed the index, persisting a
parallel set of observation rows attributed to the same generation job.

Match cancelGenerationJob's 409 guard for completed jobs. failed and
cancelled remain valid retry targets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build(server-beta): rebuild bundles after rebase onto main

Regenerates the three plugin bundles so they reflect the rebased source
state. Mechanical rebuild output only — no source changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server-beta): wrap resolveServerSession in try/catch for structured error response

Greptile P1 on PR #2383: resolveServerSession was called before the try/catch
in both compat adapters, so Postgres errors during session lookup (timeout,
pool exhaustion, etc.) escaped to Express's default error handler and returned
HTML/text 500s. Legacy clients calling response.json() would get a parse
failure instead of the documented { stored: false, reason: 'internal_error' }
(or { status: 'error', reason: 'internal_error' } for the summarize adapter)
shape.

Move the resolveServerSession call inside the existing try block in both
adapters so any failure flows through the structured catch handler.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server-beta): catch 23505 unique violation in POST /v1/sessions/start

Greptile P1 on PR #2383: concurrent requests with the same externalSessionId
can both pass the findByExternalIdForScope check, both call repo.create,
and the loser hits the (project_id, external_session_id) unique constraint.
The handler treated that as an unknown error and returned a 500.

Apply the same pattern resolveServerSession already uses: catch error.code
'23505' when externalSessionId is set, refetch the row inserted by the
winning request, and return 200 with that session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 00:26:11 -07:00
Alex Newman a10d1b342f docs(plans): add architectural plan files for issues #2376-#2381
Six numbered plan documents covering:
- 01 Hook IO Discipline (#2376)
- 02 Spawn-Contract Templating (#2377)
- 03 Worker / Daemon Lifecycle Hardening (#2378)
- 04 Installer Failure Transparency (#2379)
- 05 Observer SDK Tool Enforcement (#2380)
- 06 Worker Env Isolation (#2381)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 00:31:02 -07:00
Alex Newman 386e00ccce docs: update CHANGELOG.md for v13.0.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 00:27:02 -07:00
Alex Newman 9b014b588a chore(release): bump claude-mem to 13.0.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:09:05 -07:00
Alex Newman 5533412984 fix: sequoia-territory bug-fix bundle (chroma, env, build, MCP, worker) (#2394)
* fix(mcp): drop ${_R%/} parameter-expansion trim that trips Claude Code MCP validator

The POSIX substring trim ${_R%/} is misread by Claude Code's MCP-config
validator as a required env var named "_R%/", causing /doctor to flag
mcp-search as invalid on every install. POSIX collapses // in paths, so
the trim was cosmetic — drop it and the validator passes.

Fixes #2350, #2354, #2356.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(env): block ANTHROPIC_BASE_URL leak + three-branch OAuth-skip predicate

Issue #2375: parent-shell ANTHROPIC_BASE_URL leaked through to subprocess
isolatedEnv, while ANTHROPIC_AUTH_TOKEN was blocked. The OAuth-skip
predicate fired on bare BASE_URL, but no auth credential reached the
subprocess -> "Not logged in". Add ANTHROPIC_BASE_URL to BLOCKED_ENV_VARS
so it can only enter isolatedEnv via ~/.claude-mem/.env.

Replace the OAuth-skip predicate with three branches to prevent a
second-order security regression: a user with a tokenless gateway
configured in .env (BASE_URL only, no token) would otherwise have their
Anthropic OAuth token fetched and sent to their gateway. Token leak to
third party. Three-branch predicate:

1. BASE_URL set -> return without OAuth (custom gateway, never leak token)
2. API_KEY or AUTH_TOKEN set -> return without OAuth (explicit credentials)
3. Otherwise -> OAuth lookup for api.anthropic.com

Adds tests/env-isolation.test.ts.

Fixes #2375.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(worker): classify Claude SDK HTTP 400 as unrecoverable

ClaudeProvider previously had no explicit HTTP 400 handling — the
default branch classified all errors as `transient`, so a permanent
400 (e.g., model rejecting an `effort` parameter forwarded from a
leaked CLAUDE_CODE_EFFORT_LEVEL) would be retried indefinitely
(#1874+ retries observed in one session per #2357).

Mirror GeminiProvider/OpenRouterProvider's pattern: classify 400 as
`unrecoverable`, 401/403 as `auth_invalid`, 429 as `rate_limit`,
default to `transient`. When the 400 body matches the
"effort parameter" signature, emit a one-time SDK warn log pointing
at the env-leak fix in ~/.claude-mem/.env.

Adds tests/claude-provider-error-classifier.test.ts.

Fixes #2357.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chroma): pin onnxruntime>=1.20 + protobuf<7 to fix INVALID_PROTOBUF on macOS arm64

The shipped all-MiniLM-L6-v2 model has pytorch-2.0 IR. chroma-mcp 0.2.6
transitively depends on `chromadb>=1.0.16` which only requires
`onnxruntime>=1.14.1` — uv can therefore resolve to an onnxruntime old
enough to fail every embedding add with `[ONNXRuntimeError] : 7 :
INVALID_PROTOBUF` on macOS arm64 / Python 3.13. Semantic search silently
degraded to FTS-only and smart backfill broke (#2371).

Path B (override) was required because chroma-mcp 0.2.6 is the latest
PyPI release — no upstream bump exists.

Inject `--with onnxruntime>=1.20 --with protobuf<7` into the uvx spawn
args (both persistent and remote modes). The protobuf cap is essential:
forcing only `onnxruntime>=1.20` causes uv to re-resolve and land on
protobuf 7.x, which trips opentelemetry's `_pb2` stubs with `TypeError:
Descriptors cannot be created directly` because they were generated
with protoc <3.19. Capping below 7 lands on protobuf 6.x which
opentelemetry tolerates.

Verified end-to-end: ONNX model loads, embeddings produce a 384-dim
vector, PersistentClient init / add / query roundtrip succeeds:

    uvx --python 3.13 --with "onnxruntime>=1.20" --with "protobuf<7" \
        chroma-mcp==0.2.6 --help     # clean
    # programmatic test: onnxruntime 1.26.0, protobuf 6.33.6,
    # embedding ok 384, query ok ids=[['1']]

Fixes #2371.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(chroma): enforce single chroma-mcp subprocess per worker (#2313)

Root cause: every reconnect path in ChromaMcpManager — connectInternal's
re-entry, the connect-timeout catch, callTool's transport-error retry, and
the transport.onclose handler — used to abandon `this.transport`/`this.client`
by calling at most `transport.close()` and nulling the handles. The MCP SDK's
StdioClientTransport.close() only signals the direct child (uvx); on Linux the
grandchildren (uv -> python -> chroma-mcp) re-parent to init and survive
because the SDK does not put the subprocess in its own process group. Each
reconnect therefore leaked a full chroma-mcp tree, accumulating 20+ instances
per session.

Fix: introduce a private disposeCurrentSubprocess() helper that always tree-
kills via the existing killProcessTree primitive before nulling the transport
reference, and route every "abandon current transport" path (reconnect,
connect-timeout, transport error, onclose, stop) through it. The existing
`connecting: Promise<void> | null` lock continues to serialize concurrent
ensureConnected() callers into a single spawn.

Adds tests/services/sync/chroma-mcp-manager-singleton.test.ts covering:
- 5 parallel ensureConnected() calls produce exactly one spawn
- a transport-error reconnect tree-kills the prior subprocess pid before
  spawning a replacement
- stop() disposes state including any pending connecting promise

Manual verification needed on Linux: after a long session with multiple
tool uses, `ps aux | grep chroma-mcp | wc -l` should return 1, not 20+.

Fixes #2313.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(build): polyfill import.meta.url to __filename in CJS worker bundle

The worker bundles ESM dependencies (notably @anthropic-ai/claude-agent-sdk's
*.mjs files) into CJS output. Those modules call createRequire(import.meta.url)
at module-load time. esbuild's CJS output left this as createRequire(ute.url)
— where `ute` is its `import.meta` polyfill `{}` — so `ute.url` was undefined
and module-load crashed with:

  TypeError: The argument 'filename' must be a file URL object, file URL
  string, or absolute path string. Received undefined
  code: ERR_INVALID_ARG_VALUE

Every Stop hook and every worker subprocess invocation hit this. Fix is the
esbuild `define` option mapping `import.meta.url` to `__filename` (provided as
a real absolute path by the existing CJS prelude in the banner).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: daily dep bump per CLAUDE.md maintenance policy

Root: @anthropic-ai/claude-agent-sdk, @clack/prompts, @types/node,
dompurify, postcss, react, react-dom, yaml, zod.
plugin/: tree-sitter-cli, zod.
openclaw/: @types/node.

All patch/minor bumps; no major version changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build: regenerate plugin artifacts after env/chroma/mcp fixes

Built artifacts are committed so the marketplace-installable plugin
ships with the runtime bundles. Picks up:
- d7b145e9 .mcp.json shell-prelude trim drop
- a8cbd651 EnvManager BASE_URL block + 3-branch predicate
- 8cb73b8c ClaudeProvider HTTP 400 unrecoverable classifier
- ecd5b802 ChromaMcpManager onnxruntime/protobuf overrides
- c79324ea ChromaMcpManager singleton enforcement
- e8376f46 esbuild import.meta.url -> __filename polyfill
- a7541d71 daily dep bump

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build: regenerate plugin artifacts after main merge

Bundles now include both v13.0.0 server-beta runtime (server-beta-service.cjs
+ updated mcp-server.cjs / worker-service.cjs) and this branch's chroma /
env / build / Claude SDK fixes.

Verified: bun test tests/env-isolation.test.ts \\
  tests/claude-provider-error-classifier.test.ts \\
  tests/services/sync/chroma-mcp-manager-singleton.test.ts
→ 13/13 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(review): address CodeRabbit findings on PR #2394

1. scripts/build-hooks.js — `import.meta.url` now maps to a file:// URL
   (via pathToFileURL(__filename).href in the CJS banner) instead of the
   raw __filename path. Preserves URL semantics for any bundled ESM dep
   that does `new URL(rel, import.meta.url)`. createRequire still works.

2. src/shared/EnvManager.ts — added envFilePath() that resolves
   CLAUDE_MEM_ENV_FILE lazily (falling back to paths.envFile()), and
   switched internal load/save call sites to use it. ENV_FILE_PATH is
   kept as a deprecated snapshot for back-compat. Lets tests target a
   temp file without depending on module-load order.

3. tests/env-isolation.test.ts — redirects to a temp dir via
   CLAUDE_MEM_ENV_FILE in beforeAll, removes all mutation of the real
   ~/.claude-mem/.env, and wraps the OAuth-spy assertion in try/finally
   so the spy is always restored even if the test fails.

Verified:
  bun test tests/env-isolation.test.ts \
    tests/claude-provider-error-classifier.test.ts \
    tests/services/sync/chroma-mcp-manager-singleton.test.ts
  → 13/13 pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 18:05:48 -07:00
Alex Newman 13d5fa71c2 fix(openclaw): correct homepage to claude-mem.ai
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:23:57 -07:00
Alex Newman d17b372b60 chore(release): bump claude-mem to 13.0.0
Major version bump following PR #2351 merge — server-beta runtime,
Postgres observation storage, BullMQ queue engine, and Apache 2.0
relicense are now on main.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:21:19 -07:00
Alex Newman 36b0929fae Server-beta: Postgres storage + independent runtime + BullMQ queue (Phases 1–3) (#2351)
* Add server beta runtime foundation

* Address server beta review findings

* Resolve server beta review comments

* Tighten server beta review follow-ups

* Harden server beta auth and search

* Avoid unnecessary FTS rebuilds

* Block scoped keys from creating projects

* Release BullMQ claims best effort on close

* Address server beta review blockers

* Reset BullMQ claims best effort

* Add Postgres observation storage foundation

* feat(server-beta): add independent runtime service

Introduce src/server/runtime/ as a self-contained server-beta runtime
that owns its lifecycle, Postgres bootstrap, and HTTP boundary without
depending on WorkerService.

ServerBetaService wraps the existing Server class, exposes
/healthz and /v1/info with runtime="server-beta", and persists state
to dedicated paths (.server-beta.pid|.port|.runtime.json). The four
boundary managers (queue, generation worker, provider registry, event
broadcaster) are intentionally disabled in this phase and report their
status through /v1/info; later phases activate them.

Adds plans/2026-05-07-finish-bullmq-branch-ship-plan.md to track the
remaining work for this branch.

Phase 2 of plans/2026-05-07-server-beta-independent-bullmq-observation-runtime.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): route CLI lifecycle and bundle separate runtime

scripts/build-hooks.js now produces plugin/scripts/server-beta-service.cjs
as a separate Node CJS bundle, alongside the existing worker-service
bundle. The server-beta runtime is now installable independently.

src/npx-cli/commands/server.ts routes start|stop|restart|status to the
server-beta lifecycle instead of the legacy worker. The worker keeps its
own start|stop|restart|status under the worker namespace; the two
runtimes can be operated independently.

src/services/worker-service.ts adds a server-* command parser branch
that delegates to the sibling server-beta-service.cjs bundle so
direct worker-service invocations still route to the right runtime.

tests/npx-cli-server-namespace.test.ts updated to expect server-beta
lifecycle routing.

Includes rebuilt plugin/scripts/*.cjs bundles produced by
build-and-sync.

Phase 2 of plans/2026-05-07-server-beta-independent-bullmq-observation-runtime.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): add BullMQ job queue primitives

Introduce src/server/jobs/ as the queue-side primitives that Phase 3 of
the server-beta runtime needs to operate.

types.ts defines a discriminated union over the four job kinds (event,
event-batch, summary, reindex) and maps each to a per-kind BullMQ queue
name and deterministic-ID prefix.

job-id.ts builds deterministic, colon-free BullMQ jobIds from
(kind, team, project, source). The colon ban exists because BullMQ uses
':' as a Redis key separator internally; embedding ':' in jobIds
breaks scan and state lookups.

ServerJobQueue.ts is a thin wrapper over BullMQ Queue + Worker that
enforces autorun:false, default concurrency 1, and an attached error
listener — all per BullMQ docs requirements. Test seams accept queue
and worker factories so unit tests do not need Redis.

outbox.ts publishes through the Postgres ObservationGenerationJob
repository as canonical history. enqueueOutbox writes the row first,
then publishes to BullMQ; if BullMQ throws, the row is transitioned to
failed and a failed event is appended. reconcileOnStartup re-enqueues
queued + processing rows after a restart, replacing terminal BullMQ
jobs that may still be holding the deterministic ID slot. markCompleted
and markFailed wrap transitionStatus and append the matching event row.

Includes 20 unit tests covering deterministic ID stability, colon-free
output, queue lifecycle, error-listener attachment, double-start
refusal, idempotent enqueue, BullMQ failure rollback, startup
reconciliation, max-attempts skipping, and completion / failure /
retry transitions.

Phase 3 commit 1 of plans/2026-05-07-server-beta-independent-bullmq-observation-runtime.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(server-beta): activate queue boundary in runtime service

Wire ActiveServerBetaQueueManager into the server-beta runtime graph.
The active manager owns one ServerJobQueue per generation kind (event,
event-batch, summary, reindex) and surfaces lane metadata through
boundary health.

Selection is opt-in and fail-fast: if CLAUDE_MEM_QUEUE_ENGINE is set to
bullmq the active manager is constructed (and any Redis/config error
throws — no silent fallback to SQLite, per Phase 3 anti-pattern guard).
For any other engine the disabled boundary remains so worker-era and
test setups stay compatible.

Widens ServerBetaBoundaryHealth.status to a discriminated union
('disabled' | 'active' | 'errored') with optional details. The disabled
adapter still emits status='disabled', which keeps the existing
server-beta-service test green.

ServerBetaService receives the manager through a new optional
queueManager field on CreateServerBetaServiceOptions so test graphs
and Phase 4 wiring can inject custom managers.

Adds tests/server/runtime/active-queue-manager.test.ts covering bullmq
guard, active health shape, per-kind queue access, close behavior, and
post-close errored health.

Phase 3 commit 2 of plans/2026-05-07-server-beta-independent-bullmq-observation-runtime.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(server-beta): cap /v1/events/batch at 500 events

Prevents unbounded array DoS surface flagged in PR review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 01:20:07 -07:00
Alex Newman 0a43ab7632 docs: update changelog for v12.7.5 2026-05-06 21:31:29 -07:00
Alex Newman e3cc382fa2 fix: reconcile Codex marketplace source conflicts 2026-05-06 21:29:40 -07:00
Alex Newman 97f1c52de0 docs: update changelog for v12.7.4 2026-05-06 19:40:59 -07:00
Alex Newman 7a9573759e chore: bump version to 12.7.4 2026-05-06 19:39:51 -07:00
Alex Newman b89b140aa1 Merge pull request #2342 from thedotmack/fix-and-ship-codex-mem-search-access
Fix Codex mem-search MCP startup
2026-05-06 19:12:40 -07:00
Alex Newman b6115db720 Merge remote-tracking branch 'origin/codex-mode-session-start-hook-migration' into fix-and-ship-codex-mem-search-access
# Conflicts:
#	plugin/scripts/worker-service.cjs
2026-05-06 19:11:41 -07:00
Alex Newman 2e3532127a Merge remote-tracking branch 'origin/codex-version-mismatch-investigation-plan' into fix-and-ship-codex-mem-search-access 2026-05-06 19:11:10 -07:00
Alex Newman 3a000b6bc6 Merge remote-tracking branch 'origin/main' into fix-and-ship-codex-mem-search-access
# Conflicts:
#	.mcp.json
#	plugin/.mcp.json
#	plugin/scripts/mcp-server.cjs
#	plugin/scripts/worker-service.cjs
#	tests/infrastructure/plugin-distribution.test.ts
2026-05-06 19:10:49 -07:00
Alex Newman a2872dabfa docs: plan Codex plugin version mismatch fix 2026-05-06 19:08:12 -07:00
Alex Newman 7af43146e4 docs: update changelog for v12.7.3 2026-05-06 18:32:44 -07:00
Alex Newman 02c7a3dfa9 chore: bump version to 12.7.3 2026-05-06 18:30:53 -07:00
Alex Newman 65f2fd8cdd fix: harden startup and schema repair contracts
Reliability patch covering startup path resolution, install marker compatibility, export CLI request contracts, schema repair safety, hard-stop retry-loop handling, and the PR babysit status helper.
2026-05-06 18:29:26 -07:00
airkjw deb0d25a26 build: rebuild plugin artifacts for v12.7.2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 08:59:59 +09:00
airkjw 71c1e618be chore: merge upstream v12.7.2 + keep local fixes
# Conflicts:
#	plugin/scripts/context-generator.cjs
#	plugin/scripts/mcp-server.cjs
#	plugin/scripts/worker-service.cjs
2026-05-07 08:55:54 +09:00
Alex Newman 627c484791 chore: bump version to 12.7.3 2026-05-06 14:41:15 -07:00
Alex Newman 8d65b95a59 fix: narrow legacy codex context cleanup 2026-05-06 14:39:42 -07:00
Alex Newman a64d60b9cb fix: address codex migration review feedback 2026-05-06 14:32:03 -07:00
Alex Newman 37d186e767 test: guard MCP launcher fallback distribution 2026-05-06 14:29:49 -07:00
Alex Newman 156062170d fix: migrate codex context off agents injection 2026-05-06 14:24:40 -07:00
Alex Newman c80225147f chore: bump version to 12.7.3 2026-05-06 14:23:37 -07:00
Alex Newman 938c608507 fix(codex): make mem-search MCP startup self-locating 2026-05-06 14:20:55 -07:00
Alex Newman bb3dbfdb5a docs(skill): document discord notify working directory 2026-05-06 12:11:27 -07:00
Alex Newman 4db99da432 docs: update changelog for 12.7.2 2026-05-06 03:35:09 -07:00
Alex Newman 20e98d8361 chore: bump version to 12.7.2 2026-05-06 03:34:22 -07:00
Alex Newman 65607897a8 fix(install): disable Claude Code auto-memory on every claude-code install
Disable Claude Code auto-memory during claude-code installs and harden atomic settings writes, including symlink and dangling-symlink destinations.
2026-05-06 03:32:40 -07:00
Alex Newman d31c4d2a57 docs: fix outdated references in CLAUDE.md (#2304)
- Correct hook lifecycle list: 6 hooks (Setup, SessionStart,
  UserPromptSubmit, PreToolUse, PostToolUse, Stop), not the
  fictional 'Summary' / 'SessionEnd' pair.
- Replace misleading 'src/hooks/*.ts' description with the actual
  build path from src/services/worker-service.ts via
  scripts/build-hooks.js, and list the real subcommands.
- Drop the broken link to private/context/claude-code/exit-codes.md
  (path no longer exists in the repo).
2026-05-06 03:20:15 -07:00
Alex Newman 1eaed3141f docs: update changelog for 12.7.1 2026-05-06 03:07:39 -07:00
Alex Newman 6198762f1e chore: bump version to 12.7.1 2026-05-06 03:06:20 -07:00
Alex Newman 9f2ce1754c Add babysit PR monitoring skill
Add a babysit skill for monitoring PR checks, review comments, and review threads until a PR is merge-ready.
2026-05-06 03:04:40 -07:00
Alex Newman 9e4e30a01d docs: update changelog for 12.7.0 2026-05-06 01:59:32 -07:00
Alex Newman 1667eac0be chore: bump version to 12.7.0 2026-05-06 01:57:45 -07:00
Alex Newman 56db06811e Add native Codex hooks integration (#2319)
* Add native Codex hooks integration

* Address Codex review feedback

* Use durable Codex marketplace root

* Address Codex file context review feedback

* Harden Codex installer review paths

* Report Codex legacy cleanup failures

* fix: keep MCP manifests in marketplace sync

* fix: bundle zod in MCP server

* fix: warn on Codex legacy cleanup failure

* Fix hook observation readiness timeouts

* Address Codex hook review notes

* Tighten Codex MCP file context matching

* Resolve final Codex review nits

* Add Codex marketplace version guidance

* Reset worker failure counter on API fallback

* Fix Codex cat flag file extraction
2026-05-06 01:55:27 -07:00
Alex Newman a5bb6b346a docs: document LiteLLM gateway routing 2026-05-05 15:08:09 -07:00
Alex Newman 09dcecafd0 chore: bump version to 12.6.5 2026-05-05 14:48:24 -07:00
Alex Newman b414f57edb Merge pull request #2302 from thedotmack/codex/remove-agent-pool-timeout
[codex] Remove agent pool timeout data loss
2026-05-05 14:46:28 -07:00
Alex Newman 46b59573b5 Merge remote-tracking branch 'origin/main' into codex/remove-agent-pool-timeout
# Conflicts:
#	plugin/scripts/mcp-server.cjs
#	plugin/scripts/worker-service.cjs
2026-05-05 14:45:57 -07:00
Alex Newman 89718f79b0 chore: bump version to 12.6.4 2026-05-05 14:34:55 -07:00
Alex Newman ce35bb520d chore: sync plugin artifacts for 12.6.3 2026-05-05 13:07:36 -07:00
Alex Newman 519fbe5daa 12.6.3 2026-05-05 13:05:38 -07:00
Alex Newman 92f800d49c fix: drain invalid observer responses 2026-05-05 13:00:42 -07:00
Alex Newman 9a2818fc2e docs: regenerate CHANGELOG for v12.6.2 2026-05-04 22:36:10 -07:00
Alex Newman ec97813582 chore: bump version to 12.6.2 2026-05-04 21:48:43 -07:00
Alex Newman 1981f9b2fe fix(install): revert tree-sitter grammars to devDependencies (#2300 regression) (#2305)
PR #2300 moved 21 tree-sitter grammar packages from devDependencies into
root dependencies, claiming "their .wasm files are loaded at runtime by
parser.ts." That justification is wrong for the root claude-mem npm
package: parser.ts compiles into plugin/scripts/worker-service.cjs, which
runs from the marketplace folder where plugin/package.json already lists
every grammar as a runtime dep. Nothing in dist/npx-cli/ ever loads a
grammar, and resolveGrammarPath() handles missing packages gracefully.

The regression: `npx claude-mem@12.6.1 install` now fetches all 21
grammars at npx time. tree-sitter-swift's postinstall pulls a nested
tree-sitter-cli that downloads a Rust binary from GitHub and hangs the
install. npm ignores the trustedDependencies bun-allowlist, so there's
no way to skip the postinstall scripts on a bare `npx` fetch.

Fix: move grammar packages back to root devDependencies. The marketplace
plugin install (installPluginDependencies → bun install in plugin/) still
works because plugin/package.json keeps them as deps and Bun honors
trustedDependencies: ["tree-sitter-cli"] to skip the harmful postinstalls
on every other grammar.

Keep PR #2300's --legacy-peer-deps + --omit=dev install.ts changes —
those address a separate, valid marketplace ERESOLVE.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:47:27 -07:00
Alex Newman a122d34ebf fix: address Greptile P1 + CodeRabbit follow-ups (cycle 9)
- waitForSlot now accepts an optional AbortSignal. When the signal
  fires (e.g. session.abortController.abort() during shutdown or
  cancel), the queued waiter is removed from slotWaiters and the
  promise rejects immediately, instead of hanging until a slot
  naturally opens. Restores the cancellation guarantee that the
  removed 60s timeout used to provide. ClaudeProvider.startSession
  now passes session.abortController.signal at the call site.
- EnvManager: a bare ANTHROPIC_BASE_URL now also short-circuits the
  OAuth lookup. Tokenless gateways (allowed by the new install flow)
  were otherwise being authenticated against api.anthropic.com via the
  injected OS-keychain OAuth token.
- install.ts: resolveClaudeAuthMethod now reads the raw stored
  CLAUDE_MEM_CLAUDE_AUTH_METHOD value via a direct settings.json read
  (readRawStoredAuthMethod), bypassing SettingsDefaultsManager's
  default backfill. Without this, getSetting() always returned
  'subscription' for unmigrated installs and the env-based fallback
  never ran — so the previous fix only addressed the optics, not
  the actual misclassification.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 21:07:42 -07:00
Alex Newman 5edf1557c4 fix: address Greptile P1 security + CodeRabbit follow-ups on PR #2302
- EnvManager: add ANTHROPIC_AUTH_TOKEN to BLOCKED_ENV_VARS so a token
  inherited from the parent shell can no longer short-circuit the OAuth
  lookup at SDK spawn time. Mirrors the ANTHROPIC_API_KEY treatment
  added in issue #733. Explicit gateway tokens in
  ~/.claude-mem/.env are still re-injected by buildIsolatedEnv().
- install.ts: extract resolveClaudeAuthMethod() that returns a stored
  CLAUDE_MEM_CLAUDE_AUTH_METHOD when present and otherwise infers
  the mode from ~/.claude-mem/.env (ANTHROPIC_BASE_URL → gateway,
  ANTHROPIC_API_KEY → api-key, else subscription). persistClaudeProvider,
  the interactive Claude auth flow, and promptClaudeModel now use it,
  so older installs that pre-date the setting are no longer
  misclassified as 'subscription' (which would clear working
  credentials and disable custom gateway models).
- configureDirectApiKey: when an Anthropic API key already exists,
  prompt to keep or rotate it instead of silently re-saving — restores
  the ability to update a revoked or rotated key from the installer
  without losing the cancel-safe behaviour added in 7f3686fd.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:59:11 -07:00
Alex Newman 7f3686fd2c fix: address CodeRabbit Major findings on install.ts
- Cancel of API-key / Gateway-URL prompts no longer wipes existing
  credentials by switching to subscription auth and emptying
  ANTHROPIC_API_KEY / ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN. Cancel
  now leaves the prior config untouched.
- Empty gateway-token input preserves the existing token instead of
  clearing it. The new prompt copy explains that blank keeps the
  current token.
- Interactive install no longer hard-locks to Claude when
  --provider is unset. Prompt now asks for provider
  (claude/gemini/openrouter) up front, then runs the Claude auth flow
  only when the user picks Claude.
- Claude auth-mode prompt now seeds initialValue from the stored
  CLAUDE_MEM_CLAUDE_AUTH_METHOD setting, so reruns honor existing
  configuration instead of always defaulting to subscription.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:51:00 -07:00
Alex Newman 0cc45c6e7f fix: drop duplicate notifySlotAvailable() in SDK child exit handler
CodeRabbit flagged a duplicate slot wakeup: spawnSdkProcess's child
'exit' handler called registry.unregister(recordId) and then
notifySlotAvailable() unconditionally. Registry.unregister() already
fires notifySlotAvailable() internally when removing an SDK entry, so
the trailing call woke a second waiter for the same freed slot — both
could see count < maxConcurrent in the same synchronous tick before
either replacement registered, transiently exceeding maxConcurrent.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:47:08 -07:00
Alex Newman 4e49dcf445 fix: address Greptile P1 findings on PR #2302
- process-registry.ts: skip the trailing notifySlotAvailable() when
  pruneDeadEntries() removed entries — prune already wakes one waiter
  per removed SDK process, so the unconditional call double-woke and
  could let two waiters spawn in the same synchronous tick, briefly
  exceeding maxConcurrent. Only fire the safety-net notify when nothing
  was pruned.
- install.ts: persistClaudeProvider() no longer silently rewrites
  CLAUDE_MEM_CLAUDE_AUTH_METHOD to 'subscription'. When called without
  an explicit auth method, preserve the existing setting; only fall
  back to 'subscription' when none is configured. Prevents re-running
  'claude-mem install --provider claude' from wiping a user's
  configured 'api-key' or 'gateway' auth.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:43:00 -07:00
Alex Newman 53cdccdf7a docs: regenerate CHANGELOG for v12.6.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:32:20 -07:00
Alex Newman 1a8432fe97 chore: bump version to 12.6.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:29:31 -07:00
Alex Newman 9902a15b21 Remove stale internal docs and reports (#2290) 2026-05-04 20:22:29 -07:00
Alex Newman c4097b4ebb add Claude SDK gateway installer setup 2026-05-04 20:21:36 -07:00
Alex Newman 3ff6fcbe1e fix: install no longer fails on tree-sitter peer-dep ERESOLVE (#2300)
* fix: install no longer fails on tree-sitter peer-dep ERESOLVE

The marketplace npm install was failing on a peer-dep conflict between
@derekstride/tree-sitter-sql (peers tree-sitter@^0.21) and
@tree-sitter-grammars/tree-sitter-lua (peers tree-sitter@^0.22.4),
breaking install across all 12 supported IDEs (#2261-#2272).

The conflict is irrelevant: smart_outline/smart_search/smart_unfold use
the tree-sitter CLI + .wasm files shipped inside each grammar package,
never the JS native bindings, so the peer warning is harmless.

- package.json: move grammar packages to dependencies (their .wasm files
  are loaded at runtime by parser.ts, so they were never devDeps).
- src/npx-cli/commands/install.ts: pass --legacy-peer-deps to silence
  the resolver and replace deprecated --production with --omit=dev.

Verified across all 12 IDEs in the install harness: zero npm errors,
21 grammar packages installed, smart_outline parses TypeScript and
smart_search matches across TypeScript+Python.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: clarify --legacy-peer-deps rationale in marketplace install

Addresses Greptile review comment on PR #2300.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:21:23 -07:00
Alex Newman 8bef7c6a34 fix: remove agent pool timeout data loss 2026-05-04 19:43:45 -07:00
Alex Newman 39f1102600 fix: remove ONNX/OpenBLAS thread cap from chroma-mcp spawn env
The 2-thread cap was a bandaid for #2220 (Windows) and #2253 (macOS Intel)
CPU runaway reports on v12.4.9. The actual root causes (watermark stuck
at 0 → continuous re-embed, orphan process trees, fire-and-forget backfill
across 80+ projects) were fixed structurally in #2282: per-batch watermark
persistence, killProcessTree() + pgid registration, max-3 concurrent
backfills with re-entrancy guard, kernel-enforced child cleanup (#2216).

With the structural fixes in place, capping ONNX/OpenBLAS/MKL at 2 threads
slows initial backfill 3–6× on multi-core machines and provides no
steady-state benefit. Defer to the OS scheduler and the user's environment.

ANONYMIZED_TELEMETRY=false stays — unrelated to the storm, blocks
background HTTP from the embedding subprocess.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 13:08:53 -07:00
Alex Newman 43037782a8 docs: regenerate CHANGELOG for v12.6.0
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:53:10 -07:00
Alex Newman a3b161f8c8 chore: bump version to 12.6.0
Minor release: 17 issues fixed and 4 foundations introduced via PR #2282.

Foundations (new public modules):
- F1 spawnHidden wrapper (src/shared/spawn.ts) — windowsHide default
- F2 paths namespace (src/shared/paths.ts) — 24 hardcoded sites collapsed,
  CLAUDE_MEM_DATA_DIR flows 100% of runtime
- F3 getUptimeSeconds (src/shared/uptime.ts) — fixes ms-bug at Server.ts:165
- F4 ClassifiedProviderError (src/services/worker/provider-errors.ts) — kind
  union (transient/unrecoverable/rate_limit/quota_exhausted/auth_invalid),
  per-provider classifiers, unrecoverablePatterns allowlist deleted

User-facing capabilities:
- OAuth keychain reader (#2215): readClaudeOAuthToken() reads from
  macOS keychain, Windows DPAPI, Linux libsecret at worker spawn-time
- Quota-aware wall-clock guard (#2234): RateLimitStore with auth-type gate
  (api_key never aborts; cli/oauth aborts at per-window thresholds);
  rateLimits surfaced on /api/health
- withRetry helper (#2254): honors ClassifiedProviderError.kind, exponential
  backoff with jitter, request-id capture for dedup logging

Bug fixes also landing in 12.6.0:
#2188 stdin fallback, #2196 ANTHROPIC_BASE_URL docs, #2220 chroma CPU storm,
#2225 opencode _zod.def crash, #2231 SECURITY.md, #2233 parser fence (Part A),
#2236 observer visible windows, #2237/#2238 hardcoded paths, #2240 Chroma
dedupe, #2242 check-pending-queue endpoints, #2243 scripts/package.json,
#2244 summaryStored=null, #2247 Codex task_complete, #2248 Cursor sessions,
#2250 health uptime ms, #2253 chroma macOS CPU.

Tests: 1454 pass / 77 fail (matches main baseline, zero net regressions).
All CI green: build, CodeRabbit (17 rounds resolved), Greptile (clean).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:48:11 -07:00
Alex Newman d384d3c595 fix: bug-batch — 17 issues + 4 foundations (chroma, opencode, parser, OAuth, paths, uptime, classification) (#2282)
* feat: foundations F1-F4 + simple bug fixes

Foundations (no consumer adoption yet):
- F1 spawnHidden wrapper at src/shared/spawn.ts
- F2 paths namespace with 18 accessors + invariant test (tests/shared/paths.test.ts)
- F3 getUptimeSeconds at src/shared/uptime.ts
- F4 ClassifiedProviderError at src/services/worker/provider-errors.ts + 6 tests

Issue fixes (file-isolated, parallel-safe):
- #2231: SECURITY.md at repo root for GitHub Security tab
- #2240: dedupe observationIds before Chroma sync (ResponseProcessor.ts)
- #2247: add task_complete to Codex session-end events
- #2243: rsync excludes scripts/package.json + scripts/node_modules

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: validate Claude executable with --version and detect desktop app

Extract findClaudeExecutable() into shared utility used by both
SDKAgent and KnowledgeAgent (deduplication). Every candidate is now
validated with --version (3s timeout). Desktop app executables in
AppData/Program Files get an actionable error message directing
users to install the CLI via npm.

Closes #2222

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use Zod schemas in OpenCode plugin to fix _zod.def crash

OpenCode 1.14.x walks arg._zod.def at plugin registration, which
crashes on plain JSON Schema objects like {type: "string"}. Replace
with z.string().describe() so the Zod internals are present.

Closes #2226, #2225, #2154

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: neutralize chroma-mcp CPU storm at the root

Two surgical fixes to the chroma backfill path that together cause the
sustained 60–80% CPU + orphan accumulation pattern reported across

1. ChromaMcpManager.getSpawnEnv: cap embedding-thread fanout
   ONNX Runtime / OpenBLAS / MKL all default to cpu_count(), so a 12-core
   machine spins 12 threads burning embeddings concurrently. The user's
   getSpawnEnv only handled SSL certs — no thread limits at all. Inject
   OMP_NUM_THREADS / ONNX_NUM_THREADS / OPENBLAS_NUM_THREADS / MKL_NUM_THREADS
   defaults of 2 (only if user hasn't pinned them), and
   ANONYMIZED_TELEMETRY=false to stop background HTTP from the embedding
   subprocess. Closes the storm at the source.

2. ChromaSync.backfill{Observations,Summaries,Prompts}: per-batch watermark
   The bump was in a trailing finally block. SIGKILL / OOM / power loss
   mid-flight skips finally entirely, so the watermark stayed at 0 and the
   next worker boot re-embedded the entire history (16K obs in #2220's
   case), which then pegged CPU forever in combination with (1). Move the
   bump inside the loop so progress is durable per batch.
   Closes #2214.

Verification:
- 26/26 chroma tests pass (tests/services/sync, tests/integration/chroma-vector-sync)
- Bundle confirms thread caps and per-batch bumps are present
- Full suite: 1429 pass / 20 fail — pre-existing failures only, no
  regression vs v12.4.9 baseline (1429 pass / 27 fail)

Closes #2214.
Substantially de-amplifies #2220 (the structural Job-Object cleanup is
still tracked separately at #2216).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: kill chroma-mcp process tree and limit backfill concurrency

Three fixes for orphan chroma-mcp processes and resource exhaustion:

1. killProcessTree() in ChromaMcpManager.stop() tears down the full
   uvx->uv->python->chroma-mcp spawn chain (pkill -P on POSIX,
   taskkill /T on Windows) before MCP client.close().

2. Register chroma process with pgid for supervisor shutdown cascade.

3. backfillAllProjects() now processes max 3 projects concurrently
   with a re-entrancy guard to prevent overlapping fire-and-forget runs.

Closes #2216, advances #2220, #2213

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* build: regenerate plugin artifacts after cherry-picks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: foundation consumers + Cursor/stdin/queue/docs fixes

F1 spawnHidden adoption (#2236):
- 8 spawn → spawnHidden conversions across worker-utils, ProcessManager,
  npx-cli (install/runtime), supervisor/process-registry

F3 getUptimeSeconds adoption (#2250):
- Server.ts:165 (THE BUG: returned ms)
- Server.ts:270, SessionRoutes.ts:326 (4th ms-bug consumer found),
  DataRoutes.ts:225 (refactor for consistency)

#2188 stdin '{}' fallback removal:
- Diagnostic logging to <DATA_DIR>/logs/runner-errors.log + CAPTURE_BROKEN
  marker; exit 0 to preserve Windows Terminal exit-code strategy

#2196 ANTHROPIC_BASE_URL docs:
- New docs/public/configuration/custom-anthropic-backends.mdx
- Note: issue may need separate auto-detect feature; docs document
  existing plumbing only

#2242 check-pending-queue endpoints:
- Point at /api/processing-status + /api/processing per DataRoutes.ts;
  honor CLAUDE_MEM_WORKER_PORT env

#2248 Cursor sessions never summarized:
- Pulled reporter wbingli's tested fix (commit 46eaba44)
- Bug A: cursor adapter now derives transcriptPath from cwd+sessionId
- Bug B: parser accepts both line.type and line.role
- Bug C: walk backward, prefer non-empty text, fallback to empty
- Tests: 10-case regression suite + tests/fixtures/cursor-session.jsonl

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: F2 paths namespace adoption (#2237 + #2238)

Replaced 24 hardcoded homedir() + '.claude-mem' sites across 18 source
files with paths.<accessor>() calls from src/shared/paths.ts.

Accessors used: dataDir, workerPid, settings, database, chroma,
combinedCerts, transcriptsConfig, transcriptsState, corpora,
supervisorRegistry, envFile, logsDir.

Sites converted (file:area):
- src/cli/claude-md-commands.ts (database)
- src/services/context/ContextConfigLoader.ts (settings)
- src/services/infrastructure/ProcessManager.ts (workerPid)
- src/services/infrastructure/WorktreeAdoption.ts (settings)
- src/services/integrations/CodexCliInstaller.ts (settings)
- src/services/sync/ChromaMcpManager.ts (chroma + combinedCerts)
- src/services/transcripts/config.ts (transcriptsConfig + transcriptsState)
- src/services/worker/ClaudeProvider.ts (envFile)
- src/services/worker/GeminiProvider.ts (envFile + 2 more)
- src/services/worker/http/routes/DataRoutes.ts (dataDir)
- src/services/worker/http/routes/SettingsRoutes.ts (settings + envFile)
- src/services/worker/knowledge/CorpusStore.ts (corpora)
- src/shared/EnvManager.ts (envFile)
- src/supervisor/index.ts (supervisorRegistry)
- src/supervisor/process-registry.ts (supervisorRegistry)
- src/supervisor/shutdown.ts (supervisorRegistry)
- src/utils/claude-md-utils.ts (database)
- src/utils/logger.ts (logsDir + settings, lazy to avoid cycle)

CLAUDE_MEM_DATA_DIR override now flows through 100% of the worker
runtime; no per-file env reads needed.

Verification:
- Grep guard: zero homedir+'.claude-mem' sites remain in src/
  (excluding paths.ts itself and SettingsDefaultsManager.ts)
- F2 invariant test: 3/3 pass (60 expects)
- Foundation tests: 19/19 pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: F4 provider classification + parser fence + OAuth keychain

F4 adoption (#2244 + #2254):
- Per-provider classifiers: classifyClaudeError, classifyGeminiError,
  classifyOpenRouterError. Each lives in the provider file.
- New retry helper at src/services/worker/retry.ts: withRetry() honors
  ClassifiedProviderError.kind; retriable=transient/rate_limit (with
  retryAfterMs); not retriable=unrecoverable/auth_invalid/quota_exhausted.
  maxRetries=2, perAttemptTimeout=30s, exponential backoff with jitter.
- GeminiProvider + OpenRouterProvider fetch calls wrapped with retry.
  Best-effort request-id capture (x-goog-request-id, x-request-id,
  x-openrouter-request-id) for dedup logging.
- Deleted unrecoverablePatterns allowlist at worker-service.ts:540 area;
  worker dispatches on err.kind instead.
- 28 new classifier tests at tests/worker/provider-classifiers.test.ts:
  429-no-Retry-After, 500-with-quota-exceeded, OverloadedError,
  per-provider auth_invalid signals.

#2233 Part A — parser fence handling:
- src/sdk/prompts.ts: removed 4 fence markers from XML example blocks.
  Model now sees plain XML, eliminating the failure-mode that drained
  quota via repeated retries.
- src/sdk/parser.ts: stripCodeFences() at top, called before
  parseAgentXml. Fence-tolerant regardless of model behavior.
- TODO comment references #2233 Part B (tool-use migration as separate
  scope).
- 4 fence-tolerance tests added to tests/sdk/parser.test.ts.

#2215 OAuth token keychain:
- New src/shared/oauth-token.ts (~360 LOC): readClaudeOAuthToken()
  reads from platform-native credential stores at worker spawn-time.
  - macOS: security find-generic-password -s "Claude Code-credentials"
  - Windows: PowerShell wrapper around CredRead (Win32 Advapi32.dll)
  - Linux: secret-tool lookup
  - Fallback: env CLAUDE_CODE_OAUTH_TOKEN with JWT exp claim or sidecar
    expiresAt validation; refuses stale-token injection.
- EnvManager.buildIsolatedEnvWithFreshOAuth() (async) replaces silent
  process.env copy. Empty injection on absent; marker write on expired.
- <DATA_DIR>/oauth-stale.marker surfaces "re-login via Claude Desktop"
  via existing SessionStart additionalContext mechanism (context.ts).
- ClaudeProvider.startSession + KnowledgeAgent.prime/executeQuery now
  await the async env builder.
- 17 oauth-token tests covering decodeJwtExpMs, marker round-trip,
  env-fallback expiry detection.

Verification:
- npx tsc --noEmit: only pre-existing bun-types error
- bun test (foundations + new): 70 pass, 0 new fails (8 fails are
  pre-existing parser.test.ts cases unrelated to fence work)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: #2234 quota-aware wall-clock guard

New src/services/worker/RateLimitStore.ts (207 LOC) — vendor pattern from
meridian/rateLimitStore.ts (MIT, copied not depended).

API:
- class RateLimitStore: set/get/getAll/getMostRecentByWindow/size/clear,
  in-memory last-write-wins keyed by rateLimitType.
- globalRateLimitStore singleton.
- shouldAbortForQuota(authMethod, store, now?) → {abort, reason?, window?}
- isApiKeyAuth(authMethod): matches both verbose getAuthMethodDescription
  strings and concise "api_key".

Thresholds (auth-type gated):
- api_key: never aborts (user authorized per-call spend).
- cli/oauth/subscription:
  - five_hour utilization >= 0.95 OR resetsAt within 15min (with 0.85
    utilization floor to avoid false trip on freshly-reset windows)
  - seven_day_opus >= 0.93
  - seven_day_sonnet >= 0.92
  - seven_day >= 0.93
  - overage >= 0.95

ClaudeProvider integration (line 198, for-await loop):
- Detects message.type === 'system' && subtype === 'rate_limit'
- Records rate_limit_info via globalRateLimitStore.set
- Calls shouldAbortForQuota(authMethod, globalRateLimitStore)
- On abort: session.abortReason = 'quota:<window>', abortController.abort,
  break out of loop. Worker continues other sessions.

Health endpoint (Server.ts:174):
- New rateLimits field on /api/health from getMostRecentByWindow().
- Field shape: {five_hour?, seven_day?, seven_day_opus?, seven_day_sonnet?,
  overage?} each carrying utilization, status, resetsAt, observedAt.

Tests (tests/worker/rate-limit-store.test.ts):
- 22 cases covering store CRUD, isApiKeyAuth, abort decision matrix.
- api_key never aborts at any utilization.
- cli aborts at threshold breaches per window.
- Reset-grace buffer with utilization floor.

Verification:
- npx tsc --noEmit: only pre-existing bun error
- bun test tests/worker/rate-limit-store.test.ts: 22/22 pass
- bun test tests/claude-provider-resume.test.ts: 9/9 pass
- bun test tests/server/: 44/44 pass

Plugin artifacts regenerated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build: regenerate worker-service.cjs after final build-and-sync

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: align test assertions with F4 classification + timeout

Two test fixes for branch-introduced regressions vs main:

1. tests/gemini_provider.test.ts "should throw on other errors":
   F4's classifyGeminiError replaced upstream Error message with
   ClassifiedProviderError. Test was pinned to pre-F4 string.
   Updated assertion to match new "Gemini bad request (status 400)".

2. tests/infrastructure/graceful-shutdown.test.ts:
   Test pokes real ~/.claude-mem/supervisor.json registry which on a
   developer machine contains live worker + chroma-mcp PIDs. SIGTERM →
   wait → SIGKILL cascade takes ~6s end-to-end. Bumped per-test timeout
   to 15000ms. Underlying shutdown code unchanged. Future cleanup
   should mock getSupervisor() here.

Result: branch failure count == main (77 pre-existing failures).
No new regressions from this branch's work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: address 4 Greptile P1/P2 findings on PR #2282

P1 (real bug): clearStaleMarker silently broken in ESM
- src/shared/oauth-token.ts:14: add unlinkSync to top-level fs import
- src/shared/oauth-token.ts:342: drop inline require('fs'), call
  unlinkSync directly. ESM has no require, so the previous code threw
  ReferenceError swallowed by try/catch — making clearStaleMarker a
  permanent no-op. Stale oauth marker would persist indefinitely after
  Claude Desktop refreshed the token.

P2 (security): execSync shell-string interpolation
- src/shared/find-claude-executable.ts:39: execSync(`"${candidate}"
  --version`) → execFileSync(candidate, ['--version']). Path containing
  ", ;, & — reachable on Windows via crafted CLAUDE_CODE_PATH in
  settings.json — would otherwise produce a malformed/exploitable
  command.

P2 (security): PowerShell username injection
- src/shared/oauth-token.ts:119: userInfo().username escaped with PS
  single-quote convention (' → '') before interpolation into
  `'Claude Code-credentials:${user}'`. Defensive against future Windows
  versions or domain-joined machines that may permit ' in usernames.

P2 (style): Unreachable throw lastError post-loop
- src/services/worker/retry.ts:109: explained as the safety net for
  opts.maxRetries < 0 (pathological input where the loop never executes
  and lastError is undefined). Annotated with comment + descriptive
  fallback Error so the dead-looking code is now self-documenting.

Verification:
- npx tsc --noEmit: clean (only pre-existing bun-types error)
- bun test tests/shared/oauth-token.test.ts tests/worker/provider-classifiers.test.ts
  tests/worker/provider-errors.test.ts: 50 pass / 0 fail

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: tighten SECURITY.md data-flow and audit dates

Fixes CodeRabbit comments #3178957249 (Data Storage section overstated
"no external transmission" — softened to call out Claude Agent SDK,
alternate provider, Chroma MCP, OAuth keychain, and registry fetches)
and #3178957250 (Next Scheduled Audit was earlier than Last Updated;
bumped Last Updated to 2026-05-03 and audit to 2026-09-16) on PR #2282.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: drop inline require('fs') in paths.ts

Fixes CodeRabbit outside-diff comment on src/shared/paths.ts:25-29 from
PR #2282 review. resolveDataDir() ran require('fs') inside an ESM module
(this file uses import.meta.url and .js imports), which can break in
strict ESM environments. readFileSync now imports at the top alongside
existsSync/mkdirSync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: block CLAUDE_CODE_OAUTH_TOKEN from parent env (issue #2215)

Fixes CodeRabbit outside-diff comment on src/shared/EnvManager.ts:14-17
from PR #2282 review. The OAuth-token leak fix was bypassed because
buildIsolatedEnv() copied every parent env var that wasn't in
BLOCKED_ENV_VARS, but CLAUDE_CODE_OAUTH_TOKEN was not blocked. A stale
parent token therefore still reached isolatedEnv even when the fresh
keychain read returned expired/absent — defeating the fix documented
inline at lines 178-183.

Adds CLAUDE_CODE_OAUTH_TOKEN to BLOCKED_ENV_VARS and defensively deletes
it again at the top of buildIsolatedEnvWithFreshOAuth() so the
fresh-spawn-time read is the only path that can populate it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: validate cursor sessionId against path traversal

Fixes CodeRabbit comment #3178957252 on PR #2282. The Cursor adapter
took sessionId straight from stdin and concatenated it into a
join(homedir(), '.cursor', 'projects', ..., sessionId, ...) path. A
crafted value containing path separators or '..' segments could escape
~/.cursor/projects, and the later transcript read would then probe
arbitrary local files.

deriveCursorTranscriptPath() now rejects any sessionId that doesn't
match /^[A-Za-z0-9_-]+$/ — Cursor's real session ids are UUID-style
identifiers, so the safe whitelist is non-disruptive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: scope stripCodeFences() to full-wrapper payloads only

Fixes CodeRabbit comment #3178957253 on PR #2282. The previous regex
greedily removed the first opening and last closing triple-backticks
anywhere in the input, which could mangle valid content with internal
fenced examples or surrounding prose — and ran before XML parsing so
it created false negatives.

stripCodeFences() now only strips when the entire payload is a single
fenced block (start-to-end, with optional language tag and surrounding
whitespace), capturing the inner content. Adds a regression test that
feeds prose with internal triple-backtick markers around a real
<observation> block and asserts the inner ``` are preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: honor abortSignal during retry backoff sleep

Fixes CodeRabbit comment #3178957263 on PR #2282. The retry helper used
an unconditional `setTimeout` Promise for backoff between attempts, so
an external abort that fired during the wait was delayed until the
timer completed.

The backoff now races setTimeout against opts.abortSignal: if the signal
flips, the timer is cleared and the Promise rejects with 'Aborted'
immediately. The abort listener is registered with { once: true } and
removed when the timer fires to avoid leaks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: abort immediately on provider-side rejected status

Fixes CodeRabbit comment #3178957261 on PR #2282. shouldAbortForQuota()
only checked utilization thresholds and reset-grace heuristics; a
snapshot with status='rejected' (or overageStatus='rejected' on the
overage window) but no utilization number could still return
{ abort: false }, letting the worker keep consuming after the provider
had already declared the bucket exhausted.

Provider-side rejection is now checked before utilization. When either
rejection signal is present the guard returns abort=true with reason
"quota:<window> rejected by provider".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: only bump Chroma watermark on confirmed batch writes

Fixes CodeRabbit comments #3178957259 (watermark advances on swallowed
batch failures) and #3178957260 (backfillInProgress can stick true if
init throws) on PR #2282.

addDocuments() previously logged and swallowed per-batch failures with a
void return type, so all three backfill loops (observations, summaries,
prompts) bumped the watermark unconditionally after the call —
turning a transient Chroma failure into permanently-skipped records.
addDocuments() now returns the count of documents that actually landed
(including delete+add reconcile retries), and each loop only advances
the watermark when the batch wrote successfully. Failed batches log a
debug message and continue so the loop still gets through the rest.

backfillAllProjects() now constructs SessionStore and ChromaSync inside
a try block so a constructor throw can't leave the static
backfillInProgress guard stuck true and silently skip every later
backfill. The finally always clears the guard and best-effort closes
each resource.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: fall back to pid kill when process group is gone

Fixes CodeRabbit outside-diff comment on src/supervisor/shutdown.ts:118-134
from PR #2282 review. signalProcess() returned silently when a pgid was
present and process.kill(-pgid, signal) threw ESRCH, never attempting
the per-pid signal. With the new chroma registration path that records a
pgid alongside the pid, an already-collapsed group could turn shutdown
into a no-op even though the root pid was still alive.

The POSIX branch now tries -pgid first when present, and on ESRCH falls
through to process.kill(pid, signal). Non-ESRCH errors still propagate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: settings path, uptime clamp, fetch timeouts

Fixes three smaller CodeRabbit issues on PR #2282:

- SettingsRoutes (outside-diff #2282 review on lines 65-79): the
  parse-error response told users to delete ~/.claude-mem/settings.json
  even when paths.settings() resolved elsewhere. Now uses the resolved
  settingsPath variable in the message.

- uptime.ts (#3178957264 / lines 2-3): getUptimeSeconds() could return
  a negative value if startedAtMs was in the future or the system clock
  moved backward. Clamps with Math.max(0, ...) so health endpoints
  never see negative seconds.

- check-pending-queue.ts (#3178957248 / lines 27-45): checkWorkerHealth,
  getProcessingStatus and triggerProcessing all called fetch with no
  timeout, so the script could block forever if the worker accepted the
  TCP connection but never responded. Wraps each fetch with an
  AbortController + 10s timeout that throws a clear timeout message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: walk descendants recursively when killing chroma-mcp tree

Fixes CodeRabbit comment #3178957258 on PR #2282. The POSIX teardown in
ChromaMcpManager.killProcessTree() relied on `pkill -P <pid>`, which
only signals direct children. Under uv, chroma-mcp spawns python as a
grandchild — when uv exits and python re-parents to init, pkill -P
never reaches it and the descendant survives the "tree kill".

killProcessTree() now collects the full descendant set via a recursive
`pgrep -P` walk before each signal phase. The walk returns leaves first
so signals propagate bottom-up (SIGTERM children before their parents,
then again for SIGKILL after the 500ms grace window so any layer that
re-parented during teardown still gets cleaned up). pgrep failures
(no children, missing binary) return [] so this stays best-effort and
falls back to the existing per-pid signal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: tolerate malformed JSONL lines in transcript-parser

Fixes Greptile P1 comment 3178964456 on PR #2282.

extractLastMessageFromJsonl previously called JSON.parse(rawLine) with no
guard. A truncated/malformed JSONL line — common when a transcript was
crashed mid-write or partially flushed — would throw SyntaxError, crash
the summarization pipeline for that session, and silently lose all
prior valid messages.

Fix: wrap JSON.parse in try/catch and skip bad lines. The empty-line
guard only catches truly empty strings, not malformed fragments.

Regression tests added for two cases:
- Mixed valid + truncated lines: returns last valid match.
- All lines malformed: returns empty string (no throw).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: classify FK constraint failures BEFORE provider classifier

Fixes Greptile P1 comment 3178979583 on PR #2282.

The F4 #2244 work introduced a regression: reclassifyAtDispatch always
returns a non-null ClassifiedProviderError for known agent types
(Claude/Gemini/OpenRouter), so the isFkConstraintFailure branch was dead
code. Per-provider classifiers don't recognize "FOREIGN KEY constraint
failed", so SQLite FK failures fell through to the default 'transient'
kind and would retry indefinitely — restart loop on corrupted session
DB state.

Old unrecoverablePatterns explicitly listed FK constraint as
unrecoverable; restoring that semantic by checking FK FIRST and only
deferring to the classifier when not an FK error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: validate CLAUDE_MEM_WORKER_PORT in check-pending-queue

Parse the env var, range-check (1-65535), and fall back to 37777 with a
console.warn on invalid input instead of letting a malformed value flow
into the URL builder unchecked (CodeRabbit Minor on PR #2282).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: SIGKILL union of pre-TERM and post-wait descendant sets

When the chroma-mcp root exits during the SIGTERM grace window, its
descendants get re-parented to init and drop out of the post-wait
pgrep -P scan. Without including the pre-TERM snapshot, those
re-parented PIDs would never receive SIGKILL even though they were
definitely children before SIGTERM and may still be alive (CodeRabbit
Major on PR #2282).

Compute Array.from(new Set([...descendantsBeforeTerm, ...descendantsBeforeKill]))
and SIGKILL the union. The two sets typically overlap, so dedupe is
required.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: enforce addDocuments return-count in direct sync paths

syncObservation/syncSummary/syncUserPrompt now capture the written count
from addDocuments() and only bump the watermark when every requested
document landed in Chroma. addDocuments() tolerates per-batch failures
(returns the actual written count), so the previous unconditional bump
was silently marking unsynced rows as synced on transient errors —
preventing the next backfill from retrying them (CodeRabbit Major on PR
#2282).

A partial write now logs a warn with the (requested, written) pair and
preserves retryability on the next pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: guard backfill watermark against non-contiguous failures

The backfill watermark is a single monotonic id, so it cannot represent
sparse success: "synced through 200, gap at 201–250, then 251 onward"
would, on restart, skip 201–250 forever because the watermark sat at
either 200 or 251 — both lose data (CodeRabbit Major on PR #2282).

Add a per-loop hadGap flag to backfillObservations / backfillSummaries /
backfillPrompts. Once any batch under-writes, every subsequent batch
must also skip the bump, regardless of whether it itself succeeded.
Also tighten the failure check from `writtenInBatch <= 0` to
`writtenInBatch < batch.length` so partial-batch writes are caught.

The watermark stays at the last contiguously-synced position; the next
backfill pass retries from there, eventually closing the gap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: clear oauth-stale marker when token is absent

When an OAuth token disappears entirely (user logs out, keychain
cleared), buildIsolatedEnvWithFreshOAuth's absent branch was leaving any
prior stale-marker file in place. The session-start hook would then keep
surfacing an "expired token, re-login" warning even though the token is
no longer expired — it's gone, and re-login was already done elsewhere
or not applicable (CodeRabbit Minor on PR #2282).

Call clearStaleMarker() in the absent branch the same way the present
branch already does. Add a regression test exercising the full
buildIsolatedEnvWithFreshOAuth path: pre-write a marker, force absent
via spoofed unsupported platform, assert the marker is gone after.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: skip unknown message.content shapes instead of throwing

extractLastMessageFromJsonl already tolerates malformed JSONL lines
(JSON.parse failure -> continue), but a valid JSON line whose
message.content is an unexpected type (null, number, plain object) was
still throwing — contradicting the new tolerance and crashing the entire
summary pipeline on a single weird line (CodeRabbit Major + Greptile P1
on PR #2282).

Replace the `throw new Error(...)` with `continue` so a single bad
content shape skips that line instead of failing the whole transcript
read. Forward compat: future content schemas land harmlessly.

Add regression tests covering null, number, and plain-object content;
each must not throw and must fall back to the most recent valid line.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* review: guard null/primitive entries in message.content array

Fixes CodeRabbit comment 3179004190 on PR #2282.

The Array.isArray branch previously did `c.type === 'text'` directly,
which throws if `c` is null or a primitive — possible in malformed logs.
Tightened the filter with a type guard: requires c to be a non-null
object with type === 'text' and a string text field. Same defensive
class as the malformed-line and unknown-content-shape tolerances.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 22:27:07 -07:00
airkjw 0289eab838 chore: merge upstream v12.5.1 + keep local fixes
Deploy Install Scripts / deploy (push) Has been cancelled
Windows / build (push) Has been cancelled
Upstream changes (v12.4.7 → v12.5.1):

v12.4.8 — timeline tool: coerce stringified numeric anchor in MCP tool (#2176)

v12.4.9 — 7 critical fixes (PR #2219):
  - build/bundle drift fix
  - strip privacy tags before summarization
  - preserve relevance order in semantic search
  - restore Windows spawn + Windows CI
  - Codex transcript ingestion + Windows queue self-deadlock fix
  - SDK boundary isolation (closes 6 issues)
  - standalone batch fixes

v12.5.0 — observation pipeline cleanup:
  - removed per-message retry counter that silently dropped data
  - parser collapsed to binary {valid:true,...} | {valid:false}
  - schema migrations v31 + v32 drop dead pending_messages columns:
    retry_count, failed_at_epoch, completed_at_epoch, worker_pid
  - status enum reduced to 'pending' | 'processing'
  - GeneratorExitHandler drain-in-flight loop deleted
  - PendingMessageStore: 226 → 165 lines (markFailed etc removed)
  - net -181 lines of source

v12.5.1 — install fix for Node 25+ (tree-sitter native build skipped via
  trustedDependencies allowlist) (#2278)

Local fixes preserved (resolved manual conflicts in SessionStore.ts and
sessions/create.ts where upstream removed surrounding context):
- env-sanitizer PATH extension for claude CLI lookup (auto-merged with
  upstream's expanded ENV_PRESERVE for AWS Bedrock / Vertex auth vars)
- SessionStore + sessions/create stale session reset (mac sleep / 4h
  wall-clock)

Built artifacts rebuilt; both fixes verified present in worker-service.cjs.
Worker restarted to v12.5.1 (PID 94088). Schema v31+v32 auto-migration
ran on startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 13:27:58 +09:00
Alex Newman 7fb0745ddb docs: update CHANGELOG.md for v12.5.1 2026-05-03 15:07:35 -07:00
Alex Newman 9f9fbd76fb chore: bump version to 12.5.1 2026-05-03 15:05:47 -07:00
Alex Newman 99ff296f6f fix: skip tree-sitter native build via trustedDependencies allowlist (#2278)
bun install fails on Node 25+ because the upstream tree-sitter@0.25.0
package's binding.gyp specifies C++17, but Node 25's V8 headers require
C++20 and #error on older standards. The package ships no prebuilds for
this platform/Node combo, so node-gyp-build falls back to source compile
and dies with hundreds of errors.

claude-mem doesn't use the tree-sitter runtime — it only shells out to
the prebuilt tree-sitter-cli Rust binary (see Hu/CS in the bundled
mcp-server). Add trustedDependencies: ["tree-sitter-cli"] so bun runs
the CLI's install.js (downloads the Rust binary) but blocks every other
postinstall, including the failing native compile and the unused .node
bindings of all 24+ grammar packages.

Verified end-to-end on darwin-arm64 / Node 25.9.0: 37 packages install
in ~30s, 28 postinstalls correctly blocked, CLI binary works,
grammars still JIT-compile via tree-sitter query -p <grammar-dir>.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 14:58:54 -07:00
Alex Newman 794745b3a3 docs: update CHANGELOG.md for v12.5.0 2026-05-02 16:09:31 -07:00
Alex Newman a81a1f6964 chore: bump version to 12.5.0 2026-05-02 16:07:20 -07:00
Alex Newman 9e2973059a UX redesign: installer + provider rename + /learn-codebase + welcome card + SessionStart hint (#2255)
* feat(ux): claude-mem UX improvements with installer enhancements

Squashed PR #2156 commits for clean rebase onto main:
- feat(installer): add provider selection, model prompt, worker auto-start
- refactor: rename *Agent provider classes to *Provider
- feat: add /learn-codebase skill and viewer welcome card
- feat(worker): inject welcome hint when project has zero observations
- fix(pr-2156): address greptile review comments
- fix(pr-2156): address coderabbit review comments
- fix(pr-2156): persist CLAUDE_MEM_PROVIDER for non-claude in non-TTY mode
- fix(pr-2156): file-backed settings reads in installer + env-first SKILL doc

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build: rebuild plugin artifacts after rebase onto v12.4.7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(skills): strip claude-mem internals from learn-codebase

The learn-codebase skill, install next-step copy, WelcomeCard, and
welcome-hint previously walked the primary agent through worker endpoints
and synthetic observation payloads. The PostToolUse hook already captures
every Read/Edit the agent makes — the agent should have no awareness that
the memory layer exists. Collapse the skill to one instruction ("read every
source file in full") and rephrase touchpoints to describe only what the
user observes (Claude reading files), not what happens behind the scenes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sync): preflight version mismatch + settings-aware port resolution

Two related fixes for build-and-sync's worker restart step:

1. Read CLAUDE_MEM_WORKER_PORT from ~/.claude-mem/settings.json the same
   way the worker does, instead of computing the default port from the
   uid alone. Previously, users with a custom port saw a misleading
   "Worker not running" message because the restart POST hit the wrong
   port and got ECONNREFUSED.

2. Add a preflight check that aborts the sync when the running worker's
   reported version does not match the version we are about to build.
   Claude Code's plugin loader pins the worker to a specific cache
   version per session, so syncing into a newer cache directory has no
   effect until the user runs `claude plugin update thedotmack/claude-mem`
   to bump the pin. The preflight surfaces this explicitly with the exact
   command to run; --force bypasses it for intentional cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(learn-codebase): note sed for partial reads of large files

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: strip comments codebase-wide

Removed prose comments from all tracked source. Preserved directives
(@ts-ignore, eslint-disable, biome-ignore, prettier-ignore, triple-slash
references, webpack magic, shebangs). Deleted two tests that asserted
on comment text rather than runtime behavior.

Net: 401 files, -14,587 / +389 lines, -10.4% bytes.

Verified: typecheck passes, build passes, test count unchanged from
baseline (22 pre-existing fails, all unrelated).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(installer): move runtime setup into npx, eliminate hook dead air

Smart-install ran 3 times during a fresh install — the worst run was silent,
fired by Claude Code's Setup hook after `claude plugin install`, producing
~30s of dead air that looked like the plugin was hung.

This change makes `npx claude-mem install` the single place heavy work
happens, with a visible spinner. Hooks become runtime-only.

- New `src/npx-cli/install/setup-runtime.ts` module: ensureBun, ensureUv,
  installPluginDependencies, read/writeInstallMarker, isInstallCurrent.
  Marker schema preserved exactly ({version, bun, uv, installedAt}) so
  ContextBuilder and BranchManager readers keep working.
- `npx claude-mem install`: ungated copy/register/enable for every IDE,
  inserts a "Setting up runtime" task with honest "first install can take
  ~30s" spinner. The claude-code shell-out to `claude plugin install` is
  removed — npx already populated everything Claude reads.
- New `npx claude-mem repair` command for post-`claude plugin update`
  recovery, force-reinstalls runtime.
- Setup hook now runs `plugin/scripts/version-check.js` (29ms wall) instead
  of smart-install. Mismatch prints "run: npx claude-mem repair" on stderr.
  Always exits 0 (non-blocking, per CLAUDE.md exit-code strategy).
- SessionStart loses the smart-install entry; 2 hooks remain (worker start,
  context fetch).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(installer): delete smart-install sources, retarget tests

- Delete scripts/smart-install.js + plugin/scripts/smart-install.js (both
  are source files kept in sync manually; both must go).
- Delete tests/smart-install.test.ts (covered surface is gone).
- tests/plugin-scripts-line-endings: drop smart-install.js entry.
- tests/infrastructure/plugin-distribution: retarget two assertions at
  version-check.js (the new Setup hook script).
- New tests/setup-runtime.test.ts: 9 tests covering marker read/write,
  isInstallCurrent semantics. Marker schema invariant verified.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(installer): describe npx-driven setup + version-check Setup hook

Sweep public docs and architecture notes to reflect the new flow:
npx installer does Bun/uv setup with a visible spinner; Setup hook runs
sub-100ms version-check.js; users hit `npx claude-mem repair` after a
`claude plugin update`.

- docs/architecture-overview.md: hook lifecycle table + npx flow paragraph
- docs/public/configuration.mdx: tree + hook config example
- docs/public/development.mdx: build output line
- docs/public/hooks-architecture.mdx: full rewrite of pre-hook section,
  timing table, performance table
- docs/public/architecture/{overview,hooks,worker-service}.mdx: tree
  comments, JSON config example, Bun requirement section

docs/reports/* untouched (historical incident reports).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): mergeSettings writes via USER_SETTINGS_PATH

Greptile P1 (#2156): `settingsFilePath()` only resolved
`process.env.CLAUDE_MEM_DATA_DIR`, while `getSetting()` reads via
`USER_SETTINGS_PATH` which `resolveDataDir()` populates from BOTH the env
var AND a `CLAUDE_MEM_DATA_DIR` entry persisted in
`~/.claude-mem/settings.json`. Result: a user with the data dir saved in
settings.json but not exported in their shell would have provider/model
settings silently written to `~/.claude-mem/settings.json` while
`getSetting()` read from `/custom/path/settings.json` — read/write split.

Drop `settingsFilePath()` and the now-unused `homedir` import; reuse the
already-imported `USER_SETTINGS_PATH` constant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cli): parse --provider, --model, --no-auto-start install flags

Greptile P1 (#2156): InstallOptions has fields `provider`, `model`,
`noAutoStart`, but the install case in the npx-cli switch only parsed
`--ide`. The other three flags were silently dropped — `npx claude-mem
install --provider gemini` was a no-op.

Extract a `parseInstallOptions(argv)` helper, share it between the bare
`npx claude-mem` and `npx claude-mem install` paths, and validate
`--provider` against the allowed set. Update help text accordingly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): pipe runtime-setup output, always show IDE multiselect

Two issues caught in a docker test of the installer:

1. The bun.sh installer, uv installer, and `bun install` were using
   stdio: 'inherit', dumping their stdout/stderr through clack's spinner
   region — visible as raw "downloading uv 0.11.8…" / "Checked 58
   installs across 38 packages…" text streaming under the spinner. Switch
   to stdio: 'pipe' and surface captured stderr only on failure (via a
   shared describeExecError() helper that includes stdout when stderr is
   empty). Spinner stays clean on the happy path.

2. promptForIDESelection() silently picked claude-code when no IDEs were
   detected, never showing the user the multiselect. On a fresh machine
   with no IDEs present yet (e.g. our docker test container), the user
   never got to choose. Now: always show the full IDE list when
   interactive; mark detected ones with [detected] hints and pre-select
   them; show a warn line if zero are detected explaining they should pick
   what they plan to use. Non-TTY callers still get the silent
   claude-code default at the call site (unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): skip marketplace work for claude-code-only, offer to install Claude Code

Two related UX fixes from a docker test:

**Delay between "Saved Claude model=…" and "Plugin files copied OK"**

After dropping the needsManualInstall gate, every install was unconditionally
running `copyPluginToMarketplace` (which copied the entire root node_modules
tree — thousands of files, dozens of seconds) and `runNpmInstallInMarketplace`
(npm install --production) even when only claude-code was selected. Neither
is needed for claude-code: that path uses the plugin cache dir + the
installed_plugins.json + enabledPlugins flag, all of which we already write.

- Drop `node_modules` from `copyPluginToMarketplace`'s allowed-entries list;
  the dependency-install task populates it on the destination side anyway.
- Re-introduce `needsMarketplace = selectedIDEs.some(id => id !== 'claude-code')`
  scoped *only* to `copyPluginToMarketplace`, `runNpmInstallInMarketplace`,
  and the pre-install `shutdownWorkerAndWait` (also pointless for claude-code-
  only flows since we're not overwriting the worker's running cache dir
  source). All other tasks (cache copy, register, enable, runtime setup) stay
  unconditional.

**Claude Code missing → silent install of an IDE that isn't there**

When the user picked claude-code on a machine without it (e.g. a fresh
container), the install completed but `claude` was unavailable and the only
hint was a generic warn line. Replace with an explicit pre-flight prompt:

  Claude Code is not installed. Claude-mem works best in Claude Code, but
  also works with the IDEs below.
  ? Install Claude Code now?
    ◆ Yes — install Claude Code (recommended)
    ◯ No — pick another IDE below
    ◯ Cancel installation

If the user picks "Yes", run `curl -fsSL https://claude.ai/install.sh | bash`
(or the PowerShell equivalent on Windows), then re-detect IDEs and proceed
with claude-code pre-selected. If the install fails or the user picks "No",
the multiselect still appears with claude-code visible (just unmarked
[detected]), so they can opt in or pick another IDE.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): detect Claude Code via `claude` CLI, not ~/.claude dir

The directory `~/.claude` can exist (e.g. mounted in Docker, or created
by tooling) without Claude Code actually being installed. Detect the
`claude` command in PATH instead so the installer correctly offers to
install Claude Code when missing.

* docs(learn-codebase): add reviewer note explaining the cost tradeoff

The skill intentionally reads every file in full to build a cognitive
cache that pays off across the rest of the project. Add a brief note
so reviewers (human or bot) understand the tradeoff before flagging
the unbounded read as a cost issue.

* fix: address Greptile P1 feedback on welcome hint and learn-codebase

- SearchRoutes: skip welcome hint when caller passes ?full=true so
  explicit full-context requests aren't intercepted by the hint.
- learn-codebase: replace `sed` instruction with the Read tool's
  offset/limit parameters, since Bash is gated in Claude Code by
  default.

* feat(install): ASCII-animated logo splash on interactive install

Plays a ~1s bloom animation of the claude-mem sunburst logomark when
the installer starts in an interactive terminal — geometrically rendered
via 12 ray curves around a center disc, in the brand orange. The
wordmark and tagline type on alongside the final frame.

Auto-skipped on non-TTY, in CI, when NO_COLOR or CLAUDE_MEM_NO_BANNER
is set, or when the terminal is too narrow.

Inspired by ghostty +boo.

* feat(banner): replace rotation frames with angular-sector bloom generator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(banner): replace rotation frames with angular-sector bloom generator

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(banner): three-act choreography renderer with radial gradient and diff redraw

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(banner): update preview script to support small/medium/hero tier selection

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(docker): add COLORTERM=truecolor to test-installer sandbox

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(install): auto-apply PATH for Claude Code with spinner UX

The Claude Code install.sh prints a Setup notes block telling users to
manually edit "your shell config file" to add ~/.local/bin to PATH —
which left fresh installs unable to launch claude from the command line.

After a successful install, detect ~/.local/bin/claude on disk and, if
the dir is missing from PATH, append the right export line to .zshrc /
.bash_profile / .bashrc / fish config (idempotent, marked with a
comment). Also updates process.env.PATH for the current install run.

Wraps the curl|bash install in a clack spinner (interactive only) so the
~4 minute native-build download doesn't look frozen — output is captured
silently and dumped on failure for debuggability. Non-interactive mode
keeps inherited stdio for CI logs.

Verified end-to-end in the test-installer docker sandbox: spinner
animates, .bashrc gets the export, fresh login shell resolves claude.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(banner): video-frame ASCII renderer with three-act choreography

Generator switched from a single Jimp-rendered logo to pre-extracted
video frames concatenated with \x01 separators and gzip-deflated, ported
from ghostty's boo wire format. Renderer rewritten around three acts
(ignite → stagger bloom → text reveal + breathe) with adaptive sizing,
radial gradient, and diff-based redraw.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(onboarding): unify install / SessionStart / viewer around one first-success moment

Three surfaces now point at the same north-star moment — open the viewer, do
anything in Claude Code, watch an observation appear within seconds — with the
same verbatim timing and privacy lines, and a single canonical "how it works"
explainer instead of three diverging copies.

- Canonical explainer at src/services/worker/onboarding-explainer.md served via
  GET /api/onboarding/explainer; mirrored into plugin/skills/how-it-works/SKILL.md
- SessionStart welcome hint rewritten as third-person status (no imperatives
  Claude tries to execute), pinned with a default-value regression test
- Post-install Next Steps reframed as "two paths": passive default + optional
  /learn-codebase front-load; drops /mem-search and /knowledge-agent from this
  surface; adds verbatim timing + privacy lines and /how-it-works link
- /api/stats response gains firstObservationAt for the viewer stat row
- Viewer WelcomeCard branches on observationCount === 0: empty state shows live
  worker-connection dot + "waiting for activity"; has-data state shows
  observations · projects · since [date] and two example prompts. v2 dismiss key
- jimp added to package.json to fix pre-existing banner-frame build break

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(banner): play unconditionally; only honor CLAUDE_MEM_NO_BANNER

The 128-col / TTY / CI / NO_COLOR gates silently swallowed the banner in
narrower terminals, CI logs, and any non-TTY pipe — including Docker runs
where -it should preserve the experience but column width was the wrong
gate. Remove the implicit gates; keep the explicit opt-out only.

If a frame wraps in a narrow terminal, that's better than the banner
not playing at all.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* revert(banner): restore 15:33 gating logic per user request

Reverts eb6fc157. Restores isBannerEnabled to the state at commit
8e448015 (2026-04-30 15:33): TTY check, !CI, !NO_COLOR, !CLAUDE_MEM_NO_BANNER,
and cols >= BANNER.width.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(install): wrap remaining slow steps with spinners

Each IDE installer (Cursor, Gemini CLI, OpenCode, Windsurf, OpenClaw,
Codex CLI, MCP integrations) now runs inside a clack task spinner with
per-step progress messages instead of silent dynamic-import + cpSync.
Pre-overwrite worker shutdown (up to 10s) and the post-install health
probe (up to 3s) also get spinners.

Internal console.log/error/warn from each IDE installer is buffered
during the spinner; if the install fails, captured output is replayed
afterward via log.warn so users can see what broke.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(review): observation count + IDE pre-selection regressions

WelcomeCard's "no observations yet" empty state was triggered when a
project filter narrowed the feed to zero rows, even with thousands of
observations elsewhere. Source the count from global stats.database
to match firstObservationAt's scope.

Restore initialValues: [] in the IDE multiselect — pre-selecting every
detected IDE was the exact regression #2106 was filed for.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): trichotomy worker state + cache fallback for script path

ensureWorkerStarted now returns 'ready' | 'warming' | 'dead' instead of
boolean. The spawned-but-still-warming case (common in Docker cold
starts and slow first-time inits) was being misreported as 'did not
start', which contradicted the next-steps panel saying 'still starting
up'. Install task message and Next Steps headline now agree on the
actual state.

Also fixes the actual root cause of 'Worker did not start' on
claude-code-only installs: the worker script path was hardcoded to the
marketplace dir, which is left empty when no non-claude-code IDE is
selected. Now falls back to pluginCacheDirectory(version) when the
marketplace copy isn't present.

Verified end-to-end in docker/claude-mem with --ide claude-code,
--ide cursor, and a fresh container — install task and headline
agree on 'Worker ready at http://localhost:<port>' in all cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: align CLAUDE.md and public docs with current code

Sweep across CLAUDE.md and 10 high-traffic docs/public/ MDX files to
remove point-in-time references and align with the actual current
shape of the codebase. Highlights:

- Hardcoded port 37777 → per-user formula (37700 + uid % 100) on the
  front-door pages (introduction, installation, configuration,
  architecture/overview, architecture/worker-service, troubleshooting,
  hooks-architecture, platform-integration).
- Default model 'sonnet' → 'claude-haiku-4-5-20251001' (matches
  SettingsDefaultsManager).
- Node 18 → 20 (matches package.json engines).
- Lifecycle hook count corrected (5 events).
- Removed the nonexistent 'Smart Install' component and pre-built
  directory tree referencing files that no longer exist
  (context-hook.ts, save-hook.ts, cleanup-hook.ts, etc.); replaced
  with the real worker dispatcher shape.
- Removed CLAUDE.md '#2101' issue tag (kept the design rationale).
- Replaced obsolete hooks.json example with a description of the real
  bun-runner.js / worker-service.cjs hook event shape.

Lower-traffic doc pages still hardcode 37777 — left for a separate
global pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(scripts): land strip-comments around real parsers (postcss, remark, parse5)

Each language gets a real parser to locate comments, then we splice ranges
out of the original source. The library never serializes — that's how
remark-stringify produced 243 reformat-noise diffs in the first attempt
versus the 21 real strip targets here.

  JS/TS/JSX  -> ts.createSourceFile + getLeadingCommentRanges
  CSS/SCSS   -> postcss.parse + walkComments + node.source offsets
  MD/MDX     -> remark-parse (+ remark-mdx) + AST html / mdx-expression nodes
  HTML       -> parse5 with sourceCodeLocationInfo
  shell/py   -> kept hand-rolled hash stripper (no library worth the dep)

Preserves: shebangs, @ts-* directives, eslint-disable, biome-ignore,
prettier-ignore, triple-slash refs, webpack magic, /*! license keep,
@strip-comments-keep file marker. JS/TS handler runs a parse-roundtrip
check and refuses to write if syntax errors increased (catches the
worker-utils.ts breakage class from the 2026-04-29 attempt).

npm scripts:
  strip-comments         (apply)
  strip-comments:check   (CI-style, exits non-zero if changes needed)
  strip-comments:dry-run (list, no writes)

Verified --check on this repo: 21 changes, -4.0% bytes, no parse-error
regressions, no reformat-suspect false positives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: strip comments codebase-wide via parser-backed tool

21 files changed, -17,550 bytes (-4.0%) of narrative comments removed
across .ts / .tsx / .js / .mjs and the .gitignore. JS/TS comments stripped
via ts.createSourceFile + getLeadingCommentRanges — same canonical lexer,
same behavior as the 2026-04-29 strip, no reformat noise.

Preexisting baseline (unchanged):
  typecheck: 16 errors at HEAD, 16 errors after strip (line numbers shift,
             no new error classes — verified via diff of sorted error lists)
  build:     fails at HEAD with CrushHooksInstaller.js unresolved import
             (preexisting, unrelated to this strip)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(install): drop Crush integration references after extract

The Crush integration was extracted to its own branch on May 1, but the
import at install.ts:280 (and the case block + ide-detection entry +
McpIntegrations config + npx-cli help text) still referenced the now-
removed CrushHooksInstaller.js, breaking the build.

Removes:
- case 'crush' block in install.ts
- crush entry in ide-detection.ts
- CRUSH_CONFIG and registration in McpIntegrations.ts
- 'crush' from the IDE Identifiers help line in index.ts

Rebuilds worker-service.cjs to match.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(banner): mark generated banner-frames.ts with @strip-comments-keep

Without this, every build/strip cycle ping-pongs five lines of doc
comments in and out of the auto-generated output. The keep-marker tells
strip-comments.ts to skip the file entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(build): drop banner-frame regen from build script

generate-banner-frames.mjs requires PNG frames in /tmp/cmem-banner-frames
that only exist after the maintainer runs ffmpeg locally on the source
video. CI has neither the video nor the frames, so the build broke on
Windows. The output (src/npx-cli/banner-frames.ts) is committed, so the
regen is a one-shot dev step — not a build step. Run the script directly
when the video changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(worker): unstick the spinner — kill claim-self-lock, wake on fail, auto-broadcast

Three surgical changes that cure the stuck-spinner bug at the source.

Phase 1.1 (L9): claimNextMessage no longer self-excludes its own worker_pid.
A single UPDATE-RETURNING grabs the oldest pending row by id. Removes the
LiveWorkerPidsProvider plumbing that was never injected — Supervisor enforces
single-worker via PID file, so the multi-worker SQL was defending against a
configuration the project does not support.

Phase 1.2 (L19): SessionManager.markMessageFailed wraps PendingMessageStore.markFailed
and emits 'message' on the per-session EventEmitter. The iterator's waitForMessage
now wakes immediately on re-pend instead of parking for 3 minutes. ResponseProcessor
and SessionRoutes routed through the new wrapper.

Phase 1.3 (L24): PendingMessageStore takes an optional onMutate callback fired
from every mutator (enqueue, claimNextMessage, confirmProcessed, markFailed,
transitionMessagesTo, clearFailedOlderThan). SessionManager wires it; WorkerService
passes broadcastProcessingStatus. Ten manual broadcast calls deleted across
SessionCleanupHelper, SessionEventBroadcaster, SessionRoutes, DataRoutes, and
worker-service. Caller discipline becomes structurally impossible to forget.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(worker): delete dead code — legacy routes, processPendingQueues, decorative guards

Pure deletions. Phase 2 of kill-the-asshole-gates.

- Legacy /sessions/:sessionDbId/* routes (handleSessionInit, handleObservations,
  handleSummarize, handleSessionStatus, handleSessionDelete, handleSessionComplete)
  bypassed all five ingest gates and were a parallel write path. Folded the
  initializeSession + broadcastNewPrompt + syncUserPrompt + ensureGeneratorRunning
  + broadcastSessionStarted work into the canonical /api/sessions/init handler so
  the hook makes one round trip instead of two.
- processPendingQueues (~104 lines, zero callers) — replaced in Phase 6 by a
  one-statement startup sweep.
- spawnInProgress Map and crashRecoveryScheduled Set — decorative dedupe over
  generatorPromise and stillExists checks that already provide the real safety.
- STALE_GENERATOR_THRESHOLD_MS — pre-empted live generators and raced with the
  finally block; the 3min idle timeout already kills zombies.
- MAX_SESSION_WALL_CLOCK_MS — ran a SELECT on every observation to enforce 24h.
  Runaway-spend protection lives in the API key, not in claude-mem.
- Missing-id 400 in shared.ts ingestObservation — Zod already enforces min(1)
  on contentSessionId and toolName at the route schema.
- SessionCompletionHandler import + completionHandler field on SessionRoutes
  (orphaned after handler deletions).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(worker): SQL-backed getTotalQueueDepth — single source of truth

Was: iterate this.sessions.values() and sum getPendingCount per session.
Now: SELECT COUNT(*) FROM pending_messages WHERE status IN ('pending','processing').

The in-memory sessions Map drifted from the DB rows whenever a generator exited
without confirm/fail, leading to false-positive isProcessing in the UI. Phase 1.3's
auto-broadcast fires on every mutation, but it broadcast a stale Map count.
Reading from the DB makes the UI's spinner state match what the queue actually holds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(worker): typed abortReason replaces wasAborted boolean

Was: a boolean wasAborted that lumped every abort together. The finally block
branched on !wasAborted, so any abort skipped restart — including idle aborts
with pending work, which is exactly the case where we DO want to restart.

Now: ActiveSession.abortReason is a typed enum 'idle' | 'shutdown' | 'overflow'
| 'restart-guard'. The finally block consumes the reason and only skips restart
for 'shutdown' and 'restart-guard'. Idle and overflow aborts fall through, so
if pending work exists they trigger restart correctly.

Dropped 'stale' and 'wall-clock' from the union — Phase 2 deleted those paths.
Natural-completion abort (post-success) intentionally has no reason; it's not
gating restart logic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(worker): unify the two generator-exit finally blocks

Was: worker-service.ts:startSessionProcessor and SessionRoutes:ensureGeneratorRunning
each had their own ~70-line finally block with divergent restart-guard handling.
The worker-service path called terminateSession on RestartGuard trip and orphaned
pending rows (the L16 bug); the SessionRoutes path drained them. Two places to
update when rules changed.

Now: handleGeneratorExit in src/services/worker/session/GeneratorExitHandler.ts
owns the contract:
  1. Always kill the SDK subprocess if alive.
  2. Always drain processingMessageIds via sessionManager.markMessageFailed
     (which wakes the iterator — Phase 1.2).
  3. shutdown / restart-guard reasons: drain pending rows via
     transitionMessagesTo('failed'), finalize, remove from Map. Fixes L16.
  4. pendingCount=0: finalize normally and remove from Map.
  5. pendingCount>0: backoff respawn via per-session respawnTimer (no global Set;
     Phase 2.4 deleted that). RestartGuard trip drains to 'abandoned'.

Both finally blocks are now ~10-line wrappers that translate local state into the
canonical abortReason and delegate. Restored completionHandler injection into
SessionRoutes (was dropped in Phase 2 cleanup; needed by the unified helper for
finalizeSession).

Behavior change: SessionRoutes' previous "keep idle session in memory" was
deliberately replaced by the plan's "remove from Map on natural completion" —
next observation reinitializes via getMessageIterator → initializeSession.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(worker): startup orphan sweep — reset 'processing' rows at boot

When the worker dies (crash, kill, restart), any pending_messages rows it left
in 'processing' state are by definition orphans (the only worker is dead).
Single SQL UPDATE at boot resets them to 'pending' so the iterator can claim
them again. Replaces the deleted processPendingQueues function (Phase 2.2).

Runs in initializeBackground after dbManager.initialize() and before the
initializationComplete middleware releases blocked HTTP requests, so no
in-flight request can race the sweep. NOT on a periodic timer — after boot,
every 'processing' row has a live consumer and a periodic sweep would race.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(worker): simplify enqueue catch, replace memorySessionId throw with re-pend

7.1: queueObservation's catch was logging two ERROR-level messages and rethrowing.
The rethrow is correct (FK violations / disk full / schema drift should crash
loudly), but the verbose ERROR logging pretended the error was recoverable.
Reduced to one INFO line + rethrow.

7.2: ResponseProcessor's memorySessionId guard was throwing if the SDK hadn't
included session_id on the first user-yield, terminal-failing the entire batch.
Now warns and re-pends in-flight messages via sessionManager.markMessageFailed
(which wakes the iterator — Phase 1.2). The next iteration tries again with
memorySessionId hopefully captured.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sync): mirror builds to installed-version cache for hot reload

When package.json bumps past Claude Code's installed pin, sync-marketplace
wrote new code to cache/<buildVersion>/ but the worker loaded from
cache/<installedVersion>/, so worker:restart reloaded the same old code.

Replace the exit-on-mismatch preflight with a mirror step: when versions
differ, also rsync plugin/ into cache/<installedVersion>/ so worker:restart
hot-reloads new code without a Claude Code session restart. The
build-version cache still gets written for the eventual
`claude plugin update`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: delete dead barrel files and orphan utilities

- src/sdk/index.ts (re-exports parser+prompts; nothing imported the barrel)
- src/services/Context.ts (re-exports ./context/index.js; no importers)
- src/services/integrations/index.ts (no importers)
- src/services/worker/Search.ts (3-line barrel of ./search/index.js)
- src/services/infrastructure/index.ts: drop CleanupV12_4_3 re-export
- src/utils/error-messages.ts (getWorkerRestartInstructions never imported)
- src/types/transcript.ts (170 LoC of types, zero importers)
- src/npx-cli/_preview.ts (banner dev preview, no script wires it)

Build + tests still pass; observations still flowing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(parser): drop unused detectLanguage

Only the user-grammar-aware variant detectLanguageWithUserGrammars()
is actually called.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(types): drop unused SdkSessionRecord + ObservationWithContext

Both interfaces in src/types/database.ts had zero importers anywhere
in src or tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(npx-cli): drop unused getDetectedIDEs + claudeMemDataDirectory

getDetectedIDEs has no callers — install.ts uses detectInstalledIDEs
directly. claudeMemDataDirectory has no callers either.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ProcessManager): drop dead orphan-reaper + signal-handler helpers

Each had zero callers in src/ or tests/:
  - cleanupOrphanedProcesses + enumerateOrphanedProcesses
  - ORPHAN_PROCESS_PATTERNS + ORPHAN_MAX_AGE_MINUTES
  - forceKillProcess
  - waitForProcessesExit
  - createSignalHandler
  - resetWorkerRuntimePathCache

The orphan reaper was retired in PATHFINDER Plan 02 ("OS process groups
replace hand-rolled reapers", commit 94d592f2) — these were the leftover
pieces. shutdown.ts uses the supervisor's own kill-pgid path instead.

parseElapsedTime kept (covered by tests/infrastructure/process-manager.test.ts).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(scripts): delete 11 unreferenced DX/forensic scripts

None of these are referenced by package.json npm scripts or docs/.
All last touched on Apr 29 only as part of the comment-stripping
pass — the feature code itself is older and orphaned:

  analyze-transformations-smart.js
  debug-transcript-structure.ts
  dump-transcript-readable.ts
  endless-mode-token-calculator.js
  extract-prompts-to-yaml.cjs
  extract-rich-context-examples.ts
  find-silent-failures.sh
  fix-all-timestamps.ts
  format-transcript-context.ts
  test-transcript-parser.ts
  transcript-to-markdown.ts

These are standalone tools — runtime behavior unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(scripts): delete unused extraction/ and types/ subdirs

- scripts/extraction/{extract-all-xml.py, filter-actual-xml.py, README.md}
  point at ~/Scripts/claude-mem/ — the user's pre-relocation path that no
  longer exists. Zero references in package.json, src/, or tests/.
- scripts/types/export.ts duplicates ObservationRecord etc. and has no
  importers (CodexCliInstaller imports transcripts/types, not this).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(BranchManager): drop dead getInstalledPluginPath

OpenCodeInstaller has its own (used) getInstalledPluginPath; the
BranchManager copy never had any external callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(ChromaSyncState): unexport DocKind (used internally only)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(gemini): drop stale earliestPendingTimestamp / processingMessageIds

Both fields were removed from ActiveSession in earlier queue-engine
cleanup. Tests had been silently keeping them because the mock sessions
use 'as any' to bypass strict typing, so the dead fields rode along
without complaint.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: drop 3 unused module-level constants

- src/npx-cli/banner.ts: CURSOR_HOME, CLEAR_DOWN (banner uses
  CLEAR_SCREEN which combines clear-down + cursor-home into a single
  CSI sequence; the standalone constants were leftovers).
- src/services/worker/BranchManager.ts: DEFAULT_SHELL_TIMEOUT_MS
  (BranchManager only uses GIT_COMMAND_TIMEOUT_MS / NPM_INSTALL_TIMEOUT_MS).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(opencode-plugin): drop dead workerPost helper

Only the fire-and-forget variant (workerPostFireAndForget) is actually
called. workerPost was the await-result version with no remaining caller.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: drop 8 truly-unused interface fields

Verified each by grepping for `.field`, `"field"`, `'field'`, and
`field:` patterns across src/ + tests/ + plugin/scripts. Where the
only remaining usage was the assignment site, removed the assignments too.

- GitHubStarsData: watchers_count, forks_count (only stargazers_count read)
- TableColumnInfo: dflt_value (PRAGMA returns it but no caller reads it)
- IndexInfo: seq (PRAGMA returns it but no caller reads it)
- ObservationRecord: source_files (legacy field, no readers)
- HookResult.hookSpecificOutput: permissionDecisionReason
- WatchTarget: rescanIntervalMs (set in config, never read)
- ShutdownResult: confirmedStopped (write-only — assigned but no
  reader; updated all 3 return sites to drop it)
- ModePrompts: language_instruction (multilingual support never wired)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(npx-cli): reuse InstallOptions type instead of inline duplicate

parseInstallOptions had its return type written out inline as an
anonymous duplicate of InstallOptions. Use the canonical type
(import type — zero bundle cost).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(integrations): drop unused Platform type alias

The detectPlatform() function that returned this type was deleted earlier
in the branch (along with getScriptExtension that consumed it). The type
itself outlived its consumer; only string literals "Platform:" survive in
console.log diagnostics, which don't reference the alias.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(worker): broadcast processing_status when summarize is queued

broadcastSummarizeQueued was an empty no-op even though
handleSummarizeByClaudeId calls it after enqueueing. The PendingMessageStore
onMutate callback already fires broadcastProcessingStatus on enqueue, but
calling it explicitly from broadcastSummarizeQueued ensures the spinner
ticks on the moment a summary is requested even if the onMutate chain has
any timing race.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(worker): keep spinner on while summary generates

ClaudeProvider's SDK can pull multiple synthetic prompts (e.g.
observation + summarize) before producing responses. Each pull pushed
an ID to session.processingMessageIds. When the SDK's first
observation response came back, ResponseProcessor.confirmProcessed
deleted ALL pending message rows — including the still-in-flight
summary — so getTotalQueueDepth dropped to 0 and the spinner turned
off, even though the summary took another ~22s to actually generate.

Tag each in-flight message with its type ({id, type}) so the response
processor can pop only the FIFO message of the matching type
(observation vs summarize). The summary row stays in 'processing'
until its own response arrives, keeping the spinner lit through the
entire summary window.

Also updates Gemini/OpenRouter providers and GeneratorExitHandler for
the new shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(worker): clear summary from queue on any SDK response

Switch ResponseProcessor from type-aware FIFO matching to strict FIFO
popping (each SDK response → 1 in-flight message consumed). This way
the summary always clears when the SDK responds, even when the
response is unparseable or the summary doesn't actually generate
content — preventing stuck spinner / queue-depth-stuck-at-1.

Spinner behavior is preserved: messages enqueued after the summary
keep the queue depth elevated, and only when the SDK has responded
to every prompt does the queue drain to zero.

Also: when the consumed message is a 'summarize' and parsing fails,
treat it as best-effort and confirmProcessed (no retry) — summaries
that can't be parsed shouldn't keep retrying.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(viewer): redesign welcome card and remove source filters

The first-start welcome card now explains the three feed card types
(observation/summary/prompt) with color-coded badges, points users at
the gear icon for settings and the project dropdown for filtering, and
plugs /mem-search for recall — replacing the old two-line "ask:" prompts.

Source filter tabs (Claude/Codex/etc.) are removed from the header.
Filtering by AI provider was nonsense from a user POV; the project
dropdown is the only header filter now. Source tracking is also
stripped from useSSE, usePagination, App state, and CSS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(viewer): keep welcome card in feed column, swap rows for 3 squares

Two visible problems in the previous design: the card stretched
edge-to-edge while feed cards sit in a centered 650px column, and
the body was a stack of long horizontal rows that scanned line-by-line.

Both fixed: Feed now accepts a pinnedTop slot so the welcome card
renders inside the same .feed-content column as observation cards.
Body is now a 3-column grid of square feature blocks — Live feed,
Tune it, Recall it — each with a custom inline SVG illustration
(stacked cards with color-coded stripes, gear+sliders, magnifier
over cards). Old text-row sections (welcome-card-types,
welcome-card-tips, welcome-card-section, welcome-card-tip-icon)
are removed. Squares stack to one column under 600px.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(viewer): convert welcome card to glassy modal with stylized logo

Card now opens as a centered modal with a frosted/glass backdrop
(blur + saturate) so it doubles as a proper help dialog when reopened
from the header's question-mark button. Removed the observation count,
project count, and "since" date — those don't make sense for a
first-launch surface and felt out of place in a help context.

Header art swapped from the small webp logomark to the new
high-resolution sun/sunburst PNG (claude-mem-logo-stylized.png),
shipped as a checked-in asset in src/ui and plugin/ui.

Bigger throughout: 28px h2, 16px tagline, 88px illustrations,
26px feature padding, 1:1 aspect-ratio squares. Backdrop click and
Esc both close. Mobile collapses the grid to one column and drops
the aspect-ratio constraint.

Reverted the unused pinnedTop slot on Feed.tsx since the welcome
card is now a true overlay rather than an in-feed pinned card.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(viewer): make welcome modal actually glassy

Previous version had a 55%-opacity black backdrop that almost fully
blocked the underlying UI — the "glass" was just a dark plate.

Now the backdrop is fully transparent (no darkening at all), the
panel itself drops to 55% bg-card opacity with its existing
backdrop-filter blur(28px) saturate(170%), and the feature squares
drop to 35% bg-tertiary so they layer as glass-on-glass over the
already-blurred panel. The header and feed below now read clearly
through the modal's frosted blur.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(viewer): bulletproof square features via padding-bottom + clamp() fluid type

Squares were rendering taller than wide because aspect-ratio is treated
as a minimum — content can push the box past 1:1. Switched to the
classic padding-bottom: 100% trick: percentage padding resolves against
the parent's width, so the box is ALWAYS W × W regardless of content.
Inner content sits in an absolutely-positioned flex column that can't
push the shell taller.

Whole modal is now desktop-first and fluid via clamp() — no media-query
stair-steps for type, padding, gaps, border-radius, illustration size,
or modal width. Single mobile breakpoint at <600px collapses the grid
to one column and reverts the padding-bottom trick so each feature can
grow to natural content height.

Tightened the three feature descriptions so they fit comfortably inside
the square at the desktop size.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* style(viewer): 15% black overlay + heavier modal shadow for elevation

Backdrop goes from transparent to rgba(0,0,0,0.15) — just enough
darkening to push the modal visually forward without burying the
underlying UI. Modal shadow stacked: 40px/120px ambient + 16px/48px
contact, both deeper, plus the existing inset 1px highlight.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(build): clear pending_messages queue on build-and-sync

Rewrites scripts/clear-failed-queue.ts to talk directly to SQLite via
bun:sqlite — the previous HTTP endpoints (/api/pending-queue/*) were
removed during the queue engine rewrite, so the script was orphaned.
Wires `npm run queue:clear` into `build-and-sync` so each rebuild
starts with a clean queue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(worker): collapse parser to binary valid/invalid + clearPendingForSession model

- Parser: { valid: true, observations, summary } | { valid: false } — drops kind/skipped enum dispatch
- ResponseProcessor: two branches only (parseable → store + clearPendingForSession; else → no-op)
- Drop processingMessageIds + per-message claim/confirm/markFailed lifecycle across 3 providers
- PendingMessageStore: 226 → 140 lines; remove markFailed/transitionMessagesTo/confirmProcessed/clearFailedOlderThan/getAllPending/peekPendingTypes... wait keep peekPendingTypes
- Schema migration v31+v32: drop retry_count, failed_at_epoch, completed_at_epoch, worker_pid columns
- SessionQueueProcessor: delete two 1s recovery sleeps (let iterator end on error)
- Server.ts/SettingsRoutes.ts: replace four magic-number setTimeout exit-flush patterns with flushResponseThen helper
- GeneratorExitHandler: 183 → 117 lines (drain in-flight loop gone)

Net: -181 lines. No more silent data loss via maxRetries=3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-2255): address review comments batch 1

- install.ts: needsMarketplace true when claude-code selected (P1, was no-op)
- install.ts: throw on invalid --model so CLI exits non-zero
- install.ts: skip worker health checks + adapt next-step copy when --no-auto-start
- install.ts: repair regenerates plugin cache when missing
- index.ts: readFlag rejects missing/flag-shaped values
- index.ts: route flag-first invocations (e.g. `--provider claude`) to install
- banner.ts: fail-open if frame payload decode throws
- SearchRoutes.ts: 5s TTL cache for settings reads on hot hook path (P2)
- detect-error-handling-antipatterns.ts: trailing-brace strip whitespace-tolerant
- investigate-timestamps.ts: compute Dec 2025 epochs at runtime (was Dec 2024)
- regenerate-claude-md.ts: include workingDir in fallback walker so root is covered
- sync-marketplace.cjs: parseWorkerPort validates 1..65535 before http.request
- sync-to-marketplace.sh: resolve SOURCE_DIR from script location, not cwd
- Dockerfile.test-installer: bash --login sources .bashrc via .bash_profile
- docs/configuration.mdx: drop nonexistent .worker.port file refs, use settings.json
- docs/architecture-overview.md: dynamic port + queue model after parser collapse
- docs/architecture/worker-service.mdx: dynamic port example + drop port-file claim
- docs/platform-integration.mdx: WORKER_BASE_URL pattern, drop hardcoded 37777
- install/public/install.sh: Node 20 floor (was 18) to match docs

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-2255): reset claimed messages to pending on early-return paths

ResponseProcessor returns early in two cases:
- parser invalid (unparseable response)
- memorySessionId not yet captured

Both paths previously left the just-claimed message in `status='processing'`,
which counts toward `getPendingCount`. The generator-exit handler then sees
`pendingCount > 0` and respawns the generator, looping until the restart
guard trips and `clearPendingForSession` deletes the message — silent data
loss.

Calling `resetProcessingToPending` on these paths lets the next generator
pass re-claim the message and try again, instead of burning the restart
budget on no-op respawns.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-2255): swebench fallback row + troubleshooting port path

- evals/swebench/run-batch.py: append fallback prediction row when
  orchestrator future raises, preserving "never drop an instance" guarantee
- docs/troubleshooting.mdx: drop nonexistent .worker.port / worker.port file
  references; use settings.json + /api/health for port discovery

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-2255): memoize per-project observation count for welcome-hint hot path

handleContextInject runs on every PostToolUse hook (after every Read/Edit).
The welcome-hint block ran a COUNT(*) on observations for every call once
CLAUDE_MEM_WELCOME_HINT_ENABLED was true. Observation counts are
monotonically increasing — once a project has any observations it always
will — so cache the positive result in a Set and skip the COUNT(*) on
subsequent requests.

Combined with the 5s settings TTL added earlier, the steady-state cost on
the hook hot path drops to a Set lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr-2255): use clearProcessingForSession on AI-success path

clearPendingForSession deletes ALL rows for the session. On the success
path of processAgentResponse, that's wrong: messages that arrived as
'pending' during the (1-5s) AI response latency get deleted along with
the 'processing' row we just consumed. In a hook burst (three quick
PostToolUse hooks), B and C land while A is in flight; A's success then
nukes B and C — silent data loss.

Add a status-scoped clearProcessingForSession to PendingMessageStore +
SessionManager, and use it in ResponseProcessor's success path. The
unconditional clearPendingForSession remains correct in
GeneratorExitHandler for hard-stop / restart-guard-trip paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Revert "fix(pr-2255): use clearProcessingForSession on AI-success path"

This reverts commit a08995299c30cbad36bddc3e5bddda7af8604b35.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:05:56 -07:00
Alex Newman 28b40c05f2 docs: update CHANGELOG.md for v12.4.9 2026-04-29 17:12:25 -07:00
Alex Newman 58a413185c chore: bump version to 12.4.9 2026-04-29 17:09:24 -07:00
Alex Newman 46d204ee9b Integration: 7 critical fixes (post band-aid strip) (#2219)
* fix: strip privacy tags from last_assistant_message in summarize path

(cherry picked from commit bd68bfcc3cfe9d82977d5bdb87cf7e91a7258489)

* fix: preserve Chroma relevance ordering in SQLite hydration

When ChromaSearchStrategy queries by vector similarity with
orderBy='relevance', SessionStore.getObservationsByIds and related
methods silently coerced undefined to 'date_desc', destroying the
semantic ranking. Add 'relevance' as a valid orderBy value that skips
SQL ORDER BY and preserves caller-provided ID order.

Fixes #2153

(cherry picked from commit 9fedf8fc165c01cc3a8a8cdb8c057ea980bf511e)

* test(privacy): mock executeWithWorkerFallback and loadFromFileOnce

Update the cherry-picked privacy-tag stripping test from swithek's fork to
match current main:

- Mock executeWithWorkerFallback / isWorkerFallback (the handler now uses
  these instead of workerHttpRequest directly).
- Mock loadFromFileOnce in hook-settings.js (called by shouldTrackProject)
  so the handler resolves CLAUDE_MEM_EXCLUDED_PROJECTS to a string.
- Switch the workerCallLog shape to record { path, method, body } and
  accept either object or JSON-string bodies.

10/10 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: pass relevance through to SessionStore in ChromaSearchStrategy

The Chroma strategy was coercing orderBy='relevance' to undefined before
calling SessionStore. Combined with SessionStore's date_desc default for
undefined, this destroyed the semantic ranking that Chroma had just
computed. Pair this with the SessionStore-side fix from rogerdigital
(commit 37c8988f) which now accepts 'relevance' as a valid orderBy and
preserves caller-provided ID order.

Adds a regression test asserting that getObservationsByIds returns rows
in caller-provided order when orderBy='relevance', and continues to
return date_desc order when orderBy is omitted.

Closes #2153

Co-Authored-By: Roger Deng <13251150+rogerdigital@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: isolate SDK boundary — settingSources, strictMcpConfig, cloud-provider env, observation cap

Single architectural fix at the three @anthropic-ai/claude-agent-sdk query()
call sites (SDKAgent.startSession, KnowledgeAgent.prime, KnowledgeAgent
.executeQuery) plus the env sanitizer and ingest gate. Closes 6 issues:

- #2155 settings.json bleed-through into observer SDK subprocess: pass
  settingSources: [] so user/project/local settings aren't inherited.
- #2159 / #2171 / #2194 user MCP servers leak into observer SDK: pass
  strictMcpConfig: true alongside the existing mcpServers: {}.
- #2199 Bedrock/Vertex env vars dropped: extend ENV_PRESERVE in
  src/supervisor/env-sanitizer.ts to keep CLAUDE_CODE_USE_BEDROCK,
  CLAUDE_CODE_USE_VERTEX, AWS_*, ANTHROPIC_VERTEX_PROJECT_ID, etc.
- #2201 runaway tokens (345M/day reported): extend default
  CLAUDE_MEM_SKIP_TOOLS with exec_command, write_stdin, apply_patch and
  add a configurable CLAUDE_MEM_MAX_OBSERVATION_BYTES (default 64 KB)
  cap at the ingest gate.

SDK option names verified against
node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts:
  settingSources?: SettingSource[]    (SettingSource = 'user'|'project'|'local')
  strictMcpConfig?: boolean

Anti-pattern guards observed:
- Did not modify the proxy strip (#2099/#2115).
- Did not skip Read/Write/Edit/Bash — those remain the primary
  observation surface; only added high-volume agentic-tool names
  (exec_command, write_stdin, apply_patch).
- Did not invent SDK options.

Closes #2155, #2159, #2171, #2194, #2199, #2201

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: restore Windows spawn fix from PR #751 + add Windows CI

Re-applies the PowerShell Start-Process -WindowStyle Hidden daemon spawn
that PR #751 (e6ae0176) introduced and commit d13662d5 reverted. Also
fixes the bun-runner cmd /c popup, sets detached:false on Windows for
SDK subprocesses (so windowsHide actually works and claude.exe doesn't
outlive the worker), and adds windows-latest CI to prevent regression.

- ProcessManager.spawnDaemon: PowerShell -EncodedCommand branch back.
  Returns 0 sentinel on success — callers MUST use pid === undefined
  for failure detection, never falsy checks.
- bun-runner.js: drop "cmd /c" wrapper. shell:true lets Node resolve
  bun.cmd via PATHEXT and respects windowsHide (the explicit cmd.exe
  wrapper was popping a visible window per hook — #2150, #2186).
- process-registry.ts spawnSdkProcess: detached:false on Windows.
  Mixing detached:true with windowsHide:true is documented-undefined
  on Windows; with detached:false, windowsHide actually hides
  claude.exe and the SDK subprocess dies with the parent (#2190, #2198).
- .github/workflows/windows.yml: smoke test counts visible cmd windows
  before/after spawn + grep guard that the Start-Process branch survives.

WSL bash stdin (#2188) is acknowledged but deferred — the bash → node
pipe boundary needs a real Windows VM to test, beyond this PR's scope.
PTY for Claude CLI SDK mode (#2173, #2177) is also deferred per plan.

Closes #2150, #2169, #2186, #2187, #2190, #2198
Refs #2183 (Windows perf — same root cause)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: Codex transcript ingestion + queue self-deadlock on Windows

Three Windows-specific bugs surfaced by @MakaveliGER in #2192:

A. Glob path normalization
   path.join(homedir(), ...) emits backslashes on Windows. globSync treats
   backslashes as escape characters, not separators, so it silently fails to
   match transcript files. Normalize backslashes to forward slashes before
   passing to globSync (only affects Windows; Unix paths unchanged).

B. Live appends not picked up
   Per-file fs.watch on Windows ReFS/SMB misses appends to live JSONL files;
   the recursive root watcher is the only signal we can trust there. Expose
   FileTailer.poke() and call it from the root-watcher event when the file
   is already tailed, instead of returning early. Also normalize the
   resolved path so the tailer-map key matches what globSync stored.

C. Queue self-deadlock on abort
   When the SDK generator aborts (idle timeout, user cancel, shutdown) with
   rows already claimed and yielded but not yet confirmed by ResponseProcessor,
   those rows sit in 'processing' under THIS worker's PID. The self-healing
   claim predicate skips them because the worker is still alive — the queue
   deadlocks until the worker restarts. In the .finally() block, walk the
   in-flight ids through markFailed so the retry ladder requeues them as
   'pending' (or terminates them if retries are exhausted).

Includes regression test tests/codex-transcript-watcher-windows.test.ts
that asserts each fix at the source level so future refactors can't silently
revert them.

Co-Authored-By: MakaveliGER <noreply@github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Closes #2192

* fix: standalone batch — npm peer-deps overrides, marketplace self-heal warning, cache prune

- Add `overrides: { tree-sitter: ^0.25.0 }` to the generated plugin/package.json
  so `npm install --production` resolves cleanly without --legacy-peer-deps.
  Fixes the ERESOLVE between grammar packages declaring three different majors
  of tree-sitter as peer deps. Closes #2147.

- mcp-server.ts: emit a single loud, actionable warning when MCP boots but the
  marketplace directory at ~/.claude/plugins/marketplaces/<source>/ is missing.
  IDE plugin loaders silently skip claude-mem hooks in this state while MCP
  keeps working — the user has no way to know memory capture is dead. We don't
  run an installer from MCP startup (different permission model), but we tell
  the user exactly which command to run. Closes #2174.

- smart-install.js (both root and plugin variants): prune older claude-mem
  version directories from ~/.claude/plugins/cache/thedotmack/claude-mem/.
  Claude Code resolves and caches hook commands per session, so a stale 12.x
  directory keeps the old hook path alive across restarts even after upgrade.
  Pruning makes the stale path physically unreachable. Closes #2172 (stale
  version reference). Note: the issue's secondary claim that
  @anthropic-ai/claude-agent-sdk is missing from package.json is no longer
  true — it was added at line 115 in v12.4.x.

- #2170 ("ToolUseContext is required for prompt hooks") triaged as upstream:
  the string does not appear anywhere in this repo. The error originates in
  Claude Code's hook framework, which we don't own. No code change here.

Co-Authored-By: Amadan04 <amadan04@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: remove stale macOS binary, regen plugin artifacts (build/bundle drift)

The committed plugin/scripts/claude-mem (63 MB Mach-O) was last built at
v10.3.2 (Feb 2026). It baked in BUILT_IN_VERSION="10.3.1", dev paths
(/Users/alexnewman/Scripts/claude-mem/...), and a now-removed POST
/api/sessions/complete client + handler (deleted by PR #2136).

That meant macOS users running the cached binary hit 404s every time the
SessionEnd hook fired (issue #2200), the /api/health endpoint reported a
two-major-versions-ago version (issue #2158), and the binary embedded a
Zod copy that drifted from the worker bundle (issue #2154).

- Delete plugin/scripts/claude-mem and gitignore it. The npm package
  already excludes it from the "files" allowlist, so no consumer change.
  The JS fallback (bun-runner.js → worker-service.cjs) covers all
  functionality on every platform per the existing
  checkBinaryPlatformCompatibility comment in smart-install.js.
- Add npm run build:cli-binary for users who want the macOS speedup
  back. Produces it on demand from current source — no drift.
- Regenerate plugin/scripts/{worker-service,mcp-server}.cjs and
  plugin/ui/viewer-bundle.js so the shipped artifacts match HEAD.

Closes #2158, #2200, #2154.

* fix(ci): Windows workflow — install without lockfile (project uses Bun)

actions/setup-node@v4 cache: npm requires a package-lock.json and this
project uses Bun (only bunfig.toml exists at root). Drop the cache
directive, switch npm ci to npm install --no-audit --no-fund, and
narrow the build step to npm run build — build-and-sync also runs a
marketplace sync + worker restart that hardcodes ~/.claude/plugins,
which doesn't exist on CI.

* fix: harden observation cap parsing + safe stringify in debug logger

CodeRabbit majors on #2206:
- shared.ts: validate parsed cap is finite and > 0 before use; wrap
  JSON.stringify(payload.toolResponse) in try/catch and skip with
  reason 'payload_unserializable' on circular/throwing payloads, so
  ingestion never crashes on a bad tool response shape.
- logger.ts: the debug-mode JSON dump for objects was unguarded; wrap
  stringify in try/catch and fall back to formatData on cycles. This
  is the source the bundled plugin/scripts/context-generator.cjs is
  built from.

* fix(ci+windows): quote bun-runner shell:true args; replace dynamic smoke with static guards

CodeRabbit majors on #2208:

1. plugin/scripts/bun-runner.js — shell:true with separate spawnArgs
   triggers DEP0190 on Node 22+ and breaks paths/args containing
   spaces. Build a single fully-quoted command string (mirroring
   findBun()'s 'where bun' approach) and pass spawnArgs=[].

2. .github/workflows/windows.yml — the dynamic smoke step that counted
   visible cmd windows around 'claude-mem start' exits 1 on
   'claude-mem is not installed' before exercising the spawn path,
   AND PowerShell try/catch doesn't suppress native exit codes
   regardless. Replace with three static regression guards covering
   the exact patterns PR #2208 protects:
   - PowerShell Start-Process + WindowStyle Hidden in spawnDaemon
   - bun-runner shell:true with empty spawnArgs (DEP0190 guard)
   - windowsHide set on SDK spawn factory (issue #2190)

* fix(2210): cross-platform paths — Windows USERPROFILE + XDG cache symmetry

Greptile P2s on #2210:

- mcp-server.ts checkMarketplaceMarker: switch from process.env.HOME ?? ''
  to os.homedir(). HOME is unset on Windows; the empty fallback resolves
  relative to cwd, silently no-opping the canary on every Windows install.
  Also probe both ~/.claude/ and ~/.config/claude/ for the cache check so
  XDG users get the same warning behavior.

- smart-install.js pruneStaleVersionCache (both root + plugin copies):
  scan both ~/.claude/plugins/cache/thedotmack/ and ~/.config/claude/...
  paths so users on XDG don't keep stale dirs re-triggering #2172.

Greptile's third P2 (mtime vs semver sort for current version) deferred:
mtime works correctly for the common case and the directory names start
with versions that lexicographically sort the same way mtime does for
sequentially-installed versions; semver sort would be a separate change.

Refs PR #2210

* fix(2211): drop hardcoded --target from build:cli-binary

Greptile P2: the npm script was pinned to bun-darwin-arm64, so an
Intel Mac user (or anyone on Linux/Windows running this script
manually) got a cross-compiled arm64 binary that runs only via
Rosetta on x64 macOS and not at all elsewhere.

Bun's --compile defaults to the host platform when --target is
omitted. Drop the flag so the script produces a binary that matches
whoever runs it. CI builds that need a specific target can still
pass --target explicitly.

Refs PR #2211

* ci(windows): drop static-grep tripwires, keep real Windows build

The "Anti-regression" steps grep ProcessManager.ts/bun-runner.js/process-registry.ts
for specific strings (Start-Process, WindowStyle Hidden, shell:true, windowsHide).
Tripwires aren't fixes — they make refactoring harder forever and verify nothing
the actual Windows build doesn't already verify. The npm install + npm run build
on windows-latest is the real guard.

* revert: drop byte cap and skip-list extension band-aids

Strips two band-aid mechanisms from the SDK boundary fix, keeping only
the genuine isolation flags (settingSources: [], strictMcpConfig: true)
and the cloud-provider env preservation.

Removed:
- CLAUDE_MEM_MAX_OBSERVATION_BYTES (default 65536) — dropped oversize
  observations entirely. The structural fix is to chunk/summarize
  oversize tool results, not punish the data flow with an invented
  byte threshold. Tracked separately.
- exec_command, write_stdin, apply_patch added to default skip list —
  static taste decision baked into defaults for everyone. Users can
  still set CLAUDE_MEM_SKIP_TOOLS themselves.

The data flows again. Real fix is a follow-up.

* revert: drop pruneStaleVersionCache walker

Removes the cache walker that scans plugin cache dirs and deletes
"old" version directories by inferred staleness. The structural fix
for #2172 is for the installer to delete the prior version when it
writes the new one — not for a separate walker to wake up later
and guess which directories are stale.

Keeps:
- npm peer-dep override for tree-sitter (#2147)
- Marketplace marker startup probe (#2174)
- Cross-platform path handling

Tracked separately as a follow-up.

* build: regenerate bundled artifacts after merge

Rebuilt plugin/scripts/*.cjs from src after merging #2211, #2204, #2205,
#2208, #2209, #2206 (post-strip), #2210 (post-strip). Conflicts during
merge were resolved by accepting incoming bundled artifacts; this commit
replaces them with a clean rebuild from the merged source.

Verified: 0 references to MAX_OBSERVATION_BYTES, payload_too_large,
or pruneStaleVersionCache in the rebuilt artifacts.

---------

Co-authored-by: swithek <52840391+swithek@users.noreply.github.com>
Co-authored-by: Roger Deng <13251150+rogerdigital@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Amadan04 <amadan04@users.noreply.github.com>
2026-04-29 17:08:04 -07:00
Alex Newman 7e2da10a8e docs: update CHANGELOG.md for v12.4.8 2026-04-27 19:42:03 -07:00
Alex Newman 594becf2f0 chore: bump version to 12.4.8 2026-04-27 19:41:18 -07:00
Alex Newman ff0793f7df fix: coerce stringified numeric anchor in timeline() MCP tool (#2176)
* fix: coerce stringified numeric anchor in timeline() to repair MCP anchor routing

HTTP query params arrive as strings, so the typeof anchor === 'number'
dispatch always missed the observation-ID branch, falling through to
ISO-timestamp parsing and silently returning a wrong-epoch window with
the correct anchor echoed in the header. Closes the timeline regression
reported on cut-guardian.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: extract parseNumericAnchor helper and expand timeline tests

Address CodeRabbit review nitpicks on PR #2176:
- Extract anchor coercion into private parseNumericAnchor helper
- Add whitespace-padded numeric-string anchor test case
- Add explicit numeric-anchor-not-found regression test

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: assert exact ordering and rendered anchor header in timeline tests

Address CodeRabbit nitpick on PR #2176: drop sort to verify chronological
ordering, and assert that the rendered anchor/header text echoes the
requested numeric ID and marks the anchor row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: extract anchor-render helper and tighten garbage-anchor assertion

Address CodeRabbit nitpicks: DRY-up the three repeated anchor header/row
assertions into expectAnchorRendered(), and assert the exact
"Invalid timestamp: 123abc" error in the garbage-anchor branch instead
of a generic non-empty-string check.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 19:40:02 -07:00
Alex Newman 5458dd2322 docs: add openclaw manifest to version-bump skill
Include openclaw/openclaw.plugin.json in the list of manifests the
release workflow must update so its version stays in sync with the
other plugin manifests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-27 03:21:50 -07:00
JOUNGWOOK KWON 5a18f08e5f chore: merge upstream v12.4.7 + keep local fixes
Major upstream changes (v12.3.9 → v12.4.7):

v12.3.9 — Stop hook fire-and-forget (eliminates ~110s terminal block);
  hook port precedence; Telegram notifier; security_alert/security_note
  observation types

v12.4.0/12.4.1 — worker startup streamlined; consolidated DB connections;
  Chroma backfill watermark cache (422% CPU → 0% on restart)

v12.4.2 — context-overflow infinite loop fixed (clears memorySessionId
  on "Prompt is too long"); <task-notification> payload pollution blocked
  at hook + worker boundary

v12.4.3 — one-time pollution cleanup migration (CleanupV12_4_3):
  purges observer-sessions rows + cascade, stuck pending chains, Chroma
  rebuild; auto VACUUM INTO backup. Ran successfully on this DB:
  - 1463 observer-sessions purged
  - 3682 cascade rows
  - 102MB backup at ~/.claude-mem/backups/

v12.4.4 — stop draining queue on /clear (removes SessionEnd shim that
  had been abandoning pending observations for 6 months)

v12.4.5 — fix observation persistence on fresh installs (migration 28
  mirror in SessionStore)

v12.4.7 — cynical-deletion sweep (closes 27 issues); multi-account
  isolation via per-UID worker port (37700 + uid % 100, with explicit
  CLAUDE_MEM_WORKER_PORT override); CLAUDE_MEM_INTERNAL=1 trust boundary
  replaces cwd-based observer-session detection; observations.metadata
  column (migration 30); proxy env vars stripped from spawned subprocs

Local fixes preserved:
- env-sanitizer PATH extension for claude CLI lookup (auto-merged
  cleanly with upstream's new ENV_PROXY_VARS proxy stripping)
- SessionStore stale session reset (mac sleep / 4h wall-clock)

Settings: CLAUDE_MEM_WORKER_PORT=37777 explicit override preserved
through the per-UID port migration. Worker restarted to v12.4.7.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 18:13:22 +09:00
Alex Newman bb1f86949d docs: update CHANGELOG.md for v12.4.7 2026-04-25 21:29:54 -07:00
Alex Newman 2d146b78f2 chore: bump version to 12.4.7 2026-04-25 21:27:09 -07:00
Alex Newman 9dd8d6649e chore: bump version to 12.4.6 2026-04-25 21:24:52 -07:00
Alex Newman d13662d5d8 Cynical deletion: close 27 issues by removing defenders + tolerators (#2141)
* fix: mirror migration 28 in SessionStore so pending_messages.tool_use_id and worker_pid columns are created (#2139)

SessionStore's inline migration list jumped from v27 to v29, skipping
rebuildPendingMessagesForSelfHealingClaim. The worker uses SessionStore
directly via worker/DatabaseManager.ts and bypasses the canonical
MigrationRunner, so fresh installs ended up at "max v29" with neither
column present — every queue claim and observation insert failed.

Adds addPendingMessagesToolUseIdAndWorkerPidColumns following the existing
mirror precedent (addObservationSubagentColumns / addObservationsUniqueContentHashIndex).
Uses ALTER TABLE + column-existence guards so already-broken DBs at v29
self-heal on next worker boot.

Verified on fresh DB and on a synthetic v29-without-v28 broken DB:
both columns and indexes (idx_pending_messages_worker_pid,
ux_pending_session_tool) appear after one boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: wrap v28 mirror dedup+index creation in transaction

Addresses Greptile P2 review on PR #2140: matches the existing pattern in
addObservationsUniqueContentHashIndex (v29 mirror at SessionStore.ts:1127)
and runner.ts rebuildPendingMessagesForSelfHealingClaim. A crash between
the dedup DELETE and the schema_versions INSERT no longer leaves the DB
in a half-applied state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(plan): cynical-deletion plan for 29 open issues

9-phase plan applying delete-first lens to triaged issue corpus.
Headlines: kill defenders (orphan cleanup, EncodedCommand spawn,
restart-port-steal) and tolerators (silent JSON drops, drifted SSE
filters). Each phase closes a named subset of issues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: delete process-management theater (Phase 1: DEL-1 + DEL-2)

Delete aggressiveStartupCleanup, the PowerShell -EncodedCommand
spawn branch, and the restart-with-port-steal sequence. Replace
daemon spawning with a single uniform child_process.spawn path
using arg-array form, keeping setsid on Unix when available.

The defenders (orphan cleanup, duplicate-worker probes, port
stealing) bred more bugs than they fixed. PID file with start-time
token already provides correct OS-trust ownership; restart now
requests httpShutdown, waits 5s for the port to free, then exits 1
if it didn't (user resolves). Net -247 lines.

Closes #2090, #2095 (already fixed at session-init.ts:78), #2107,
#2111, #2114, #2117, #2123, #2097, #2135.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: observer-sessions trust boundary via CLAUDE_MEM_INTERNAL env (Phase 2: DEL-9)

Replace the cwd === OBSERVER_SESSIONS_DIR discriminator (which every
consumer must repeat and inevitably drifts) with a single env-var
trust boundary set once at spawn time in buildIsolatedEnv.

- buildIsolatedEnv now sets CLAUDE_MEM_INTERNAL=1, covering all three
  spawn sites (SDKAgent, KnowledgeAgent.prime, KnowledgeAgent.executeQuery)
- shouldTrackProject checks the env var first (cwd check stays as
  belt-and-braces fallback)
- New shared shouldEmitProjectRow predicate — SSE broadcaster and
  pagination filter share the same predicate so they can never drift
  apart (#2118)
- ObservationBroadcaster filters observer rows from SSE stream
- PaginationHelper hardcoded 'observer-sessions' replaced with
  OBSERVER_SESSIONS_PROJECT const
- project-filter basename match pass — *observer-sessions* now matches
  basename, not just full path (globToRegex's [^/]* can't cross /)
  (#2126 item 1)
- New `claude-mem cleanup [--dry-run]` subcommand wires CleanupV12_4_3
  through to the worker for #2126 item 5

Closes #2118, #2126.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: strip proxy env vars before spawning worker (Phase 4: CON-1)

User's HTTP_PROXY/HTTPS_PROXY config was bleeding into internal AI
calls when claude-mem spawns the claude subprocess, causing
connection failures. Strip unconditionally — no passthrough knob,
which rejects #2099's whitelist proposal.

Closes #2115, #2099.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: fail-fast on silent drops in stdin/file-context/memory-save (Phase 5: FF-1)

Three independent fail-fast fixes:

#2089 — stdin-reader silent drop. Non-empty stdin that fails JSON.parse
now rejects with a clear error instead of resolving undefined. Empty
stdin still resolves undefined.

#2094 — PreToolUse:Read truncation Edit deadlock. file-context handler
no longer returns a fake truncated Read result via updatedInput.
Removes userOffset/userLimit/truncated machinery; injects the timeline
via additionalContext only and lets the real Read pass through. Read
state and Claude's expectation now stay consistent, eliminating the
infinite Edit retry loop.

#2116 — /api/memory/save metadata drop + project bug. Schema accepts
metadata as a documented JSON column (migration 30 adds observations.
metadata TEXT, mirrored in SessionStore). Schema also tightened to
.strict() so unknown top-level fields fail fast instead of being
silently dropped. Project resolution now consults metadata.project as
a fallback before defaultProject.

Closes #2089, #2094, #2116.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: small deletions — Zod externalize / Gemini fallback / session timeout / installCLI alias (Phase 6)

DEL-4 (#2113): Externalize zod from mcp-server.cjs and context-generator.cjs
hook bundles so OpenCode's runtime resolves a single Zod copy. Worker
keeps Zod bundled (it's a daemon subprocess, not in OpenCode's hook
bundle). Added zod to plugin/package.json so externalized requires
resolve at runtime.

DEL-5 (#2087): Delete the never-wired GeminiAgent → Claude fallback.
fallbackAgent was always null in production. On 429 the agent now
throws cleanly (message stays pending for retry). Removed
setFallbackAgent, FallbackAgent interface, and the 429 fallback
branch from both GeminiAgent and OpenRouterAgent. Updated docs
that claimed automatic Claude fallback.

DEL-6 (#2127, #2098): Raise MAX_SESSION_WALL_CLOCK_MS from 4h to
24h. The timeout is a real guard against runaway-cost loops (per
issue #1590), but 4h kills legitimate long Claude Code days. 24h
preserves the guard while never hitting in normal use. No knob —
a session approaching this age is a bug worth investigating, not
a value worth tuning.

DEL-8 (#2054): Delete installCLI() alias function. Saves 4 keystrokes
at the cost of cross-platform shell-config mutation surface — not
worth it. Canonical entry is npx claude-mem (and bunx). Uninstall
now strips legacy alias/function lines from ~/.bashrc, ~/.zshrc,
and the PowerShell profile.

Closes #2087, #2098, #2113, #2127, #2054.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: de-hardcode worker port + multi-account commit (Phase 3: CON-2 + DEL-7)

Replace hardcoded 37777 fallbacks with SettingsDefaultsManager.get(
'CLAUDE_MEM_WORKER_PORT') in npx-cli (runtime/install/uninstall),
opencode-plugin, OpenClaw installer, SearchRoutes example URLs.
Timeline-report SKILL.md now resolves WORKER_PORT from settings.json
at the top and uses ${WORKER_PORT} in all curl invocations.
Remaining 37777 literals are doc comments + viewer build-time form-
field placeholder (which is replaced by /api/settings on mount).

hooks.json: add cygpath POSIX→Windows path translation between _R
resolution and node invocation. No-op on macOS/Linux. Closes the
Windows + Git Bash MODULE_NOT_FOUND in #2109.

CLAUDE.md gains a Multi-account section documenting CLAUDE_MEM_DATA_DIR
+ optional CLAUDE_MEM_WORKER_PORT — every existing path/port code
path now honors them.

Closes #2103, #2109, #2101.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: install/uninstall improvements (Phase 7: #2106)

5 fixes for the install/uninstall flow:

Item 1 — multiselect default. install.ts no longer pre-selects every
detected IDE; user explicitly opts in.

Item 3 — shutdown-before-overwrite. New
src/services/install/shutdown-helper.ts shared by install and
uninstall: POSTs /api/admin/shutdown then polls /api/health until
the worker stops responding. install calls it before
copyPluginToMarketplace so reinstall over a running worker doesn't
conflict; uninstall calls it before deletion.

Item 4 — uninstall path coverage. Removes ~/.npm/_npx/*/node_modules/
claude-mem, ~/.cache/claude-cli-nodejs/*/mcp-logs-plugin-claude-mem-*,
~/.claude/plugins/data/claude-mem-thedotmack/. Best-effort: per-path
try/catch so a single permission failure doesn't abort uninstall.
chroma-mcp shutdown is implicit via the worker's GracefulShutdown
cascade in item 3's helper.

Item 5 — install summary documents "Close all Claude Code sessions
before uninstalling, or ~/.claude-mem will be recreated by active
hooks."

Item 6 — real-port query. After install, fetches /api/health on the
configured port with 3s timeout. Reports actually-bound port if the
response carries it; falls back to requested port. No retry loop.

Closes #2106 (items 1, 3, 4, 5, 6). Items 2, 7 closed separately
as already-fixed and insufficient-detail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: pin chroma-mcp to 0.2.6 (Phase 8: DEL-3 lite)

Replace unpinned 'chroma-mcp' arg with chroma-mcp==0.2.6 in both
local and remote modes. Pinning makes installs deterministic across
machines and across time, eliminating the dependency-drift class
of bugs.

Verified 0.2.6 in a clean uv cache: starts cleanly, no httpcore/
httpx ImportError, no --with flags needed. The --with flags removed
in a0dd516c are not required at this pin (transitive deps resolve
correctly when the top-level version is fixed).

#2102's three protections (transport cleanup on failure, stale onclose
handler guard, 10s reconnect backoff) confirmed intact.

Closes #2046, #2085, #2102.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: update stale assertions for per-UID port + migration 30 (Phase 9)

SettingsDefaultsManager.CLAUDE_MEM_WORKER_PORT default is per-UID
(37700 + uid%100), not literal '37777'. Three assertions in
settings-defaults-manager.test.ts now compute the expected value
the same way the source does.

migration-runner.test.ts: drop expect(versions).toContain(19)
(version 19 was a noop never recorded — pre-existing bug at parent),
add expect(versions).toContain(30) for the new observations.metadata
column added in Phase 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile P1/P2 review comments on PR #2141

P1: spawnDaemon return value was unchecked in worker-service.ts restart
case, so a failed spawn silently exited 0 with a misleading "Worker
restart spawned" log. Now error and exit 1 when restartPid is undefined.

P2: shutdown-helper.ts health-poll catch treated AbortError (timeout)
the same as connection-refused, so a slow worker could be reported
confirmedStopped while still holding file locks. Now distinguish:
AbortError continues polling; other errors return confirmedStopped.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* build: rebuild plugin artifacts after merging main

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit review comments on PR #2141

- hooks.json: quote $HOME in cache lookup so paths with spaces work
- timeline-report SKILL.md: fall back when process.getuid is unavailable (Windows)
- opencode-plugin: validate CLAUDE_MEM_WORKER_PORT before using
- uninstall.ts: only strip alias lines, not function declarations (multi-line bodies left intact)
- MemoryRoutes: trim whitespace-only project before precedence resolution
- SessionStore migration 21: preserve metadata column if observations already has it
- stdin-reader test: restore full property descriptor to avoid cross-test pollution

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:23:24 -07:00
Alex Newman 7f255cbc51 docs: update CHANGELOG.md for v12.4.5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 18:49:04 -07:00
Alex Newman 21bd5c0d37 chore: bump version to 12.4.5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 18:47:18 -07:00
Alex Newman e8082bb992 fix: add missing migration 28 mirror in SessionStore (#2139) (#2140)
* fix: mirror migration 28 in SessionStore so pending_messages.tool_use_id and worker_pid columns are created (#2139)

SessionStore's inline migration list jumped from v27 to v29, skipping
rebuildPendingMessagesForSelfHealingClaim. The worker uses SessionStore
directly via worker/DatabaseManager.ts and bypasses the canonical
MigrationRunner, so fresh installs ended up at "max v29" with neither
column present — every queue claim and observation insert failed.

Adds addPendingMessagesToolUseIdAndWorkerPidColumns following the existing
mirror precedent (addObservationSubagentColumns / addObservationsUniqueContentHashIndex).
Uses ALTER TABLE + column-existence guards so already-broken DBs at v29
self-heal on next worker boot.

Verified on fresh DB and on a synthetic v29-without-v28 broken DB:
both columns and indexes (idx_pending_messages_worker_pid,
ux_pending_session_tool) appear after one boot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: wrap v28 mirror dedup+index creation in transaction

Addresses Greptile P2 review on PR #2140: matches the existing pattern in
addObservationsUniqueContentHashIndex (v29 mirror at SessionStore.ts:1127)
and runner.ts rebuildPendingMessagesForSelfHealingClaim. A crash between
the dedup DELETE and the schema_versions INSERT no longer leaves the DB
in a half-applied state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 18:45:51 -07:00
Alex Newman 3e01b62f72 docs: update CHANGELOG.md for v12.4.4 (#2138)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:14:36 -07:00
Alex Newman af011f2ddb chore: bump version to 12.4.4 (#2137)
PATCH release for the /clear queue-drain fix (PR #2136):
removes the SessionEnd → session-complete shim across all five
integration surfaces so pending observations are no longer abandoned
when users type /clear, logout, or exit.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:11:20 -07:00
Alex Newman 8e0e3ca109 fix: stop draining queue on /clear (remove SessionEnd shim) (#2136)
* fix: stop draining queue on /clear (and on every other SessionEnd)

The SessionEnd hook was wired to session-complete on Claude Code, Gemini
CLI, the transcripts processor, the OpenCode plugin, and OpenClaw. All of
those paths called POST /api/sessions/complete, which marked the session
completed and abandoned every still-pending observation in the queue.

So typing /clear (or logging out, or quitting) wiped in-flight work that
the worker was perfectly happy to keep processing on its own.

Removed the entire shim:
- Deleted SessionEnd hook block in plugin/hooks/hooks.json
- Deleted src/cli/handlers/session-complete.ts and its registry entry
- Deleted POST /api/sessions/complete route + Zod schema in SessionRoutes
- Removed call from transcripts processor handleSessionEnd
- Removed call from opencode-plugin session.deleted handler
- Removed Gemini SessionEnd → session-complete mapping
- Removed openclaw scheduleSessionComplete + completionDelayMs + timer state
- Updated tests + comments accordingly

Explicit user-initiated deletion (DELETE /api/sessions/:id and
POST /api/sessions/:sessionDbId/complete from the viewer UI) still works
via SessionCompletionHandler.completeByDbId — that's the only path that
should drain the queue.

The worker self-completes via its SDK-agent generator's finally-block, so
no external completion call is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: clarify opencode-plugin session.deleted is in-memory cleanup only

Greptile P2: file-level header still implied session.deleted called the
worker. Now it only cleans up the local contentSessionIdsByOpenCodeSessionId
map; worker self-completes via the SDK-agent generator finally-block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 17:08:35 -07:00
Alex Newman 298f5463d9 docs: update CHANGELOG.md for v12.4.3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:33:22 -07:00
Alex Newman 0975ea7d67 chore: rebuild artifacts for v12.4.3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:32:03 -07:00
Alex Newman 703c64c756 v12.4.3: one-time pollution cleanup migration + v12.4.1/v12.4.2 fixes (#2133)
* fix: 5 trivial bugs from v12.4.1 issue triage

- #2092: emit CJS-safe banner (no import.meta.url) in worker-service.cjs
- #2100: PreToolUse Read hook timeout 2000s → 60s
- #2131: add "shell": "bash" to every hook for Windows compat
- #2132: Antigravity dir typo .agent → .agents
- #2088: clear inherited MCP servers in worker SDK query() calls

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: stop context overflow loop + block task-notification leak

- SDKAgent: clear memorySessionId on "prompt is too long" so crash-recovery
  starts a fresh SDK session instead of resuming the same poisoned context
  forever (was producing 68+ failed pending_messages on a single stuck
  session in the wild)
- tag-stripping: new isInternalProtocolPayload() predicate; session-init
  hook + SessionRoutes both skip storage when entire prompt is one of
  Claude Code's autonomous protocol blocks (currently <task-notification>;
  conservative deny-list — does NOT touch <command-name>/<command-message>
  which wrap real user slash-commands)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: bump version to 12.4.2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: update CHANGELOG.md for v12.4.2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(cleanup): one-time v12.4.3 migration purges observer-sessions and stuck pending_messages

Adds CleanupV12_4_3 module that runs once per data dir on worker startup
(after migrations apply, before Chroma backfill). Drops accumulated pollution
that v12.4.0 (observer-sessions filter) and v12.4.2 (context-overflow guard +
task-notification leak block) prevent from recurring:

- DELETE FROM sdk_sessions WHERE project='observer-sessions' (cascades to
  user_prompts, observations, session_summaries via existing FK ON DELETE CASCADE)
- DELETE FROM pending_messages stuck in 'failed'/'processing' for any session
  with >=10 such rows (poisoned chains from the pre-v12.4.2 retry loop;
  threshold spares legitimate transient failures)
- Wipes ~/.claude-mem/chroma and chroma-sync-state.json so backfillAllProjects
  rebuilds the vector store from cleaned SQLite

Pre-flight checks free disk (1.2x DB size + 100MB) via fs.statfsSync; backs up
via VACUUM INTO with copyFileSync fallback; PRAGMA foreign_keys=ON on the
cleanup connection (off by default in bun:sqlite). Marker file
~/.claude-mem/.cleanup-v12.4.3-applied records backup path and counts. Opt-out
via CLAUDE_MEM_SKIP_CLEANUP_V12_4_3=1.

Verified locally: 311MB DB backed up to 277MB in 943ms; 11 observer sessions
+ 3 cascade rows + 141 stuck pending_messages purged; chroma rebuilt via
backfill. Total cleanup time 1.1s.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address PR #2133 code review

- SessionRoutes: check isInternalProtocolPayload before stripping tags
  so internal protocol prompts skip the strip work entirely.
- tag-stripping: bound isInternalProtocolPayload input length to
  256KB to prevent ReDoS-class scans on malformed unclosed tags.
- SDKAgent: extract resetSessionForFreshStart helper; both
  context-overflow paths now share one nullification routine.
- worker-service: drop the per-startup "Checking for one-time
  v12.4.3 cleanup" info log — runs every boot even after marker
  exists; the function already logs at debug/warn when relevant.
- tests: add isInternalProtocolPayload edge cases (whitespace,
  attributes, partial tags, unrelated tags, oversize input).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile P2 comments on PR #2133

CleanupV12_4_3.ts: derive backup directory and restore-hint path from
effectiveDataDir instead of the module-level BACKUPS_DIR/DB_PATH
constants. The dataDirectory override is meant for test isolation;
the prior version still wrote backups to the production directory.

SessionRoutes.ts: move isInternalProtocolPayload guard to the top of
handleSessionInitByClaudeId, before createSDKSession. The previous
position blocked the user_prompts insert but still created an empty
sdk_sessions row, asymmetric with the hook-layer guard in
session-init.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cleanup): retry on disk-skip; survive chroma wipe failure

CodeRabbit Major + Claude review:

- Disk pre-flight skip no longer writes the marker. A user temporarily
  low on disk would otherwise have the cleanup permanently disabled
  even after freeing space. Retry on next startup instead.

- Wrap wipeChromaArtifacts in try/catch and write the marker even on
  failure (with chromaWipeError captured). Without this, an rmSync
  permission failure on chroma/ left writeMarker unreached, so every
  subsequent boot re-ran the SQL purge AND created a fresh backup,
  consuming disk indefinitely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cleanup): close backup handle before copyFileSync fallback

Claude review:

- backupDb is now closed before falling into the copyFileSync fallback.
  On Windows an open SQLite handle holds a file lock that can prevent
  the fallback copy from reading the source. The previous version only
  closed after both branches completed.

- Add empty-body <task-notification></task-notification> case to the
  isInternalProtocolPayload tests for completeness.

Cascade-row count queries already match the actual FK columns
(content_session_id for user_prompts, memory_session_id for
observations / session_summaries) — no fix needed there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cleanup): accurate session count + add migration tests

Claude review v3:

session-init.ts: filter on rawPrompt before the [media prompt]
substitution. Functionally equivalent but explicit — the check no
longer depends on the substitution leaving real protocol payloads
untouched.

CleanupV12_4_3.ts: counts.observerSessions now comes from a pre-DELETE
COUNT(*), not from result.changes. bun:sqlite inflates result.changes
with FTS-trigger and cascade row counts (the user_prompts_fts triggers
inflate a 3-session purge to 19 changes). The previous code logged a
misleading total and wrote it to the marker.

tests/infrastructure/cleanup-v12_4_3.test.ts: happy-path coverage of
the migration against a real on-disk SQLite under a tmpdir. Verifies
observer-session purge with cascades, stuck pending_messages purge,
chroma artifact wipe, marker payload shape, idempotency on re-run, and
CLAUDE_MEM_SKIP_CLEANUP_V12_4_3 opt-out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(protocol-filter): close two-block false positive; address review

CodeRabbit + Claude review v5:

tag-stripping.ts: PROTOCOL_ONLY_REGEX rewritten with a negative-lookahead
body so a prompt like "<task-notification>x</task-notification> hi
<task-notification>y</task-notification>" no longer matches as a single
outer block — the prior greedy [\s\S]* spanned the middle user text and
would have silently dropped a real prompt. Confirmed via probe.

tag-stripping.test.ts: drop the 50ms wall-clock assertion (CI flake);
add the two-block-with-text case as a regression test.

SessionRoutes.ts: filter on req.body.prompt directly, before the
[media prompt] substitution and 256KB truncation. Mirrors the
session-init.ts hook-layer ordering and ensures a protocol payload
that happens to be near the byte limit isn't truncated before the
filter runs.

cleanup-v12_4_3.test.ts: add stuckCount=9 below-threshold case
verifying pending_messages with <10 stuck rows are preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(cleanup): include WAL/SHM in backup fallback; safer rollback

CodeRabbit Major + Claude review v6:

CleanupV12_4_3.ts: when VACUUM INTO fails and copyFileSync runs, also
copy any -wal/-shm sidecars. The DB is configured WAL mode, so recent
committed pages can live in those files; copying only the .db would
miss them. VACUUM INTO already captures everything in one file, so
the happy path is unaffected.

CleanupV12_4_3.ts: wrap ROLLBACK in try/catch so a no-op rollback
(SQLite already rolled back on a constraint failure) cannot shadow
the original purge error.

SDKAgent.ts: align both context-overflow log levels to error. Both
branches are fatal-recovery paths; the previous warn/error split was
inconsistent and made the throw branch easy to miss in logs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: pre-count stuck pending_messages; document adjacent-block fall-through

Claude review v7:

CleanupV12_4_3.ts: runStuckPendingPurge now uses a SELECT COUNT(*)
before the DELETE, matching the pattern in runObserverSessionsPurge.
result.changes is reliable today (no FTS on pending_messages) but the
explicit count protects against future schema additions, and keeps
the two purges symmetric.

tag-stripping.test.ts: add test documenting that adjacent protocol
blocks (no user text between) deliberately fall through to storage.
The deny-list is per-block; concatenations are out of scope.

Skipped per project rules / Node API constraints:
- frsize fallback in disk check: Node/Bun StatFs doesn't expose frsize
- VACUUM-INTO comment: comment-only suggestion
- Overflow string constant extraction: low value

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:30:34 -07:00
Alex Newman a2e174b90f ci: remove auto-review workflow that re-runs on every PR push (#2134)
claude-code-review.yml triggers on pull_request: [opened, synchronize]
and posts a fresh review comment on every push. In practice this
generates 8+ duplicate reviews per PR, hallucinates "missing tests"
that already exist, and adds far more noise than CodeRabbit / Greptile.

Keeps claude.yml in place — it only fires on explicit @claude mentions,
which is the useful path.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:30:28 -07:00
Alex Newman c2d033ce9b docs: update CHANGELOG.md for v12.4.1 2026-04-25 14:07:21 -07:00
Alex Newman 533c317737 chore: bump version to 12.4.1 2026-04-25 14:06:12 -07:00
Alex Newman 5769f00827 perf(chroma): cache backfill watermarks in JSON to skip per-restart Chroma scans
Worker restarts triggered a full Chroma metadata scan for every project on every
boot to figure out which sqlite ids were already embedded. With 253 projects and
~92k embeddings, this pegged chroma-mcp at 100-422% CPU on every spawn.

Replace the scan with ~/.claude-mem/chroma-sync-state.json — per-project highest
synced sqlite_id watermarks for observations/summaries/prompts. Backfill switches
from "id NOT IN (huge list)" to "id > watermark"; live syncs bump the watermark
on success; one-time bootstrap derives initial watermarks from a single Chroma
scan if the state file is missing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:05:48 -07:00
Alex Newman d7c7eccd7f chore: bump version to 12.4.0 2026-04-25 13:40:46 -07:00
Alex Newman 94d592f212 perf: streamline worker startup and consolidate database connections (#2122)
* docs: pathfinder refactor corpus + Node 20 preflight

Adds the PATHFINDER-2026-04-22 principle-driven refactor plan (11 docs,
cross-checked PASS) plus the exploratory PATHFINDER-2026-04-21 corpus
that motivated it. Bumps engines.node to >=20.0.0 per the ingestion-path
plan preflight (recursive fs.watch). Adds the pathfinder skill.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: land PATHFINDER Plan 01 — data integrity

Schema, UNIQUE constraints, self-healing claim, Chroma upsert fallback.

- Phase 1: fresh schema.sql regenerated at post-refactor shape.
- Phase 2: migrations 23+24 — rebuild pending_messages without
  started_processing_at_epoch; UNIQUE(session_id, tool_use_id);
  UNIQUE(memory_session_id, content_hash) on observations; dedup
  duplicate rows before adding indexes.
- Phase 3: claimNextMessage rewritten to self-healing query using
  worker_pid NOT IN live_worker_pids; STALE_PROCESSING_THRESHOLD_MS
  and the 60-s stale-reset block deleted.
- Phase 4: DEDUP_WINDOW_MS and findDuplicateObservation deleted;
  observations.insert now uses ON CONFLICT DO NOTHING.
- Phase 5: failed-message purge block deleted from worker-service
  2-min interval; clearFailedOlderThan method deleted.
- Phase 6: repairMalformedSchema and its Python subprocess repair
  path deleted from Database.ts; SQLite errors now propagate.
- Phase 7: Chroma delete-then-add fallback gated behind
  CHROMA_SYNC_FALLBACK_ON_CONFLICT env flag as bridge until
  Chroma MCP ships native upsert.
- Phase 8: migration 19 no-op block absorbed into fresh schema.sql.

Verification greps all return 0 matches. bun test tests/sqlite/
passes 63/63. bun run build succeeds.

Plan: PATHFINDER-2026-04-22/01-data-integrity.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: land PATHFINDER Plan 02 — process lifecycle

OS process groups replace hand-rolled reapers. Worker runs until
killed; orphans are prevented by detached spawn + kill(-pgid).

- Phase 1: src/services/worker/ProcessRegistry.ts DELETED. The
  canonical registry at src/supervisor/process-registry.ts is the
  sole survivor; SDK spawn site consolidated into it via new
  createSdkSpawnFactory/spawnSdkProcess/getSdkProcessForSession/
  ensureSdkProcessExit/waitForSlot helpers.
- Phase 2: SDK children spawn with detached:true + stdio:
  ['ignore','pipe','pipe']; pgid recorded on ManagedProcessInfo.
- Phase 3: shutdown.ts signalProcess teardown uses
  process.kill(-pgid, signal) on Unix when pgid is recorded;
  Windows path unchanged (tree-kill/taskkill).
- Phase 4: all reaper intervals deleted — startOrphanReaper call,
  staleSessionReaperInterval setInterval (including the co-located
  WAL checkpoint — SQLite's built-in wal_autocheckpoint handles
  WAL growth without an app-level timer), killIdleDaemonChildren,
  killSystemOrphans, reapOrphanedProcesses, reapStaleSessions, and
  detectStaleGenerator. MAX_GENERATOR_IDLE_MS and MAX_SESSION_IDLE_MS
  constants deleted.
- Phase 5: abandonedTimer — already 0 matches; primary-path cleanup
  via generatorPromise.finally() already lives in worker-service
  startSessionProcessor and SessionRoutes ensureGeneratorRunning.
- Phase 6: evictIdlestSession and its evict callback deleted from
  SessionManager. Pool admission gates backpressure upstream.
- Phase 7: SDK-failure fallback — SessionManager has zero matches
  for fallbackAgent/Gemini/OpenRouter. Failures surface to hooks
  via exit code 2 through SessionRoutes error mapping.
- Phase 8: ensureWorkerRunning in worker-utils.ts rewritten to
  lazy-spawn — consults isWorkerPortAlive (which gates
  captureProcessStartToken for PID-reuse safety via commit
  99060bac), then spawns detached with unref(), then
  waitForWorkerPort({ attempts: 3, backoffMs: 250 }) hand-rolled
  exponential backoff 250→500→1000ms. No respawn npm dep.
- Phase 9: idle self-shutdown — zero matches for
  idleCheck/idleTimeout/IDLE_MAX_MS/idleShutdown. Worker exits
  only on external SIGTERM via supervisor signal handlers.

Three test files that exercised deleted code removed:
tests/worker/process-registry.test.ts,
tests/worker/session-lifecycle-guard.test.ts,
tests/services/worker/reap-stale-sessions.test.ts.
Pass count: 1451 → 1407 (-44), all attributable to deleted test
files. Zero new failures. 31 pre-existing failures remain
(schema-repair suite, logger-usage-standards, environmental
openclaw / plugin-distribution) — none introduced by Plan 02.

All 10 verification greps return 0. bun run build succeeds.

Plan: PATHFINDER-2026-04-22/02-process-lifecycle.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: land PATHFINDER Plan 04 (narrowed) — search fail-fast

Phases 3, 5, 6 only. Plan-doc inaccuracies for phases 1/2/4/7/8/9
deferred for plan reconciliation:
  - Phase 1/2: ObservationRow type doesn't exist; the four
    "formatters" operate on three incompatible types.
  - Phase 4: RECENCY_WINDOW_MS already imported from
    SEARCH_CONSTANTS at every call site.
  - Phase 7: getExistingChromaIds is NOT @deprecated and has an
    active caller in ChromaSync.backfillMissingSyncs.
  - Phase 8: estimateTokens already consolidated.
  - Phase 9: knowledge-corpus rewrite blocked on PG-3
    prompt-caching cost smoke test.

Phase 3 — Delete SearchManager.findByConcept/findByFile/findByType.
SearchRoutes handlers (handleSearchByConcept/File/Type) now call
searchManager.getOrchestrator().findByXxx() directly via new
getter accessors on SearchManager. ~250 LoC deleted.

Phase 5 — Fail-fast Chroma. Created
src/services/worker/search/errors.ts with ChromaUnavailableError
extends AppError(503, 'CHROMA_UNAVAILABLE'). Deleted
SearchOrchestrator.executeWithFallback's Chroma-failed
SQLite-fallback branch; runtime Chroma errors now throw 503.
"Path 3" (chromaSync was null at construction — explicit-
uninitialized config) preserved as legitimate empty-result state
per plan text. ChromaSearchStrategy.search no longer wraps in
try/catch — errors propagate.

Phase 6 — Delete HybridSearchStrategy three try/catch silent
fallback blocks (findByConcept, findByType, findByFile) at lines
~82-95, ~120-132, ~161-172. Removed `fellBack` field from
StrategySearchResult type and every return site
(SQLiteSearchStrategy, BaseSearchStrategy.emptyResult,
SearchOrchestrator).

Tests updated (Principle 7 — delete in same PR):
  - search-orchestrator.test.ts: "fall back to SQLite" rewritten
    as "throw ChromaUnavailableError (HTTP 503)".
  - chroma/hybrid/sqlite-search-strategy tests: rewritten to
    rejects.toThrow; removed fellBack assertions.

Verification: SearchManager.findBy → 0; fellBack → 0 in src/.
bun test tests/worker/search/ → 122 pass, 0 fail.
bun test (suite-wide) → 1407 pass, baseline maintained, 0 new
failures. bun run build succeeds.

Plan: PATHFINDER-2026-04-22/04-read-path.md (Phases 3, 5, 6)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: land PATHFINDER Plan 03 — ingestion path

Fail-fast parser, direct in-process ingest, recursive fs.watch,
DB-backed tool pairing. Worker-internal HTTP loopback eliminated.

- Phase 0: Created src/services/worker/http/shared.ts exporting
  ingestObservation/ingestPrompt/ingestSummary as direct
  in-process functions plus ingestEventBus (Node EventEmitter,
  reusing existing pattern — no third event bus introduced).
  setIngestContext wires the SessionManager dependency from
  worker-service constructor.
- Phase 1: src/sdk/parser.ts collapsed to one parseAgentXml
  returning { valid:true; kind: 'observation'|'summary'; data }
  | { valid:false; reason: string }. Inspects root element;
  <skip_summary reason="…"/> is a first-class summary case
  with skipped:true. NEVER returns undefined. NEVER coerces.
- Phase 2: ResponseProcessor calls parseAgentXml exactly once,
  branches on the discriminated union. On invalid → markFailed
  + logger.warn(reason). On observation → ingestObservation.
  On summary → ingestSummary then emit summaryStoredEvent
  { sessionId, messageId } (consumed by Plan 05's blocking
  /api/session/end).
- Phase 3: Deleted consecutiveSummaryFailures field
  (ResponseProcessor + SessionManager + worker-types) and
  MAX_CONSECUTIVE_SUMMARY_FAILURES constant. Circuit-breaker
  guards and "tripped" log lines removed.
- Phase 4: coerceObservationToSummary deleted from sdk/parser.ts.
- Phase 5: src/services/transcripts/watcher.ts rescan setInterval
  replaced with fs.watch(transcriptsRoot, { recursive: true,
  persistent: true }) — Node 20+ recursive mode.
- Phase 6: src/services/transcripts/processor.ts pendingTools
  Map deleted. tool_use rows insert with INSERT OR IGNORE on
  UNIQUE(session_id, tool_use_id) (added by Plan 01). New
  pairToolUsesByJoin query in PendingMessageStore for read-time
  pairing (UNIQUE INDEX provides idempotency; explicit consumer
  not yet wired).
- Phase 7: HTTP loopback at processor.ts:252 replaced with
  direct ingestObservation call. maybeParseJson silent-passthrough
  rewritten to fail-fast (throws on malformed JSON).
- Phase 8: src/utils/tag-stripping.ts countTags + stripTagsInternal
  collapsed into one alternation regex, single-pass over input.
- Phase 9: src/utils/transcript-parser.ts (dead TranscriptParser
  class) deleted. The active extractLastMessage at
  src/shared/transcript-parser.ts:41-144 is the sole survivor.

Tests updated (Principle 7 — same-PR delete):
  - tests/sdk/parser.test.ts + parse-summary.test.ts: rewritten
    to assert discriminated-union shape; coercion-specific
    scenarios collapse into { valid:false } assertions.
  - tests/worker/agents/response-processor.test.ts: circuit-breaker
    describe block skipped; non-XML/empty-response tests assert
    fail-fast markFailed behavior.

Verification: every grep returns 0. transcript-parser.ts deleted.
bun run build succeeds. bun test → 1399 pass / 28 fail / 7 skip
(net -8 pass = the 4 retired circuit-breaker tests + 4 collapsed
parser cases). Zero new failures vs baseline.

Deferred (out of Plan 03 scope, will land in Plan 06): SessionRoutes
HTTP route handlers still call sessionManager.queueObservation
inline rather than the new shared helpers — the helpers are ready,
the route swap is mechanical and belongs with the Zod refactor.

Plan: PATHFINDER-2026-04-22/03-ingestion-path.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: land PATHFINDER Plan 05 — hook surface

Worker-call plumbing collapsed to one helper. Polling replaced by
server-side blocking endpoint. Fail-loud counter surfaces persistent
worker outages via exit code 2.

- Phase 1: plugin/hooks/hooks.json — three 20-iteration `for i in
  1..20; do curl -sf .../health && break; sleep 0.1; done` shell
  retry wrappers deleted. Hook commands invoke their bun entry
  point directly.
- Phase 2: src/shared/worker-utils.ts — added
  executeWithWorkerFallback<T>(url, method, body) returning
  T | { continue: true; reason?: string }. All 8 hook handlers
  (observation, session-init, context, file-context, file-edit,
  summarize, session-complete, user-message) rewritten to use
  it instead of duplicating the ensureWorkerRunning →
  workerHttpRequest → fallback sequence.
- Phase 3: blocking POST /api/session/end in SessionRoutes.ts
  using validateBody + sessionEndSchema (z.object({sessionId})).
  One-shot ingestEventBus.on('summaryStoredEvent') listener,
  30 s timer, req.aborted handler — all share one cleanup so
  the listener cannot leak. summarize.ts polling loop, plus
  MAX_WAIT_FOR_SUMMARY_MS / POLL_INTERVAL_MS constants, deleted.
- Phase 4: src/shared/hook-settings.ts — loadFromFileOnce()
  memoizes SettingsDefaultsManager.loadFromFile per process.
  Per-handler settings reads collapsed.
- Phase 5: src/shared/should-track-project.ts — single exclusion
  check entry; isProjectExcluded no longer referenced from
  src/cli/handlers/.
- Phase 6: cwd validation pushed into adapter normalizeInput
  (all 6 adapters: claude-code, cursor, raw, gemini-cli,
  windsurf). New AdapterRejectedInput error in
  src/cli/adapters/errors.ts. Handler-level isValidCwd checks
  deleted from file-edit.ts and observation.ts. hook-command.ts
  catches AdapterRejectedInput → graceful fallback.
- Phase 7: session-init.ts conditional initAgent guard deleted;
  initAgent is idempotent. tests/hooks/context-reinjection-guard
  test (validated the deleted conditional) deleted in same PR
  per Principle 7.
- Phase 8: fail-loud counter at ~/.claude-mem/state/hook-failures
  .json. Atomic write via .tmp + rename. CLAUDE_MEM_HOOK_FAIL_LOUD
  _THRESHOLD setting (default 3). On consecutive worker-unreachable
  ≥ N: process.exit(2). On success: reset to 0. NOT a retry.
- Phase 9: ensureWorkerAliveOnce() module-scope memoization
  wrapping ensureWorkerRunning. executeWithWorkerFallback calls
  the memoized version.

Minimal validateBody middleware stub at
src/services/worker/http/middleware/validateBody.ts. Plan 06 will
expand with typed inference + error envelope conventions.

Verification: 4/4 grep targets pass. bun run build succeeds.
bun test → 1393 pass / 28 fail / 7 skip; -6 pass attributable
solely to deleted context-reinjection-guard test file. Zero new
failures vs baseline.

Plan: PATHFINDER-2026-04-22/05-hook-surface.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: land PATHFINDER Plan 06 — API surface

One Zod-based validator wrapping every POST/PUT. Rate limiter,
diagnostic endpoints, and shutdown wrappers deleted. Failure-
marking consolidated to one helper.

- Phase 1 (preflight): zod@^3 already installed.
- Phase 2: validateBody middleware confirmed at canonical shape
  in src/services/worker/http/middleware/validateBody.ts —
  safeParse → 400 { error: 'ValidationError', issues: [...] }
  on failure, replaces req.body with parsed value on success.
- Phase 3: Per-route Zod schemas declared at the top of each
  route file. 24 POST endpoints across SessionRoutes,
  CorpusRoutes, DataRoutes, MemoryRoutes, SearchRoutes,
  LogsRoutes, SettingsRoutes now wrap with validateBody().
  /api/session/end (Plan 05) confirmed using same middleware.
- Phase 4: validateRequired() deleted from BaseRouteHandler
  along with every call site. Inline coercion helpers
  (coerceStringArray, coercePositiveInteger) and inline
  if (!req.body...) guards deleted across all route files.
- Phase 5: Rate limiter middleware and its registration deleted
  from src/services/worker/http/middleware.ts. Worker binds
  127.0.0.1:37777 — no untrusted caller.
- Phase 6: viewer.html cached at module init in ViewerRoutes.ts
  via fs.readFileSync; served as Buffer with text/html content
  type. SKILL.md + per-operation .md files cached in
  Server.ts as Map<string, string>; loadInstructionContent
  helper deleted. NO fs.watch, NO TTL — process restart is the
  cache-invalidation event.
- Phase 7: Four diagnostic endpoints deleted from DataRoutes.ts
  — /api/pending-queue (GET), /api/pending-queue/process (POST),
  /api/pending-queue/failed (DELETE), /api/pending-queue/all
  (DELETE). Helper methods that ONLY served them
  (getQueueMessages, getStuckCount, getRecentlyProcessed,
  clearFailed, clearAll) deleted from PendingMessageStore.
  KEPT: /api/processing-status (observability), /health
  (used by ensureWorkerRunning).
- Phase 8: stopSupervisor wrapper deleted from supervisor/index.ts.
  GracefulShutdown now calls getSupervisor().stop() directly.
  Two functions retained with clear roles:
    - performGracefulShutdown — worker-side 6-step shutdown
    - runShutdownCascade — supervisor-side child teardown
      (process.kill(-pgid), Windows tree-kill, PID-file cleanup)
  Each has unique non-trivial logic and a single canonical caller.
- Phase 9: transitionMessagesTo(status, filter) is the sole
  failure-marking path on PendingMessageStore. Old methods
  markSessionMessagesFailed and markAllSessionMessagesAbandoned
  deleted along with all callers (worker-service,
  SessionCompletionHandler, tests/zombie-prevention).

Tests updated (Principle 7 same-PR delete): coercion test files
refactored to chain validateBody → handler. Zombie-prevention
tests rewritten to call transitionMessagesTo.

Verification: all 4 grep targets → 0. bun run build succeeds.
bun test → 1393 pass / 28 fail / 7 skip — exact match to
baseline. Zero new failures.

Plan: PATHFINDER-2026-04-22/06-api-surface.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: land PATHFINDER Plan 07 — dead code sweep

ts-prune-driven sweep across the tree after Plans 01-06 landed.
Deleted unused exports, orphan helpers, and one fully orphaned
file. Earlier-plan deletions verified.

Deleted:
- src/utils/bun-path.ts (entire file — getBunPath, getBunPathOrThrow,
  isBunAvailable: zero importers)
- bun-resolver.getBunVersionString: zero callers
- PendingMessageStore.retryMessage / resetProcessingToPending /
  abortMessage: superseded by transitionMessagesTo (Plan 06 Phase 9)
- EnvManager.MANAGED_CREDENTIAL_KEYS, EnvManager.setCredential:
  zero callers
- CodexCliInstaller.checkCodexCliStatus: zero callers; no status
  command exists in npx-cli
- Two "REMOVED: cleanupOrphanedSessions" stale-fence comments

Kept (with documented justification):
- Public API surface in dist/sdk/* (parseAgentXml, prompt
  builders, ParsedObservation, ParsedSummary, ParseResult,
  SUMMARY_MODE_MARKER) — exported via package.json sdk path.
- generateContext / loadContextConfig / token utilities — used
  via dynamic await import('../../../context-generator.js') in
  worker SearchRoutes.
- MCP_IDE_INSTALLERS, install/uninstall functions for codex/goose
  — used via dynamic await import in npx-cli/install.ts +
  uninstall.ts (ts-prune cannot trace dynamic imports).
- getExistingChromaIds — active caller in
  ChromaSync.backfillMissingSyncs (Plan 04 narrowed scope).
- processPendingQueues / getSessionsWithPendingMessages — active
  orphan-recovery caller in worker-service.ts plus
  zombie-prevention test coverage.
- StoreAndMarkCompleteResult legacy alias — return-type annotation
  in same file.
- All Database.ts barrel re-exports — used downstream.

Earlier-plan verification:
- Plan 03 Phase 9: VERIFIED — src/utils/transcript-parser.ts
  is gone; TranscriptParser has 0 references in src/.
- Plan 01 Phase 8: VERIFIED — migration 19 no-op absorbed.
- SessionStore.ts:52-70 consolidation NOT executed (deferred):
  the methods are not thin wrappers but ~900 LoC of bodies, and
  two methods are documented as intentional mirrors so the
  context-generator.cjs bundle stays schema-consistent without
  pulling MigrationRunner. Deserves its own plan, not a sweep.

Verification: TranscriptParser → 0; transcript-parser.ts → gone;
no commented-out code markers remain. bun run build succeeds.
bun test → 1393 pass / 28 fail / 7 skip — EXACT match to
baseline. Zero regressions.

Plan: PATHFINDER-2026-04-22/07-dead-code.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: remove residual ProcessRegistry comment reference

Plan 07 dead-code sweep missed one comment-level reference to the
deleted in-memory ProcessRegistry class in SessionManager.ts:347.
Rewritten to describe the supervisor.json scope without naming the
deleted class, completing the verification grep target.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile review (P1 + 2× P2)

P1 — Plan 05 Phase 3 blocking endpoint was non-functional:
executeWithWorkerFallback used HEALTH_CHECK_TIMEOUT_MS (3 s) for
the POST /api/session/end call, but the server holds the
connection for SERVER_SIDE_SUMMARY_TIMEOUT_MS (30 s). Client
always raced to a "timed out" rejection that isWorkerUnavailable
classified as worker-unreachable, so the hook silently degraded
instead of waiting for summaryStoredEvent.
  - Added optional timeoutMs to executeWithWorkerFallback,
    forwarded to workerHttpRequest.
  - summarize.ts call site now passes 35_000 (5 s above server
    hold window).

P2 — ingestSummary({ kind: 'parsed' }) branch was dead code:
ResponseProcessor emitted summaryStoredEvent directly via the
event bus, bypassing the centralized helper that the comment
claimed was the single source.
  - ResponseProcessor now calls ingestSummary({ kind: 'parsed',
    sessionDbId, messageId, contentSessionId, parsed }) so the
    event-emission path is single-sourced.
  - ingestSummary's requireContext() resolution moved inside the
    'queue' branch (the only branch that needs sessionManager /
    dbManager). 'parsed' is a pure event-bus emission and
    doesn't need worker-internal context — fixes mocked
    ResponseProcessor unit tests that don't call
    setIngestContext.

P2 — isWorkerFallback could false-positive on legitimate API
responses whose schema includes { continue: true, ... }:
  - Added a Symbol.for('claude-mem/worker-fallback') brand to
    WorkerFallback. isWorkerFallback now checks the brand, not
    a duck-typed property name.

Verification: bun run build succeeds. bun test → 1393 pass /
28 fail / 7 skip — exact baseline match. Zero new failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile iteration 2 (P1 + P2)

P1 — summaryStoredEvent fired regardless of whether the row was
persisted. ResponseProcessor's call to ingestSummary({ kind:
'parsed' }) ran for every parsed.kind === 'summary' even when
result.summaryId came back null (e.g. FK violation, null
memory_session_id at commit). The blocking /api/session/end
endpoint then returned { ok: true } and the Stop hook logged
'Summary stored' for a non-existent row.

  - Gate ingestSummary call on (parsed.data.skipped ||
    session.lastSummaryStored). Skipped summaries are an explicit
    no-op bypass and still confirm; real summaries only confirm
    when storage actually wrote a row.
  - Non-skipped + summaryId === null path logs a warn and lets
    the server-side timeout (504) surface to the hook instead of
    a false ok:true.

P2 — PendingMessageStore.enqueue() returns 0 when INSERT OR
IGNORE suppresses a duplicate (the UNIQUE(session_id, tool_use_id)
constraint added by Plan 01 Phase 1). The two callers
(SessionManager.queueObservation and queueSummarize) previously
logged 'ENQUEUED messageId=0' which read like a row was inserted.

  - Branch on messageId === 0 and emit a 'DUP_SUPPRESSED' debug
    log instead of the misleading ENQUEUED line. No behavior
    change — the duplicate is still correctly suppressed by the
    DB (Principle 3); only the log surface is corrected.
  - confirmProcessed is never called with the enqueue() return
    value (it operates on session.processingMessageIds[] from
    claimNextMessage), so no caller is broken; the visibility
    fix prevents future misuse.

Verification: bun run build succeeds. bun test → 1393 pass /
28 fail / 7 skip — exact baseline match. Zero new failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile iteration 3 (P1 + 2× P2)

- P1 worker-service.ts: wire ensureGeneratorRunning into the ingest
  context after SessionRoutes is constructed. setIngestContext runs
  before routes exist, so transcript-watcher observations queued via
  ingestObservation() had no way to auto-start the SDK generator.
  Added attachIngestGeneratorStarter() to patch the callback in.
- P2 shared.ts: IngestEventBus now sets maxListeners to 0. Concurrent
  /api/session/end calls register one listener each and clean up on
  completion, so the default-10 warning fires spuriously under normal
  load.
- P2 SessionRoutes.ts: handleObservationsByClaudeId now delegates to
  ingestObservation() instead of duplicating skip-tool / meta /
  privacy / queue logic. Single helper, matching the Plan 03 goal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile iteration 4 (P1 tool-pair + P2 parse/path/doc)

- processor.handleToolResult: restore in-memory tool-use→tool-result
  pairing via session.pendingTools for schemas (e.g. Codex) whose
  tool_result events carry only tool_use_id + output. Without this,
  neither handler fired — all tool observations silently dropped.
- processor.maybeParseJson: return raw string on parse failure instead
  of throwing. Previously a single malformed JSON-shaped field caused
  handleLine's outer catch to discard the entire transcript line.
- watcher.deepestNonGlobAncestor: split on / and \\, emit empty string
  for purely-glob inputs so the caller skips the watch instead of
  anchoring fs.watch at the filesystem root. Windows-compatible.
- PendingMessageStore.enqueue: tighten docstring — callers today only
  log on the returned id; the SessionManager branches on id === 0.

* fix: forward tool_use_id through ingestObservation (Greptile iter 5)

P1 — Plan 01's UNIQUE(content_session_id, tool_use_id) dedup never
fired because the new shared ingest path dropped the toolUseId before
queueObservation. SQLite treats NULL values as distinct for UNIQUE,
so every replayed transcript line landed a duplicate row.

- shared.ingestObservation: forward payload.toolUseId to
  queueObservation so INSERT OR IGNORE can actually collapse.
- SessionRoutes.handleObservationsByClaudeId: destructure both
  tool_use_id (HTTP convention) and toolUseId (JS convention) from
  req.body and pass into ingestObservation.
- observationsByClaudeIdSchema: declare both keys explicitly so the
  validator doesn't rely on .passthrough() alone.

* fix: drop dead pairToolUsesByJoin, close session-end listener race

- PendingMessageStore: delete pairToolUsesByJoin. The method was never
  called and its self-join semantics are structurally incompatible
  with UNIQUE(content_session_id, tool_use_id): INSERT OR IGNORE
  collapses any second row with the same pair, so a self-join can
  only ever match a row to itself. In-memory pendingTools in
  processor.ts remains the pairing path for split-event schemas.

- IngestEventBus: retain a short-lived (60s) recentStored map keyed
  by sessionId. Populated on summaryStoredEvent emit, evicted on
  consume or TTL.

- handleSessionEnd: drain the recent-events buffer before attaching
  the listener. Closes the register-after-emit race where the summary
  can persist between the hook's summarize POST and its session/end
  POST — previously that window returned 504 after the 30s timeout.

* chore: merge origin/main into vivacious-teeth

Resolves conflicts with 15 commits on main (v12.3.9, security
observation types, Telegram notifier, PID-reuse worker start-guard).

Conflict resolution strategy:
- plugin/hooks/hooks.json, plugin/scripts/*.cjs, plugin/ui/viewer-bundle.js:
  kept ours — PATHFINDER Plan 05 deletes the for-i-in-1-to-20 curl retry
  loops and the built artifacts regenerate on build.
- src/cli/handlers/summarize.ts: kept ours — Plan 05 blocking
  POST /api/session/end supersedes main's fire-and-forget path.
- src/services/worker-service.ts: kept ours — Plan 05 ingest bus +
  summaryStoredEvent supersedes main's SessionCompletionHandler DI
  refactor + orphan-reaper fallback.
- src/services/worker/http/routes/SessionRoutes.ts: kept ours — same
  reason; generator .finally() Stop-hook self-clean is a guard for a
  path our blocking endpoint removes.
- src/services/worker/http/routes/CorpusRoutes.ts: merged — added
  security_alert / security_note to ALLOWED_CORPUS_TYPES (feature from
  #2084) while preserving our Zod validateBody schema.

Typecheck: 294 errors (vs 298 pre-merge). No new errors introduced; all
remaining are pre-existing (Component-enum gaps, DOM lib for viewer,
bun:sqlite types).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile P2 findings

1) SessionRoutes.handleSessionEnd was the only route handler not wrapped
   in wrapHandler — synchronous exceptions would hang the client rather
   than surfacing as 500s. Wrap it like every other handler.

2) processor.handleToolResult only consumed the session.pendingTools
   entry when the tool_result arrived without a toolName. In the
   split-schema path where tool_result carries both toolName and toolId,
   the entry was never deleted and the map grew for the life of the
   session. Consume the entry whenever toolId is present.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: typing cleanup and viewer tsconfig split for PR feedback

- Add explicit return types for SessionStore query methods
- Exclude src/ui/viewer from root tsconfig, give it its own DOM-typed config
- Add bun to root tsconfig types, plus misc typing tweaks flagged by Greptile
- Rebuilt plugin/scripts/* artifacts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address Greptile P2 findings (iter 2)

- PendingMessageStore.transitionMessagesTo: require sessionDbId (drop
  the unscoped-drain branch that would nuke every pending/processing
  row across all sessions if a future caller omitted the filter).
- IngestEventBus.takeRecentSummaryStored: make idempotent — keep the
  cached event until TTL eviction so a retried Stop hook's second
  /api/session/end returns immediately instead of hanging 30 s.
- TranscriptWatcher fs.watch callback: skip full glob scan for paths
  already tailed (JSONL appends fire on every line; only unknown
  paths warrant a rescan).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: call finalizeSession in terminal session paths (Greptile iter 3)

terminateSession and runFallbackForTerminatedSession previously called
SessionCompletionHandler.finalizeSession before removeSessionImmediate;
the refactor dropped those calls, leaving sdk_sessions.status='active'
for every session killed by wall-clock limit, unrecoverable error, or
exhausted fallback chain. The deleted reapStaleSessions interval was
the only prior backstop.

Re-wires finalizeSession (idempotent: marks completed, drains pending,
broadcasts) into both paths; no reaper reintroduced.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: GC failed pending_messages rows at startup (Greptile iter 4)

Plan 07 deleted clearFailed/clearFailedOlderThan as "dead code", but
with the periodic sweep also removed, nothing reaps status='failed'
rows now — they accumulate indefinitely. Since claimNextMessage's
self-healing subquery scans this table, unbounded growth degrades
claim latency over time.

Re-introduces clearFailedOlderThan and calls it once at worker startup
(not a reaper — one-shot, idempotent). 7-day retention keeps enough
history for operator inspection while bounding the table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: finalize sessions on normal exit; cleanup hoist; share handler (iter 5)

1. startSessionProcessor success branch now calls completionHandler.
   finalizeSession before removeSessionImmediate. Hooks-disabled installs
   (and any Stop hook that fails before POST /api/sessions/complete) no
   longer leave sdk_sessions rows as status='active' forever. Idempotent
   — a subsequent /api/sessions/complete is a no-op.

2. Hoist SessionRoutes.handleSessionEnd cleanup declaration above the
   closures that reference it (TDZ safety; safe at runtime today but
   fragile if timeout ever shrinks).

3. SessionRoutes now receives WorkerService's shared SessionCompletionHandler
   instead of constructing its own — prevents silent divergence if the
   handler ever becomes stateful.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: stop runaway crash-recovery loop on dead sessions

Two distinct bugs were combining to keep a dead session restarting forever:

Bug 1 (uncaught "The operation was aborted."):
  child_process.spawn emits 'error' asynchronously for ENOENT/EACCES/abort
  signal aborts. spawnSdkProcess() never attached an 'error' listener, so
  any async spawn failure became uncaughtException and escaped to the
  daemon-level handler. Attach an 'error' listener immediately after spawn,
  before the !child.pid early-return, so async spawn errors are logged
  (with errno code) and swallowed locally.

Bug 2 (sliding-window limiter never trips on slow restart cadence):
  RestartGuard tripped only when restartTimestamps.length exceeded
  MAX_WINDOWED_RESTARTS (10) within RESTART_WINDOW_MS (60s). With the 8s
  exponential-backoff cap, only ~7-8 restarts fit in the window, so a dead
  session that fail-restart-fail-restart on 8s cycles would loop forever
  (consecutiveRestarts climbing past 30+ in observed logs). Add a
  consecutiveFailures counter that increments on every restart and resets
  only on recordSuccess(). Trip when consecutive failures exceed
  MAX_CONSECUTIVE_FAILURES (5) — meaning 5 restarts with zero successful
  processing in between proves the session is dead. Both guards now run in
  parallel: tight loops still trip the windowed cap; slow loops trip the
  consecutive-failure cap.

Also: when the SessionRoutes path trips the guard, drain pending messages
to 'abandoned' so the session does not reappear in
getSessionsWithPendingMessages and trigger another auto-start cycle. The
worker-service.ts path already does this via terminateSession.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf: streamline worker startup and consolidate database connections

1. Database Pooling: Modified DatabaseManager, SessionStore, and SessionSearch to share a single bun:sqlite connection, eliminating redundant file descriptors.
2. Non-blocking Startup: Refactored WorktreeAdoption and Chroma backfill to run in the background (fire-and-forget), preventing them from stalling core initialization.
3. Diagnostic Routes: Added /api/chroma/status and bypassed the initialization guard for health/readiness endpoints to allow diagnostics during startup.
4. Robust Search: Implemented reliable SQLite FTS5 fallback in SearchManager for when Chroma (uvx) fails or is unavailable.
5. Code Cleanup: Removed redundant loopback MCP checks and mangled initialization logic from WorkerService.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: hard-exclude observer-sessions from hooks; bundle migration 29 (#2124)

* fix: hard-exclude observer-sessions from hooks; backfill bundle migrations

Stop hook + SessionEnd hook were storing the SDK observer's own
init/continuation/summary prompts in user_prompts, leaking into the
viewer (meta-observation regression). 25 such rows accumulated.

- shouldTrackProject: hard-reject OBSERVER_SESSIONS_DIR (and its subtree)
  before consulting user-configured exclusion globs.
- summarize.ts (Stop) and session-complete.ts (SessionEnd): early-return
  when shouldTrackProject(cwd) is false, so the observer's own hooks
  cannot bootstrap the worker or queue a summary against the meta-session.
- SessionRoutes: cap user-prompt body at 256 KiB at the session-init
  boundary so a runaway observer prompt cannot blow up storage.
- SessionStore: add migration 29 (UNIQUE(memory_session_id, content_hash)
  on observations) inline so bundled artifacts (worker-service.cjs,
  context-generator.cjs) stay schema-consistent — without it, the
  ON CONFLICT clause in observation inserts throws.
- spawnSdkProcess: stdio[stdin] from 'ignore' to 'pipe' so the
  supervisor can actually feed the observer's stdin.

Also rebuilds plugin/scripts/{worker-service,context-generator}.cjs.

* fix: walk back to UTF-8 boundary on prompt truncation (Greptile P2)

Plain Buffer.subarray at MAX_USER_PROMPT_BYTES can land mid-codepoint,
which the utf8 decoder silently rewrites to U+FFFD. Walk back over any
continuation bytes (0b10xxxxxx) before decoding so the truncated prompt
ends on a valid sequence boundary instead of a replacement character.

* fix: cross-platform observer-dir containment; clarify SDK stdin pipe

claude-review feedback on PR #2124.

- shouldTrackProject: literal `cwd.startsWith(OBSERVER_SESSIONS_DIR + '/')`
  hard-coded a POSIX separator and missed Windows backslash paths plus any
  trailing-slash variance. Switched to a path.relative-based isWithin()
  helper so Windows hook input under observer-sessions\\... is also excluded.
- spawnSdkProcess: added a comment explaining why stdin must be 'pipe' —
  SpawnedSdkProcess.stdin is typed NonNullable and the Claude Agent SDK
  consumes that pipe; 'ignore' would null it and the null-check below
  would tear the child down on every spawn.

* fix: make Stop hook fire-and-forget; remove dead /api/session/end

The Stop hook was awaiting a 35-second long-poll on /api/session/end,
which the worker held open until the summary-stored event fired (or its
30s server-side timeout elapsed). Followed by another await on
/api/sessions/complete. Three sequential awaits, the middle one a 30s
hold — not fire-and-forget despite repeated requests.

The Stop hook now does ONE thing: POST /api/sessions/summarize to
queue the summary work and return. The worker drives the rest async.
Session-map cleanup is performed by the SessionEnd handler
(session-complete.ts), not duplicated here.

- summarize.ts: drop the /api/session/end long-poll and the trailing
  /api/sessions/complete await; ~40 lines removed; unused
  SessionEndResponse interface gone; header comment rewritten.
- SessionRoutes: delete handleSessionEnd, sessionEndSchema, the
  SERVER_SIDE_SUMMARY_TIMEOUT_MS constant, and the /api/session/end
  route registration. Drop the now-unused ingestEventBus and
  SummaryStoredEvent imports.
- ResponseProcessor + shared.ts + worker-utils.ts: update stale
  comments that referenced the dead endpoint. The IngestEventBus is
  left in place dormant (no listeners) for follow-up cleanup so this
  PR stays focused on the blocker.

Bundle artifact (worker-service.cjs) rebuilt via build-and-sync.

Verification:
- grep '/api/session/end' plugin/scripts/worker-service.cjs → 0
- grep 'timeoutMs:35' plugin/scripts/worker-service.cjs → 0
- Worker restarted clean, /api/health ok at pid 92368

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* deps: bump all dependencies to latest including majors

Upgrades: React 18→19, Express 4→5, Zod 3→4, TypeScript 5→6,
@types/node 20→25, @anthropic-ai/claude-agent-sdk 0.1→0.2,
@clack/prompts 0.9→1.2, plus minors. Adds Daily Maintenance section
to CLAUDE.md mandating latest-version policy across manifests.

Express 5 surfaced a race in Server.listen() where the 'error' handler
was attached after listen() was invoked; refactored to use
http.createServer with both 'error' and 'listening' handlers attached
before listen(), restoring port-conflict rejection semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: surface real chroma errors and add deep status probe

Replace the misleading "Vector search failed - semantic search unavailable.
Install uv... restart the worker." string in SearchManager with the actual
exception text from chroma_query_documents. The lying message blamed `uv`
for any failure — even when the real cause was a chroma-mcp transport
timeout, an empty collection, or a dead subprocess.

Also add /api/chroma/status?deep=1 backed by a new
ChromaMcpManager.probeSemanticSearch() that round-trips a real query
(chroma_list_collections + chroma_query_documents) instead of just
checking the stdio handshake. The cheap default path is unchanged.

Includes the diagnostic plan (PLAN-fix-mcp-search.md) and updated test
fixtures for the new structured failure message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: rebuild worker-service bundle to match merged src

Bundle was stale after the squash merge of #2124 — it still contained
the old "Install uv... semantic search unavailable" string and lacked
probeSemanticSearch. Rebuilt via bun run build-and-sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: address coderabbit feedback on PLAN-fix-mcp-search.md

- replace machine-specific /Users/alexnewman absolute paths with portable
  <repo-root> placeholder (MD-style portability)
- add blank lines around the TypeScript fenced block (MD031)
- tag the bare fenced block with `text` (MD040)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 13:37:40 -07:00
Alex Newman 8ace1d9c84 docs(skill): use npm run changelog:generate in version-bump flow
The project's working changelog regenerator is `scripts/generate-changelog.js`
(not the stdin-based bundled script), exposed via `npm run changelog:generate`.
Prior wording pointed to a broken path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:15:12 -07:00
Alex Newman 984e698ca7 docs: update CHANGELOG.md for v12.3.9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:14:40 -07:00
Alex Newman b85419b220 chore: bump version to 12.3.9
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:11:03 -07:00
Alex Newman a3e0acd19a docs(skill): cover npm publish and all 6 manifest paths in version-bump
The skill previously listed 3 manifest paths and omitted `npm publish`
entirely, which meant `npx claude-mem@X.Y.Z` only resolved when someone
ran publish out-of-band. Now the skill:

- Enumerates all 6 version-bearing files (package.json, plugin/package.json,
  .claude-plugin/marketplace.json, .claude-plugin/plugin.json,
  plugin/.claude-plugin/plugin.json, .codex-plugin/plugin.json).
- Adds an explicit `npm publish` step with `npm view claude-mem@X.Y.Z version`
  verification so the npx-distributed version is the one users actually pin.
- Documents `npm run release:patch|minor|major` (np helper) as an alternative.
- Adds `git grep` pre-flight so new manifests are discovered automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:11:00 -07:00
Alex Newman f2d361b918 feat: security observation types + Telegram notifier (#2084)
* feat: security observation types + Telegram notifier

Adds two severity-axis security observation types (security_alert, security_note)
to the code mode and a fire-and-forget Telegram notifier that posts when a saved
observation matches configured type or concept triggers. Default trigger fires on
security_alert only; notifier is disabled until BOT_TOKEN and CHAT_ID are set.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(telegram): honor CLAUDE_MEM_TELEGRAM_ENABLED master toggle

Adds an explicit on/off flag (default 'true') so users can disable the
notifier without clearing credentials.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf(stop-hook): make summarize handler fire-and-forget

Stop hook previously blocked the Claude Code session for up to 110
seconds while polling the worker for summary completion. The handler
now returns as soon as the enqueue POST is acked.

- summarize.ts: drop the 500ms polling loop and /api/sessions/complete
  call; tighten SUMMARIZE_TIMEOUT_MS from 300s to 5s since the worker
  acks the enqueue synchronously.
- SessionCompletionHandler: extract idempotent finalizeSession() for
  DB mark + orphaned-pending-queue drain + broadcast. completeByDbId
  now delegates so the /api/sessions/complete HTTP route is backward
  compatible.
- SessionRoutes: wire finalizeSession into the SDK-agent generator's
  finally block, gated on lastSummaryStored + empty pending queue so
  only Stop events produce finalize (not every idle tick).
- WorkerService: own the single SessionCompletionHandler instance and
  inject it into SessionRoutes to avoid duplicate construction.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr2084): address reviewer findings

CodeRabbit:
- SessionStore.getSessionById now returns status; without it, the
  finalizeSession idempotency guard always evaluated false and
  re-fired drain/broadcast on every call.
- worker-service.ts: three call sites that remove the in-memory session
  after finalizeSession now do so only on success. On failure the
  session is left in place so the 60s orphan reaper can retry; removing
  it would orphan an 'active' DB row indefinitely under the fire-and-
  forget Stop hook.
- runFallbackForTerminatedSession no longer emits a second
  session_completed event; finalizeSession already broadcasts one.
  The explicit broadcast now runs only on the finalize-failure fallback.

Greptile:
- TelegramNotifier reads via loadFromFile(USER_SETTINGS_PATH) so values
  in ~/.claude-mem/settings.json actually take effect; SettingsDefaultsManager.get()
  alone skipped the file and silently ignored user-configured credentials.
- Emoji is derived from obs.type (security_alert → 🚨, security_note → 🔐,
  fallback 🔔) instead of hardcoded 🚨 for every observation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(hooks): worker-port mismatch on Windows and settings.json overrides (#2086)

Hooks computed the health-check port as \$((37700 + id -u % 100)),
ignoring ~/.claude-mem/settings.json. Two failure modes resulted:

1. Users upgrading from pre-per-uid builds kept CLAUDE_MEM_WORKER_PORT
   set to '37777' in settings.json. The worker bound 37777 (settings
   wins), but hooks queried 37701 (uid 501 on macOS), so every
   SessionStart/UserPromptSubmit health check failed.
2. Windows Git Bash/PowerShell returns a real Windows UID for 'id -u'
   (e.g. 209), producing port 37709 while the Node worker fell back
   to 37777 (process.getuid?.() ?? 77). Every prompt hit the 60s hook
   timeout.

hooks.json now resolves the port in this order, matching how the
worker itself resolves it:
  1. sed CLAUDE_MEM_WORKER_PORT from ~/.claude-mem/settings.json
  2. If absent, and uname is MINGW/CYGWIN/MSYS → 37777
  3. Otherwise 37700 + (id -u || 77) % 100

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr2084): sync DatabaseManager.getSessionById return type

CodeRabbit round 2: the DatabaseManager.getSessionById return type
was missing platform_source, custom_title, and status fields that
SessionStore.getSessionById actually returns. Structural typing
hid the mismatch at compile time, but it prevents callers going
through DatabaseManager from seeing the status field that the
idempotency guard in SessionCompletionHandler relies on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(pr2084): hooks honor env vars and host; looser port regex (#2086 followup)

CodeRabbit round 3: match the worker's env > file > defaults precedence
and resolve host the same way as port.

- Env: CLAUDE_MEM_WORKER_PORT and CLAUDE_MEM_WORKER_HOST win first.
- File: sed now accepts both quoted ('"37777"') and unquoted (37777)
  JSON values for the port; a separate sed reads CLAUDE_MEM_WORKER_HOST.
- Defaults: port per-uid formula (Windows: 37777), host 127.0.0.1.
- Health-check URL uses the resolved $HOST instead of hardcoded localhost.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:08:28 -07:00
JOUNGWOOK KWON e9a37056b6 chore: merge upstream v12.3.8 + keep local fixes
Upstream:
- 12.3.8: detect PID reuse in worker start-guard (#2082) — fixes
  docker container restart where new worker inherits the old PID
  and kill(pid, 0) falsely reports the old instance alive. Uses
  /proc/<pid>/stat starttime on Linux and `ps -p <pid> -o lstart=`
  on macOS/POSIX as an opaque process-start identity token.

Low impact for macOS Desktop users but worth carrying.

Local fixes preserved: env-sanitizer PATH extension, SessionStore
stale session reset. Both verified in built worker-service.cjs.
Worker restarted to v12.3.8.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:20:51 +09:00
Alex Newman 49ab404c08 docs: update CHANGELOG.md for v12.3.8 2026-04-20 19:56:35 -07:00
Alex Newman c0bfbaaf04 chore: bump version to 12.3.8 2026-04-20 19:51:26 -07:00
Alex Newman 99060bac1a fix: detect PID reuse in worker start-guard (container restarts) (#2082)
* fix: detect PID reuse in worker start-guard to survive container restarts

The 'Worker already running' guard checked PID liveness with kill(0), which
false-positives when a persistent PID file outlives the PID namespace (docker
stop / docker start, pm2 graceful reloads). The new worker comes up with the
same low PID (e.g. 11) as the old one, kill(0) says 'alive', and the worker
refuses to start against its own prior incarnation.

Capture a process-start token alongside the PID and verify identity, not just
liveness:
  - Linux: /proc/<pid>/stat field 22 (starttime, jiffies since boot)
  - macOS/POSIX: `ps -p <pid> -o lstart=`
  - Windows: unchanged (returns null, falls back to liveness)

PID files written by older versions are token-less, so verifyPidFileOwnership
falls back to the current liveness-only behavior for backwards compatibility.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: apply review feedback to PID identity helpers

- Collapse ProcessManager re-export down to a single import/export statement.
- Make verifyPidFileOwnership a type predicate (info is PidInfo) so callers
  don't need non-null assertions on the narrowed value.
- Drop the `!` assertions at the worker-service GUARD 1 call site now that
  the predicate narrows.
- Tighten the captureProcessStartToken platform doc comment to enumerate
  process.platform values explicitly.

No behavior change — esbuild output is byte-identical (type-only edits).
Addresses items 1-3 of the claude-review comment on PR #2082.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: pin LC_ALL=C for `ps lstart=` in captureProcessStartToken

Without a locale pin, `ps -o lstart=` emits month/weekday names in the
system locale. A bind-mounted PID file written under one locale and read
under another would hash to different tokens and the live worker would
incorrectly appear stale — reintroducing the very bug this helper exists
to prevent.

Flagged by Greptile on PR #2082.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: address second-round review on PID identity helpers

- verifyPidFileOwnership: log a DEBUG diagnostic when the PID is alive but
  the start-token mismatches. Without it, callers can't distinguish the
  "process dead" path from the "PID reused" path in production logs — the
  exact case this helper exists to catch.
- writePidFile: drop the redundant `?? undefined` coercion. `null` and
  `undefined` are both falsy for the subsequent ternary, so the coercion
  was purely cosmetic noise that suggested an important distinction.
- Add a unit test for the win32 fallback path in captureProcessStartToken
  (mocks process.platform) — previously uncovered in CI.

Addresses items 1, 2, and 5 of the second claude-review on PR #2082.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 19:49:03 -07:00
JOUNGWOOK KWON 4317a097de chore: merge upstream v12.3.7 + keep local fixes
Upstream brings (net of revert cycle):
- 12.3.2: search/DB/worker bug fixes (FTS5 fallback, WAL checkpoint, pending-message purge)
- 12.3.3: "Issue Blowout 2026" — 25 bugs across worker/hooks/security/search (#2080)
- 12.3.4: rollback of 12.3.3 (SessionStart context injection regression)
- 12.3.5: restore 12.3.3 fixes minus bearer auth
- 12.3.6: drop 300-req/min rate limiter (broke viewer polling)
- 12.3.7: drop bearer auth + unused platform_source context filter (#2081)

Net result: FTS5 keyword search fallback, RestartGuard, idle-session eviction,
WAL checkpoint, periodic clearFailed, path-traversal protection, health
endpoint activeSessions, summarize hook try/catch — without bearer auth or
rate limiting (localhost-only, enforced via CORS).

Local fixes preserved through merge:
- env-sanitizer PATH extension for claude CLI lookup
- SessionStore stale session reset (mac sleep / 4h wall-clock)

Built artifacts rebuilt from merged sources; both fixes verified present in
worker-service.cjs. Worker restarted to v12.3.7.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:06:31 +09:00
Alex Newman 9a22acb765 docs: update CHANGELOG.md for v12.3.7 2026-04-20 13:35:07 -07:00
Alex Newman fa4ae3b946 chore: bump version to 12.3.7 2026-04-20 13:32:20 -07:00
Alex Newman 03748acd6a refactor: remove bearer auth and platform_source context filter (#2081)
* fix: resolve search, database, and docker bugs (#1913, #1916, #1956, #1957, #2048)

- Fix concept/concepts param mismatch in SearchManager.normalizeParams (#1916)
- Add FTS5 keyword fallback when ChromaDB is unavailable (#1913, #2048)
- Add periodic WAL checkpoint and journal_size_limit to prevent unbounded WAL growth (#1956)
- Add periodic clearFailed() to purge stale pending_messages (#1957)
- Fix nounset-safe TTY_ARGS expansion in docker/claude-mem/run.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent silent data loss on non-XML responses, add queue info to /health (#1867, #1874)

- ResponseProcessor: mark messages as failed (with retry) instead of confirming
  when the LLM returns non-XML garbage (auth errors, rate limits) (#1874)
- Health endpoint: include activeSessions count for queue liveness monitoring (#1867)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: cache isFts5Available() at construction time

Addresses Greptile review: avoid DDL probe (CREATE + DROP) on every text
query. Result is now cached in _fts5Available at construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve worker stability bugs — pool deadlock, MCP loopback, restart guard (#1868, #1876, #2053)

- Replace flat consecutiveRestarts counter with time-windowed RestartGuard:
  only counts restarts within 60s window (cap=10), decays after 5min of
  success. Prevents stranding pending messages on long-running sessions. (#2053)

- Add idle session eviction to pool slot allocation: when all slots are full,
  evict the idlest session (no pending work, oldest activity) to free a slot
  for new requests, preventing 60s timeout deadlock. (#1868)

- Fix MCP loopback self-check: use process.execPath instead of bare 'node'
  which fails on non-interactive PATH. Fix crash misclassification by removing
  false "Generator exited unexpectedly" error log on normal completion. (#1876)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve hooks reliability bugs — summarize exit code, session-init health wait (#1896, #1901, #1903, #1907)

- Wrap summarize hook's workerHttpRequest in try/catch to prevent exit
  code 2 (blocking error) on network failures or malformed responses.
  Session exit no longer blocks on worker errors. (#1901)

- Add health-check wait loop to UserPromptSubmit session-init command in
  hooks.json. On Linux/WSL where hook ordering fires UserPromptSubmit
  before SessionStart, session-init now waits up to 10s for worker health
  before proceeding. Also wrap session-init HTTP call in try/catch. (#1907)

- Close #1896 as already-fixed: mtime comparison at file-context.ts:255-267
  bypasses truncation when file is newer than latest observation.

- Close #1903 as no-repro: hooks.json correctly declares all hook events.
  Issue was Claude Code 12.0.1/macOS platform event-dispatch bug.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: security hardening — bearer auth, path validation, rate limits, per-user port (#1932, #1933, #1934, #1935, #1936)

- Add bearer token auth to all API endpoints: auto-generated 32-byte
  token stored at ~/.claude-mem/worker-auth-token (mode 0600). All hook,
  MCP, viewer, and OpenCode requests include Authorization header.
  Health/readiness endpoints exempt for polling. (#1932, #1933)

- Add path traversal protection: watch.context.path validated against
  project root and ~/.claude-mem/ before write. Rejects ../../../etc
  style attacks. (#1934)

- Reduce JSON body limit from 50MB to 5MB. Add in-memory rate limiter
  (300 req/min/IP) to prevent abuse. (#1935)

- Derive default worker port from UID (37700 + uid%100) to prevent
  cross-user data leakage on multi-user macOS. Windows falls back to
  37777. Shell hooks use same formula via id -u. (#1936)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve search project filtering and import Chroma sync (#1911, #1912, #1914, #1918)

- Fix per-type search endpoints to pass project filter to Chroma queries
  and SQLite hydration. searchObservations/Sessions/UserPrompts now use
  $or clause matching project + merged_into_project. (#1912)

- Fix timeline/search methods to pass project to Chroma anchor queries.
  Prevents cross-project result leakage when project param omitted. (#1911)

- Sync imported observations to ChromaDB after FTS rebuild. Import
  endpoint now calls chromaSync.syncObservation() for each imported
  row, making them visible to MCP search(). (#1914)

- Fix session-init cwd fallback to match context.ts (process.cwd()).
  Prevents project key mismatch that caused "no previous sessions"
  on fresh sessions. (#1918)

- Fix sync-marketplace restart to include auth token and per-user port.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve all CodeRabbit and Greptile review comments on PR #2080

- Fix run.sh comment mismatch (no-op flag vs empty array)
- Gate session-init on health check success (prevent running when worker unreachable)
- Fix date_desc ordering ignored in FTS session search
- Age-scope failed message purge (1h retention) instead of clearing all
- Anchor RestartGuard decay to real successes (null init, not Date.now())
- Add recordSuccess() calls in ResponseProcessor and completion path
- Prevent caller headers from overriding bearer auth token
- Add lazy cleanup for rate limiter map to prevent unbounded growth
- Bound post-import Chroma sync with concurrency limit of 8
- Add doc_type:'observation' filter to Chroma queries feeding observation hydration
- Add FTS fallback to all specialized search handlers (observations, sessions, prompts, timeline)
- Add response.ok check and error handling in viewer saveSettings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve CodeRabbit round-2 review comments

- Use failure timestamp (COALESCE) instead of created_at_epoch for stale purge
- Downgrade _fts5Available flag when FTS table creation fails
- Escape FTS5 MATCH input by quoting user queries as literal phrases
- Escape LIKE metacharacters (%, _, \) in prompt text search
- Add response.ok check in initial settings load (matches save flow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve CodeRabbit round-3 review comments

- Include failed_at_epoch in COALESCE for age-scoped purge
- Re-throw FTS5 errors so callers can distinguish failure from no-results
- Wrap all FTS fallback calls in SearchManager with try/catch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: remove bearer auth and platform_source from context inject

Bearer token auth (#1932/#1933) added friction for all localhost API
clients with no benefit — the worker already binds localhost-only (CORS
restriction + host binding). Removed auth-token module, requireAuth
middleware, and Authorization headers from all internal callers.

platform_source filtering from the /api/context/inject path was never
used by any caller and silently filtered out observations. The underlying
platform_source column stays; only the query-time filter and its plumbing
through ContextBuilder, ObservationCompiler, SearchRoutes, context.ts,
and transcripts/processor.ts are removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: resolve CodeRabbit + Greptile + claude-review comments on PR #2081

- middleware.ts: drop 'Authorization' from CORS allowedHeaders (Greptile)
- middleware.ts: rate limiter falls back to req.socket.remoteAddress; add Retry-After on 429 (claude-review)
- SearchRoutes.ts: drop leftover platformSource read+pass in handleContextPreview (Greptile)
- .docker-blowout-data/: stop tracking the empty SQLite placeholder and gitignore the dir (claude-review)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: tighten rate limiter — correct boundary + drop dead cleanup branch

- `entry.count >= RATE_LIMIT_MAX_REQUESTS` so the 300th request is the
  first rejected (was 301).
- Removed the `requestCounts.size > 100` lazy-cleanup block — on a
  localhost-only server the map tops out at 1–2 entries, so the branch
  was dead code.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: rate limiter correctly allows exactly 300 req/min; doc localhost scope

- Check `entry.count >= max` BEFORE incrementing so the cap matches the
  comment: 300 requests pass, the 301st gets 429.
- Added a comment noting the limiter is effectively a global cap on a
  localhost-only worker (all callers share the 127.0.0.1/::1 bucket).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: normalise IPv4-mapped IPv6 in rate limiter client IP

Strip the `::ffff:` prefix so a localhost caller routed as
`::ffff:127.0.0.1` shares a bucket with `127.0.0.1`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: size-guarded prune of rate limiter map for non-localhost deploys

Prune expired entries only when the map exceeds 1000 keys and we're
already doing a window reset, so the cost is zero on the localhost hot
path (1–2 keys) and the map can't grow unbounded if the worker is ever
bound on a non-loopback interface.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 13:31:13 -07:00
Alex Newman 282e8e12f8 docs: update CHANGELOG.md for v12.3.6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:36:09 -07:00
Alex Newman 8fd3685d6e chore: bump version to 12.3.6
Removes the 300 req/min rate limiter from the worker's HTTP middleware.
The worker is localhost-only (enforced via CORS), so rate limiting was
pointless security theater — but it broke the viewer, which polls logs
and stats frequently enough to trip the limit within seconds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:35:52 -07:00
Alex Newman 934cc99ad9 docs: update CHANGELOG.md for v12.3.5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:26:43 -07:00
Alex Newman 2f2c03896c chore: bump version to 12.3.5
Restores 25 fixes from v12.3.3 (Issue Blowout 2026) while surgically removing
the bearer-token auth mechanism that broke SessionStart context injection.

Kept hardening: 5MB body limit, 300 req/min rate limiter, path traversal
protection, RestartGuard, WAL checkpoint + journal size limit, FTS5 keyword
fallback, idle session eviction, summarize try/catch, session-init health
wait, pending-messages cleanup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:23:26 -07:00
Alex Newman 8d166b47c1 Revert "revert: roll back v12.3.3 (Issue Blowout 2026)"
This reverts commit bfc7de377a.
2026-04-20 12:18:55 -07:00
Alex Newman b9836d6c2a docs: update CHANGELOG.md for v12.3.4 2026-04-20 12:02:04 -07:00
Alex Newman 7e2f463877 chore: bump version to 12.3.4 2026-04-20 11:59:38 -07:00
Alex Newman bfc7de377a revert: roll back v12.3.3 (Issue Blowout 2026)
SessionStart context injection regressed in v12.3.3 — no memory
context is being delivered to new sessions. Rolling back to the
v12.3.2 tree state while the regression is investigated.

Reverts #2080.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 11:59:15 -07:00
Alex Newman 708a258d39 docs: update CHANGELOG.md for v12.3.3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:46:35 -07:00
Alex Newman 502f2fab02 chore: bump version to 12.3.3
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:43:35 -07:00
Alex Newman ba1ef6c42c fix: Issue Blowout 2026 — 25 bugs across worker, hooks, security, and search (#2080)
* fix: resolve search, database, and docker bugs (#1913, #1916, #1956, #1957, #2048)

- Fix concept/concepts param mismatch in SearchManager.normalizeParams (#1916)
- Add FTS5 keyword fallback when ChromaDB is unavailable (#1913, #2048)
- Add periodic WAL checkpoint and journal_size_limit to prevent unbounded WAL growth (#1956)
- Add periodic clearFailed() to purge stale pending_messages (#1957)
- Fix nounset-safe TTY_ARGS expansion in docker/claude-mem/run.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent silent data loss on non-XML responses, add queue info to /health (#1867, #1874)

- ResponseProcessor: mark messages as failed (with retry) instead of confirming
  when the LLM returns non-XML garbage (auth errors, rate limits) (#1874)
- Health endpoint: include activeSessions count for queue liveness monitoring (#1867)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: cache isFts5Available() at construction time

Addresses Greptile review: avoid DDL probe (CREATE + DROP) on every text
query. Result is now cached in _fts5Available at construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve worker stability bugs — pool deadlock, MCP loopback, restart guard (#1868, #1876, #2053)

- Replace flat consecutiveRestarts counter with time-windowed RestartGuard:
  only counts restarts within 60s window (cap=10), decays after 5min of
  success. Prevents stranding pending messages on long-running sessions. (#2053)

- Add idle session eviction to pool slot allocation: when all slots are full,
  evict the idlest session (no pending work, oldest activity) to free a slot
  for new requests, preventing 60s timeout deadlock. (#1868)

- Fix MCP loopback self-check: use process.execPath instead of bare 'node'
  which fails on non-interactive PATH. Fix crash misclassification by removing
  false "Generator exited unexpectedly" error log on normal completion. (#1876)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve hooks reliability bugs — summarize exit code, session-init health wait (#1896, #1901, #1903, #1907)

- Wrap summarize hook's workerHttpRequest in try/catch to prevent exit
  code 2 (blocking error) on network failures or malformed responses.
  Session exit no longer blocks on worker errors. (#1901)

- Add health-check wait loop to UserPromptSubmit session-init command in
  hooks.json. On Linux/WSL where hook ordering fires UserPromptSubmit
  before SessionStart, session-init now waits up to 10s for worker health
  before proceeding. Also wrap session-init HTTP call in try/catch. (#1907)

- Close #1896 as already-fixed: mtime comparison at file-context.ts:255-267
  bypasses truncation when file is newer than latest observation.

- Close #1903 as no-repro: hooks.json correctly declares all hook events.
  Issue was Claude Code 12.0.1/macOS platform event-dispatch bug.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: security hardening — bearer auth, path validation, rate limits, per-user port (#1932, #1933, #1934, #1935, #1936)

- Add bearer token auth to all API endpoints: auto-generated 32-byte
  token stored at ~/.claude-mem/worker-auth-token (mode 0600). All hook,
  MCP, viewer, and OpenCode requests include Authorization header.
  Health/readiness endpoints exempt for polling. (#1932, #1933)

- Add path traversal protection: watch.context.path validated against
  project root and ~/.claude-mem/ before write. Rejects ../../../etc
  style attacks. (#1934)

- Reduce JSON body limit from 50MB to 5MB. Add in-memory rate limiter
  (300 req/min/IP) to prevent abuse. (#1935)

- Derive default worker port from UID (37700 + uid%100) to prevent
  cross-user data leakage on multi-user macOS. Windows falls back to
  37777. Shell hooks use same formula via id -u. (#1936)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve search project filtering and import Chroma sync (#1911, #1912, #1914, #1918)

- Fix per-type search endpoints to pass project filter to Chroma queries
  and SQLite hydration. searchObservations/Sessions/UserPrompts now use
  $or clause matching project + merged_into_project. (#1912)

- Fix timeline/search methods to pass project to Chroma anchor queries.
  Prevents cross-project result leakage when project param omitted. (#1911)

- Sync imported observations to ChromaDB after FTS rebuild. Import
  endpoint now calls chromaSync.syncObservation() for each imported
  row, making them visible to MCP search(). (#1914)

- Fix session-init cwd fallback to match context.ts (process.cwd()).
  Prevents project key mismatch that caused "no previous sessions"
  on fresh sessions. (#1918)

- Fix sync-marketplace restart to include auth token and per-user port.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve all CodeRabbit and Greptile review comments on PR #2080

- Fix run.sh comment mismatch (no-op flag vs empty array)
- Gate session-init on health check success (prevent running when worker unreachable)
- Fix date_desc ordering ignored in FTS session search
- Age-scope failed message purge (1h retention) instead of clearing all
- Anchor RestartGuard decay to real successes (null init, not Date.now())
- Add recordSuccess() calls in ResponseProcessor and completion path
- Prevent caller headers from overriding bearer auth token
- Add lazy cleanup for rate limiter map to prevent unbounded growth
- Bound post-import Chroma sync with concurrency limit of 8
- Add doc_type:'observation' filter to Chroma queries feeding observation hydration
- Add FTS fallback to all specialized search handlers (observations, sessions, prompts, timeline)
- Add response.ok check and error handling in viewer saveSettings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve CodeRabbit round-2 review comments

- Use failure timestamp (COALESCE) instead of created_at_epoch for stale purge
- Downgrade _fts5Available flag when FTS table creation fails
- Escape FTS5 MATCH input by quoting user queries as literal phrases
- Escape LIKE metacharacters (%, _, \) in prompt text search
- Add response.ok check in initial settings load (matches save flow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: resolve CodeRabbit round-3 review comments

- Include failed_at_epoch in COALESCE for age-scoped purge
- Re-throw FTS5 errors so callers can distinguish failure from no-results
- Wrap all FTS fallback calls in SearchManager with try/catch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 11:42:09 -07:00
Alex Newman 0dda60ad66 docs: update CHANGELOG.md for v12.3.2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 22:24:51 -07:00
Alex Newman 77a2b3a0cb chore: bump version to 12.3.2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 22:20:37 -07:00
Alex Newman be99a5d690 fix: resolve search, database, and docker bugs (#2079)
* fix: resolve search, database, and docker bugs (#1913, #1916, #1956, #1957, #2048)

- Fix concept/concepts param mismatch in SearchManager.normalizeParams (#1916)
- Add FTS5 keyword fallback when ChromaDB is unavailable (#1913, #2048)
- Add periodic WAL checkpoint and journal_size_limit to prevent unbounded WAL growth (#1956)
- Add periodic clearFailed() to purge stale pending_messages (#1957)
- Fix nounset-safe TTY_ARGS expansion in docker/claude-mem/run.sh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: prevent silent data loss on non-XML responses, add queue info to /health (#1867, #1874)

- ResponseProcessor: mark messages as failed (with retry) instead of confirming
  when the LLM returns non-XML garbage (auth errors, rate limits) (#1874)
- Health endpoint: include activeSessions count for queue liveness monitoring (#1867)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: cache isFts5Available() at construction time

Addresses Greptile review: avoid DDL probe (CREATE + DROP) on every text
query. Result is now cached in _fts5Available at construction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 22:19:18 -07:00
JOUNGWOOK KWON 8500c2f6ca chore: merge upstream v12.3.1 + keep local fixes
Upstream brings:
- 12.2.1: Break infinite summary-retry loop (#2072)
- 12.2.2: Subagent observation labeling + schema migration (#2073)
- 12.2.3: Silence parser warning on normal observation responses (#2074)
- 12.3.0: Docker harness + SWE-bench eval harness (#2076)
- 12.3.1: Error handling anti-pattern cleanup across 91 files (#2078)

Local fixes preserved through merge:
- env-sanitizer PATH extension for claude CLI lookup
- SessionStore stale session reset (mac sleep / 4h wall-clock)

Built artifacts rebuilt from merged sources; fixes verified present
in worker-service.cjs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:33:47 +09:00
JOUNGWOOK KWON d6ad6e29f0 fix: prepend claude CLI install paths to subprocess PATH (env-sanitizer)
Root cause: worker launched by Claude Desktop inherits a narrow PATH that
omits ~/.local/bin and ~/.bun/bin, so SDK subprocesses fail with
"Claude executable not found" — observations pile up in the queue but
are never processed, producing the "only my messages get recorded"
symptom that patching session reset logic could not fix.

env-sanitizer now prepends the common install locations (~/.local/bin,
~/.bun/bin, ~/bin, /opt/homebrew/bin, /usr/local/bin on Unix; matching
Windows locations) to PATH before spawning SDK subprocesses, so the
worker can locate the claude binary regardless of launch context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 13:30:23 +09:00
Alex Newman 8b6e61c70b docs: update CHANGELOG.md for v12.3.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:37:48 -07:00
Alex Newman f467763340 chore: bump version to 12.3.1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:35:01 -07:00
Alex Newman 69b702923a Merge pull request #2078 from thedotmack/fix/anti-pattern-cleanup
fix: resolve all 301 error handling anti-patterns
2026-04-19 20:33:41 -07:00
Alex Newman b8360cdee1 fix: restore assistant replies to conversationHistory in OpenRouterAgent
The extracted helper methods (handleInitResponse, processObservationMessage,
processSummaryMessage) lost the conversationHistory.push calls for assistant
replies, breaking multi-turn context for queryOpenRouterMultiTurn.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:20:13 -07:00
Alex Newman d2eb89d27f fix: resolve all CodeRabbit review comments
Critical fixes:
- GeminiCliHooksInstaller: wrap install/uninstall prep in try/catch to maintain
  numeric return contract
- McpIntegrations: move mkdirSync inside try block for Goose installer

Major fixes:
- import-xml-observations: guard invalid dates before toISOString()
- runtime.ts: guard response.json() parsing
- WorktreeAdoption: delay adoptedSqliteIds mutation until SQL succeeds
- CursorHooksInstaller: move mkdirSync inside try block
- McpIntegrations: throw on failed claude-mem block replacement
- OpenClawInstaller: propagate parse failure instead of returning {}
- OpenClawInstaller: move mkdirSync inside try block
- WindsurfHooksInstaller: validate hooks.json shape with optional chaining
- timeline/queries: pass normalized Error directly to logger
- ChromaSync: use composite dedup key (entityType:id) to prevent cross-type collisions
- EnvManager: wrap preflight directory/file ops in try/catch with ENV logging

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:17:57 -07:00
Alex Newman 30a0ab4ddb fix: resolve Greptile review comments on error handling
- Remove spurious console.error in logger JSON.parse catch (expected control flow)
- Remove debug logging from hot PID cleanup loop (approved override)
- Replace unsafe `error as Error` casts with instanceof checks in ChromaSync, GeminiAgent, OpenRouterAgent
- Wrap non-Error FTS failures with new Error(String()) instead of dropping details

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 20:05:27 -07:00
Alex Newman a0dd516cd5 fix: resolve all 301 error handling anti-patterns across codebase
Systematic cleanup of every error handling anti-pattern detected by the
automated scanner. 289 issues fixed via code changes, 12 approved with
specific technical justifications.

Changes across 90 files:
- GENERIC_CATCH (141): Added instanceof Error type discrimination
- LARGE_TRY_BLOCK (82): Extracted helper methods to narrow try scope to ≤10 lines
- NO_LOGGING_IN_CATCH (65): Added logger/console calls for error visibility
- CATCH_AND_CONTINUE_CRITICAL_PATH (10): Added throw/return or approved overrides
- ERROR_STRING_MATCHING (2): Approved with rationale (no typed error classes)
- ERROR_MESSAGE_GUESSING (1): Replaced chained .includes() with documented pattern array
- PROMISE_CATCH_NO_LOGGING (1): Added logging to .catch() handler

Also fixes a detector bug where nested try/catch inside a catch block
corrupted brace-depth tracking, causing false positives.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-19 19:57:00 -07:00
Alex Newman c9adb1c77b docs: add README for docker/claude-mem harness
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:45:35 -07:00
Alex Newman 0d9339d9ac docs: update CHANGELOG.md for v12.3.0 2026-04-19 17:36:29 -07:00
Alex Newman fc10491eae chore: bump version to 12.3.0 2026-04-19 17:35:47 -07:00
Alex Newman 97c7c999b1 feat: basic claude-mem Docker container for easy spin-up (#2076)
* feat(evals): SWE-bench Docker scaffolding for claude-mem resolve-rate measurement

Adds evals/swebench/ scaffolding per .claude/plans/swebench-claude-mem-docker.md.
Agent image builds Claude Code 2.1.114 + locally-built claude-mem plugin;
run-instance.sh executes the two-turn ingest/fix protocol per instance;
run-batch.py orchestrates parallel Docker runs with per-instance isolation;
eval.sh wraps the upstream SWE-bench harness; summarize.py aggregates reports.

Orchestrator owns JSONL writes under a lock to avoid racy concurrent appends;
agent writes its authoritative diff to CLAUDE_MEM_OUTPUT_DIR (/scratch in
container mode) and the orchestrator reads it back. Scaffolding only — no
Docker build or smoke test run yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(evals): OAuth credential mounting for Claude Max/Pro subscriptions

Skips per-call API billing by extracting OAuth creds from host Keychain
(macOS) or ~/.claude/.credentials.json (Linux) and bind-mounting them
read-only into each agent container. Creds are copied into HOME=$SCRATCH/.claude
at container start so the per-instance isolation model still holds.

Adds run-batch.py --auth {oauth,api-key,auto} (auto prefers OAuth, falls
back to API key). run-instance.sh accepts either ANTHROPIC_API_KEY or
CLAUDE_MEM_CREDENTIALS_FILE. smoke-test.sh runs one instance end-to-end
using OAuth for quick verification before batch runs.

Caveat surfaced in docstrings: Max/Pro has per-window usage limits and is
framed for individual developer use — batch evaluation may exhaust the
quota or raise compliance questions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(docker): basic claude-mem container for ad-hoc testing

Adds docker/claude-mem/ with a fresh spin-up image:
- Dockerfile: FROM node:20 (reproduces anthropics/claude-code .devcontainer
  pattern — Anthropic ships the Dockerfile, not a pullable image); layers
  Bun + uv + locally-built plugin/; runs as non-root node user
- entrypoint.sh: seeds OAuth creds from CLAUDE_MEM_CREDENTIALS_FILE into
  $HOME/.claude/.credentials.json, then exec's the command (default: bash)
- build.sh: npm run build + docker build
- run.sh: interactive launcher; auto-extracts OAuth from macOS Keychain
  (security find-generic-password) or ~/.claude/.credentials.json on Linux,
  mounts host .docker-claude-mem-data/ at /home/node/.claude-mem so the
  observations DB survives container exit

Validated end-to-end: PostToolUse hook fires, queue enqueues, worker's SDK
compression runs under subscription OAuth, observations row lands with
populated facts/concepts/files_read, Chroma sync triggers.

Also updates .gitignore/.dockerignore for the new runtime-output paths.
Built plugin artifacts refreshed by the build step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(evals/swebench): non-root user, OAuth mount, Lite dataset default

- Dockerfile.agent: switch to non-root \`node\` user (uid 1000); Claude Code
  refuses --permission-mode bypassPermissions when euid==0, which made every
  agent run exit 1 before producing a diff. Also move Bun + uv installs to
  system paths so the non-root user can exec them.
- run-batch.py: add extract_oauth_credentials() that pulls from macOS
  Keychain / Linux ~/.claude/.credentials.json into a temp file and bind-
  mounts it at /auth/.credentials.json:ro with CLAUDE_MEM_CREDENTIALS_FILE.
  New --auth {oauth,api-key,auto} flag. New --dataset flag so the batch can
  target SWE-bench_Lite without editing the script.
- smoke-test.sh: default DATASET to princeton-nlp/SWE-bench_Lite (Lite
  contains sympy__sympy-24152, Verified does not); accept DATASET env
  override.

Caveat surfaced during testing: Max/Pro subscriptions have per-window usage
limits; running 5 instances in parallel with the "read every source file"
ingest prompt exhausted the 5h window within ~25 minutes (3/5 hit HTTP 429).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address PR #2076 review comments

- docker/claude-mem/run.sh: chmod 600 (not 644) on extracted OAuth creds
  to match what `claude login` writes; avoids exposing tokens to other
  host users. Verified readable inside the container under Docker
  Desktop's UID translation.
- docker/claude-mem/Dockerfile: pin Bun + uv via --build-arg BUN_VERSION
  / UV_VERSION (defaults: 1.3.12, 0.11.7). Bun via `bash -s "bun-v<V>"`;
  uv via versioned installer URL `https://astral.sh/uv/<V>/install.sh`.
- evals/swebench/smoke-test.sh: pipe JSON through stdin to `python3 -c`
  so paths with spaces/special chars can't break shell interpolation.
- evals/swebench/run-batch.py: add --overwrite flag; abort by default
  when predictions.jsonl for the run-id already exists, preventing
  accidental silent discard of partial results.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address coderabbit review on PR #2076

Actionable (4):
- Dockerfile uv install: wrap `chmod ... || true` in braces so the trailing
  `|| true` no longer masks failures from `curl|sh` via bash operator
  precedence (&& binds tighter than ||). Applied to both docker/claude-mem/
  and evals/swebench/Dockerfile.agent. Added `set -eux` to the RUN lines.
- docker/claude-mem/Dockerfile: drop unused `sudo` apt package (~2 MB).
- run-batch.py: name each agent container (`swebench-agent-<id>-<pid>-<tid>`)
  and force-remove via `docker rm -f <name>` in the TimeoutExpired handler
  so timed-out runs don't leave orphan containers.

Nitpicks (2):
- smoke-test.sh: collapse 3 python3 invocations into 1 — parse the instance
  JSON once, print `repo base_commit`, and write problem.txt in the same
  call.
- run-instance.sh: shallow clone via `--depth 1 --no-single-branch` +
  `fetch --depth 1 origin $BASE_COMMIT`. Falls back to a full clone if the
  server rejects the by-commit fetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address second coderabbit review on PR #2076

Actionable (3):
- docker/claude-mem/run.sh: on macOS, fall back to ~/.claude/.credentials.json
  when the Keychain lookup misses (some setups still have file-only creds).
  Unified into a single creds_obtained gate so the error surface lists both
  sources tried.
- docker/claude-mem/run.sh: drop `exec docker run` — `exec` replaces the shell
  so the EXIT trap (`rm -f "$CREDS_FILE"`) never fires and the extracted
  OAuth JSON leaks to disk until tmpfs cleanup. Run as a child instead so
  the trap runs on exit.
- evals/swebench/smoke-test.sh: actually enforce the TIMEOUT env var. Pick
  `timeout` or `gtimeout` (coreutils on macOS), fall back to uncapped with
  a warning. Name the container so exit-124 from timeout can `docker rm -f`
  it deterministically.

Nitpick from the same review (consolidated python3 calls in smoke-test.sh)
was already addressed in the prior commit ef621e00.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address third coderabbit review on PR #2076

Actionable (1):
- evals/swebench/smoke-test.sh: the consolidated python heredoc had competing
  stdin redirections — `<<'PY'` (script body) AND `< "$INSTANCE_JSON"` (data).
  The heredoc won, so `json.load(sys.stdin)` saw an empty stream and the parse
  would have failed at runtime. Pass INSTANCE_JSON as argv[2] and `open()` it
  inside the script instead; the heredoc is now only the script body, which
  is what `python3 -` needs.

Nitpicks (2):
- evals/swebench/smoke-test.sh: macOS Keychain lookup now falls through to
  ~/.claude/.credentials.json on miss (matches docker/claude-mem/run.sh).
- evals/swebench/run-batch.py: extract_oauth_credentials() no longer
  early-returns on Darwin keychain miss; falls through to the on-disk creds
  file so macOS setups with file-only credentials work in batch mode too.

Functional spot-check of the parse fix confirmed: REPO/BASE_COMMIT populated
and problem.txt written from a synthetic INSTANCE_JSON.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 17:34:30 -07:00
Alex Newman de6139660b chore: gitignore runtime state files (#2075)
* chore: gitignore runtime state files

.claude/scheduled_tasks.lock is a PID+sessionId lock written by Claude
Code's cron scheduler every session. It got accidentally checked in during
the v12.0.0 bump and has been churning phantom diffs in every PR since.
Untrack it and ignore.

plugin/.cli-installed is a timestamp marker the claude-mem installer drops
to record when the plugin was installed. Never belonged in version control.
Ignore it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: add trailing newline to .gitignore

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:50:54 -07:00
Alex Newman dc906e5c00 docs: update CHANGELOG.md for v12.2.3 2026-04-19 16:35:01 -07:00
Alex Newman bbfb65668f chore: bump version to 12.2.3 2026-04-19 16:30:52 -07:00
Alex Newman 2337997c48 fix(parser): stop warning on normal observation responses (#2074)
parseSummary runs on every agent response, not just summary turns. When the
turn is a normal observation, the LLM correctly emits <observation> and no
<summary> — but the fallthrough branch from #1345 treated this as prompt
misbehavior and logged "prompt conditioning may need strengthening" every
time. That assumption stopped holding after #1633 refactored the caller to
always invoke parseSummary with a coerceFromObservation flag.

Gate the whole observation-on-summary path on coerceFromObservation. On a
real summary turn, coercion still runs and logs the legitimate "coercion
failed" warning when the response has no usable content. On an observation
turn, parseSummary returns null silently, which is the correct behavior.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:30:05 -07:00
Alex Newman e6e5751ef5 docs: update CHANGELOG.md for v12.2.2 2026-04-19 15:03:41 -07:00
Alex Newman b1dfec0f43 chore: bump version to 12.2.2 2026-04-19 14:58:55 -07:00
Alex Newman 789efe4234 feat: disable subagent summaries, label subagent observations (#2073)
* feat: disable subagent summaries and label subagent observations

Detect Claude Code subagent hook context via `agent_id`/`agent_type` on
stdin, short-circuit the Stop-hook summary path when present, and thread
the subagent identity end-to-end onto observation rows (new `agent_type`
and `agent_id` columns, migration 010 at version 27). Main-session rows
remain NULL; content-hash dedup is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address PR #2073 review feedback

- Narrow summarize subagent guard to agentId only so --agent-started
  main sessions still own their summary (agentType alone is main-session).
- Remove now-dead agentId/agentType spreads from the summarize POST body.
- Always overwrite pendingAgentId/pendingAgentType in SDK/Gemini/OpenRouter
  agents (clears stale subagent identity on main-session messages after
  a subagent message in the same batch).
- Add idx_observations_agent_id index in migration 010 + the mirror
  migration in SessionStore + the runner.
- Replace console.log in migration010 with logger.debug.
- Update summarize test: agentType alone no longer short-circuits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: address CodeRabbit + claude-review iteration 4 feedback

- SessionRoutes.handleSummarizeByClaudeId: narrow worker-side guard to
  agentId only (matches hook-side). agentType alone = --agent main
  session, which still owns its summary.
- ResponseProcessor: wrap storeObservations in try/finally so
  pendingAgentId/Type clear even if storage throws. Prevents stale
  subagent identity from leaking into the next batch on error.
- SessionStore.importObservation + bulk.importObservation: persist
  agent_type/agent_id so backup/import round-trips preserve subagent
  attribution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* polish: claude-review iteration 5 cleanup

- Use ?? not || for nullable subagent fields in PendingMessageStore
  (prevents treating empty string as null).
- Simplify observation.ts body spread — include fields unconditionally;
  JSON.stringify drops undefined anyway.
- Narrow any[] to Array<{ name: string }> in migration010 column checks.
- Add trailing newline to migrations.ts.
- Document in observations/store.ts why the dedup hash intentionally
  excludes agent fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* polish: claude-review iteration 7 feedback

- claude-code adapter: add 128-char safety cap on agent_id/agent_type
  so a malformed Claude Code payload cannot balloon DB rows. Empty
  strings now also treated as absent.
- migration010: state-aware debug log lists only columns actually
  added; idempotent re-runs log "already present; ensured indexes".
- Add 3 adapter tests covering the length cap boundary and empty-string
  rejection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* perf: skip subagent summary before worker bootstrap

Move the agentId short-circuit above ensureWorkerRunning() so a Stop
hook fired inside a subagent does not trigger worker startup just to
return early. Addresses CodeRabbit nit on summarize.ts:36-47.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:58:01 -07:00
Alex Newman 306a0b1de9 docs: update CHANGELOG.md for v12.2.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:02:16 -07:00
Alex Newman be730a09ac chore: bump version to 12.2.1
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:01:43 -07:00
Copilot 8ec91e7ffa fix: break infinite summary-retry loop (#1633) (#2072)
* Initial plan

* fix: break infinite summary-retry loop (#1633)

Three-part fix:
1. Parser coercion: When LLM returns <observation> tags instead of <summary>,
   coerce observation content into summary fields (root cause fix)
2. Stronger summary prompt: Add clearer tag requirements with warnings
3. Circuit breaker: Track consecutive summary failures per session,
   skip further attempts after 3 failures to prevent unbounded prompt growth

Agent-Logs-Url: https://github.com/thedotmack/claude-mem/sessions/e345e8ec-bc97-4eaa-94bd-6e951fda8f77

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* refactor: extract shared constants for summary mode marker and failure threshold

Addresses code review feedback: SUMMARY_MODE_MARKER and
MAX_CONSECUTIVE_SUMMARY_FAILURES are now defined once in sdk/prompts.ts
and imported by ResponseProcessor and SessionManager.

Agent-Logs-Url: https://github.com/thedotmack/claude-mem/sessions/e345e8ec-bc97-4eaa-94bd-6e951fda8f77

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* fix: guard summary failure counter on summaryExpected (Greptile P1)

The circuit breaker counter previously incremented on any response
containing <observation> or <summary> tags — which matches virtually
every normal observation response. After 3 observations the breaker
would open and permanently block summarization, reproducing the
data-loss scenario #1633 was meant to prevent.

Gate the increment block on summaryExpected (already computed for
parseSummary coercion) so the counter only tracks actual summary
attempts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: cover circuit-breaker + apply review polish

- Use findLast / at(-1) for last-user-message lookup instead of
  filter + index (O(1) common case).
- Drop redundant `|| 0` fallback — field is required and initialized.
- Add comment noting counter is ephemeral by design.
- Add ResponseProcessor tests covering:
  * counter NOT incrementing on normal observation responses
    (regression guard for the Greptile P1)
  * counter incrementing when a summary was expected but missing
  * counter resetting to 0 on successful summary storage

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: iterate all observation blocks; don't count skip_summary as failure

Addresses CodeRabbit review on #2072:

- coerceObservationToSummary now iterates all <observation> blocks
  with a global regex and returns the first block that has title,
  narrative, or facts. Previously, an empty leading observation
  would short-circuit and discard populated follow-ups.

- Circuit-breaker counter now treats explicit <skip_summary/> as
  neutral — neither a failure nor a success — so a run that happens
  to end on a skip doesn't punish the session or mask a prior bad
  streak. Real failures (no summary, no skip) still increment.

- Tests added for both cases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: reference SUMMARY_MODE_MARKER constant instead of hardcoded string

Addresses CodeRabbit nitpick: tests should pull the marker from the
canonical source so they don't silently drift when the constant is
renamed or edited.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: also coerce observations when <summary> has empty sub-tags

When the LLM wraps an empty <summary></summary> around real observation
content, the #1360 empty-subtag guard rejects the summary and returns
null — which would lose the observation content and resurrect the
#1633 retry loop. Fall back to coerceObservationToSummary in that
branch too, mirroring the unmatched-<summary> path.

Adds a test covering the empty-summary-wraps-observation case and
a guard test for empty summary with no observation content.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
Co-authored-by: Alex Newman <thedotmack@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:00:38 -07:00
JOUNGWOOK KWON 70a537c3a7 build: rebuild plugin artifacts for v12.2.0 with session reset fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 18:01:52 +09:00
JOUNGWOOK KWON daa937270c fix: apply session reset fix to SessionStore.createSDKSession (correct location)
Previous fix was applied to sessions/create.ts which is unused.
The actual method called by the worker is SessionStore.createSDKSession
in src/services/sqlite/SessionStore.ts.

Now resets started_at_epoch when session is completed or older than
the 4-hour wall-clock limit, preventing age limit blocks after mac
sleep/resume without proper SessionEnd.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 16:00:31 +09:00
JOUNGWOOK KWON e88e1a0ceb chore: merge upstream v12.2.0 + keep local session reset fixes
Local fixes retained:
- src/services/sqlite/sessions/create.ts: reset stale/completed sessions on resume
- src/services/worker/ProcessRegistry.ts: upstream v12.1.6 approach already adopted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 15:57:55 +09:00
JOUNGWOOK KWON 9667ebdeb8 Merge remote-tracking branch 'origin/main' 2026-04-19 15:57:43 +09:00
Alex Newman beea7899b9 chore: remove conductor.json shim
Conductor workspace setup is no longer needed - plugins handle hook
registration directly via plugin/hooks/hooks.json. The shim was copying
a stale settings.local.json into every worktree, registering dead hook
paths (save-hook.js, new-hook.js, summary-hook.js) that no longer exist.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 23:56:22 -07:00
Alex Newman a1741f4322 docs: update CHANGELOG.md for v12.2.0 + make generator incremental
Script now reads existing CHANGELOG.md, skips releases already documented,
only fetches bodies for new releases, and prepends them. Pass --full to
force complete regeneration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 20:21:51 -07:00
Alex Newman 11666e9ffb chore: bump version to 12.2.0
Publish to npm / publish (push) Has been cancelled
2026-04-17 20:14:22 -07:00
Alex Newman fb8e526c55 Merge pull request #2052 from thedotmack/thedotmack/worktree-remap
feat(worktree): scope per worktree, cwd backfill, and merged-worktree adoption
2026-04-17 20:10:54 -07:00
Alex Newman b8d63d949f fix(worktree): self-heal Chroma metadata on re-run
Addresses unresolved CodeRabbit finding on WorktreeAdoption.ts:296.

Previously, Chroma patch failures stranded rows permanently: adoptedSqliteIds
was built only from rows where merged_into_project IS NULL, so once SQL
committed, reruns couldn't rediscover them for retry.

The Chroma id set is now built from ALL observations whose project matches a
merged worktree — including rows already stamped to this parent. Combined
with the idempotent updateMergedIntoProject, transient Chroma failures
self-heal on the next adoption pass.

SQL writes remain idempotent (UPDATE still guards on merged_into_project IS
NULL), so adoptedObservations / adoptedSummaries continue to count only
newly-adopted rows. chromaUpdates now counts total Chroma writes per pass
(may exceed adoptedObservations when retrying).
2026-04-16 22:01:21 -07:00
Alex Newman 7a66cb310f fix(worktree): address PR review — schema guard, startup adoption, query parity
Addresses six CodeRabbit/Greptile findings on PR #2052:

- Schema guard in adoptMergedWorktrees probes for merged_into_project
  columns before preparing statements; returns early when absent so first
  boot after upgrade (pre-migration) doesn't silently fail.

- Startup adoption now iterates distinct cwds from pending_messages and
  dedupes via resolveMainRepoPath — the worker daemon runs with
  cwd=plugin scripts dir, so process.cwd() fallback was a no-op.

- ObservationCompiler single-project queries (queryObservations /
  querySummaries) OR merged_into_project into WHERE so injected context
  surfaces adopted worktree rows, matching the Multi variants.

- SessionStore constructor now calls ensureMergedIntoProjectColumns so
  bundled artifacts (context-generator.cjs) that embed SessionStore get
  the merged_into_project column on DBs that only went through the
  bundled migration chain.

- OBSERVER_SESSIONS_PROJECT constant is now derived from
  basename(OBSERVER_SESSIONS_DIR) and used across PaginationHelper,
  SessionStore, and timeline queries instead of hardcoded strings.

- Corrected misleading Chroma retry docstring in WorktreeAdoption to
  match actual behavior (no auto-retry once SQL commits).
2026-04-16 21:31:30 -07:00
Alex Newman d1601123fd feat(ui): hide observer-sessions project from UI lists
Observer sessions (internal SDK-driven worker queries) run under a
synthetic project name 'observer-sessions' to keep them out of
claude --resume. They were still surfacing in the viewer project
picker and unfiltered observation/summary/prompt feeds.

Filter them out at every UI-facing query:
- SessionStore.getAllProjects and getProjectCatalog
- timeline/queries.ts getAllProjects
- PaginationHelper observations/summaries/prompts when no project is selected

When a caller explicitly requests project='observer-sessions',
results are still returned (not a hard ban, just hidden by default).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 20:05:37 -07:00
Alex Newman f6fda8fff4 fix(worktree): address CodeRabbit PR review feedback
- Document --branch override in npx-cli help text
- Guard ContextBuilder against empty projects[] override; fall back to cwd-derived primary
- Ensure merged_into_project indexes are created even if ALTER ran in a prior partial migration
- Reject adopt --branch/--cwd flags with missing or flag-like values
- Use defined --color-border-primary token for merged badge border

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 20:03:27 -07:00
Alex Newman d24f3a7019 fix(worktree): address PR review — test assertion, dry-run sentinel, git timeouts
- Update allProjects test expectation to match [parent, composite] (matches JSDoc + callers in ContextBuilder/context handlers).
- Replace string-matched __DRY_RUN_ROLLBACK__ sentinel with dedicated DryRunRollback class to avoid swallowing unrelated errors.
- Add 5000ms timeout to spawnSync git calls in WorktreeAdoption and ProcessManager so worker startup can't hang on a stuck git process.
- Drop unreachable break after process.exit(0) in adopt case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:50:01 -07:00
Alex Newman 0a5f072aaf build(worktree): rebuild plugin artifacts for worktree adoption feature
Regenerated worker-service.cjs, context-generator.cjs, viewer.html, and
viewer-bundle.js to reflect all six implementation phases of the merged-
worktree adoption feature.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:36:00 -07:00
Alex Newman bce4ce32ec feat(ui): show merged-into-parent badge on adopted observations
ObservationCard renders a secondary "merged → <parent>" chip when
merged_into_project is set, next to the existing project label.
Both are meaningful: project is origin provenance, merged_into_project
is the current home.

Extends PaginationHelper's observations and summaries queries with
OR merged_into_project = ? so the single-project viewer fetch pulls
in adopted rows — the plan's Phase 3 covered multi-project context
injection; this is the single-project UI read path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:33:47 -07:00
Alex Newman 5664fabce4 feat(cli): npx claude-mem adopt [--dry-run] [--branch X]
Adds a manual escape hatch for the worktree adoption engine. Covers
squash-merges where git branch --merged HEAD returns nothing, and
lets users re-run adoption on demand.

Wired through worker-service.cjs (same pattern as generate/clean)
so the command runs under Bun with bun:sqlite, keeping npx-cli/
pure Node. --cwd flag passes the user's working directory through
the spawn so the engine resolves the correct parent repo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:28:17 -07:00
Alex Newman 0b90495391 feat(worktree): auto-adopt merged worktrees on worker startup
Invokes adoptMergedWorktrees() right after runOneTimeCwdRemap() and
before dbManager.initialize(), wrapped in try/catch so adoption
failures never block startup. Idempotent, so running every startup
is cheap — the SQL UPDATE only touches rows where merged_into_project
IS NULL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:24:43 -07:00
Alex Newman 3e770df332 feat(worktree): query plumbing surfaces merged rows under parent project
ObservationCompiler.queryObservationsMulti and querySummariesMulti
WHERE clause extended with OR merged_into_project IN (...), so a
parent-project read pulls in rows originally written under any
child worktree's composite name once merged.

SearchManager wraps the Chroma project filter in \$or so semantic
search behaves identically. ChromaSync baseMetadata now carries
merged_into_project on new embeddings; existing rows are patched
retroactively by the adoption engine.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:24:10 -07:00
Alex Newman a7c3c4af2d feat(worktree): adoption engine for merged worktrees
Detects merged worktrees via git (worktree list --porcelain +
branch --merged HEAD), then stamps merged_into_project on SQLite
observations/summaries and propagates the same metadata to Chroma
in lockstep. `project` stays immutable; adoption is a virtual
pointer. Idempotent via IS NULL guard on UPDATE and by idempotent
Chroma metadata writes. SQL is source of truth — Chroma failures
are logged but don't roll back SQL.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:19:02 -07:00
Alex Newman 3d1dfcc26a feat(migration): add merged_into_project column for worktree adoption
Nullable pointer on observations and session_summaries that lets a
worktree's rows surface under the parent project's observation list
without data movement. Self-idempotent via PRAGMA table_info guard;
does not bump schema_versions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 19:12:38 -07:00
Alex Newman dc198d5677 feat(worktree): include parent repo observations in worktree read scope
Worktrees are branches off main; the parent holds the architecture,
decisions, and long-tail history the worktree inherits. Scoping reads
to the worktree alone meant every new worktree started cold on any
question that required prior context.

Expand `allProjects` in a worktree to `[parent, composite]` so reads
pull both. Writes still go through `.primary` (the composite), so
sibling worktrees don't leak into each other.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:55:17 -07:00
Alex Newman 193e7e0719 feat(worktree): auto-apply cwd-based project remap on worker startup
Ports scripts/cwd-remap.ts into ProcessManager.runOneTimeCwdRemap() and
invokes it in initializeBackground() alongside the existing chroma
migration. Uses pending_messages.cwd as the source of truth to rewrite
pre-worktree bare project names into the parent/worktree composite
format so search and context are consistent.

- Backs up the DB to .bak-cwd-remap-<ts> before any writes.
- Idempotent: marker file .cwd-remap-applied-v1 short-circuits reruns.
- No-ops on fresh installs (no DB, or no pending_messages table).
- On failure, logs and skips the marker so the next restart retries.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:51:33 -07:00
Alex Newman 9d695f53ed chore: remove auto-generated per-directory CLAUDE.md files
Leftover artifacts from an abandoned context-injection feature. The
project-level CLAUDE.md stays; the directory-level ones were generated
timeline scaffolding that never panned out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:51:24 -07:00
Alex Newman a65ab055ca fix(worktree): audit observation fetch/display for composite project names
Three sites didn't account for parent/worktree composite naming:

- PaginationHelper.stripProjectPath: marker used full composite, breaking
  path sanitization for worktrees checked out outside a parent/leaf layout.
  Now extracts the leaf segment.
- observations/store.ts: fallback imported getCurrentProjectName from
  shared/paths.ts (a duplicate impl without worktree detection). Switched
  to getProjectContext().primary so writes key into the same project as
  reads.
- SearchManager.getRecentContext: fallback used basename(cwd) and lost
  the parent prefix, making the MCP tool find nothing in worktrees.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:38:49 -07:00
Alex Newman d589bc5f25 fix(cwd-remap): address PR review feedback
- Handle bare repo common-dir (strip trailing .git) instead of an
  identical-branch ternary
- Surface unexpected git stderr while keeping "not a git repository"
  silent
- Explicitly close the sqlite handle in both dry-run and apply paths
  so WAL checkpoints complete

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 17:21:49 -07:00
Alex Newman 3869b083d0 fix(context): derive project from explicit projects array, not cwd
When a caller (e.g. worker context-inject route) passes a `projects`
array without a matching cwd, the cwd-derived `context.primary` drifted
from the projects being queried — producing an empty-state header for
one project while querying another. Use the last entry of `projects` so
header and query target stay in sync.
2026-04-16 17:16:51 -07:00
JOUNGWOOK KWON 87479d1706 fix: reset session when age limit exceeded, not just when completed
Previous fix only reset sessions with completed_at_epoch set.
But mac sleep/resume without SessionEnd leaves sessions alive with stale
started_at_epoch, causing age limit to block all processing on next use.

Now resets started_at_epoch whenever the session is older than the
4-hour wall-clock limit (MAX_SESSION_MS), matching SDKAgent's threshold.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:13:19 +09:00
JOUNGWOOK KWON 1c71a2bd98 chore: merge upstream v12.1.6 + keep local fixes
- Upstream v12.1.6: fix drop orphan flag when filtering empty-string spawn args (#2049)
  → Adopted upstream's cleaner look-behind approach for ProcessRegistry.ts
- Keep: reset completed session on mac sleep/resume (create.ts, not in upstream)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 09:04:40 +09:00
Alex Newman 148e1892df chore(scripts): replace worktree-remap with cwd-based remap using pending_messages.cwd
The old worktree-remap.ts tried to reconstruct per-session cwd by regex-
matching absolute paths that incidentally leaked into observation free-text
(files_read, source_input_summary, metadata, user_prompt). That source is
derived and lossy: it only hit 1/3498 plain-project sessions in practice.

pending_messages.cwd is the structured, authoritative cwd captured from
every hook payload — 7,935 of 8,473 rows are populated. cwd-remap.ts uses
that column as the source of truth:

  1. Pull every distinct cwd from pending_messages.cwd
  2. For each cwd, classify with git:
       - rev-parse --absolute-git-dir vs --git-common-dir → main vs worktree
       - rev-parse --show-toplevel for the correct leaf (handles cwds that
         are subdirs of the worktree root)
     Parent project name = basename(dirname(common-dir)); composite is
     parent/worktree for worktrees, basename(toplevel) for main repos.
  3. For each session, take the EARLIEST pending_messages.cwd (not the
     dominant one — claude-mem's own hooks run from nested .context/
     claude-mem/ directories and would otherwise poison the count).
  4. Apply UPDATEs in a single transaction across sdk_sessions,
     observations, and session_summaries. Auto-backs-up the DB first.

Result on a real DB: 41 sessions remapped (vs 1 previously),
1,694 observations and 3,091 session_summaries updated to match.
43 cwds skipped (deleted worktrees / non-repos) are left untouched —
no inference when the data isn't there.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 16:40:37 -07:00
Alex Newman 040729beef fix(project-name): use parent/worktree composite so observations don't cross worktrees
Revert of #1820 behavior. Each worktree now gets its own bucket:
- In a worktree, primary = `parent/worktree` (e.g. `claude-mem/dar-es-salaam`)
- In a main repo, primary = basename (unchanged)
- allProjects is always `[primary]` — strict isolation at query time

Includes a one-off maintenance script (scripts/worktree-remap.ts) that
retroactively reattributes past sessions to their worktree using path
signals in observations and user prompts. Two-rule inference keeps the
remap high-confidence:
  1. The worktree basename in the path matches the session's current
     plain project name (pre-#1820 era; trusted).
  2. Or all worktree path signals converge on a single (parent, worktree)
     across the session.
Ambiguous sessions are skipped.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-16 15:40:44 -07:00
Alex Newman 53622b59e9 docs: update CHANGELOG.md for v12.1.6
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 14:35:40 -07:00
Alex Newman 69080dc291 chore: bump version to 12.1.6
Publish to npm / publish (push) Has been cancelled
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 14:31:43 -07:00
Alex Newman c76a439491 fix: drop orphan flag when filtering empty-string spawn args (#2049)
Observations were 100% failing on Claude Code 2.1.109+ because the Agent
SDK emits ["--setting-sources", ""] when settingSources defaults to [].
The existing Bun-workaround filter stripped the empty string but left
the orphan --setting-sources flag, which then consumed --permission-mode
as its value, crashing the subprocess with:

  Error processing --setting-sources:
  Invalid setting source: --permission-mode.

Make the filter pair-aware: when an empty arg follows a --flag, drop
both so the SDK default (no setting sources) is preserved by omission.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 14:30:54 -07:00
JOUNGWOOK KWON 8536eeb60a fix: remove flag+empty-value pairs to prevent --permission-mode arg parsing error
The claude-agent-sdk generates --setting-sources with an empty string value
when settingSources defaults to []. Simply filtering empty strings (as before)
leaves --setting-sources without a value, causing the next flag --permission-mode
to be consumed as its value, resulting in "Invalid setting source" exit code 1.

Fix: remove the entire flag+empty-value pair instead of just the empty string.

Also includes: reset completed session on resume (mac sleep/resume fix).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 19:34:14 +09:00
JOUNGWOOK KWON 79170f007a fix: reset completed session on resume to prevent age limit false positives
When Claude Code resumes after mac sleep without proper SessionEnd hook,
createSDKSession was reusing the old completed row with stale started_at_epoch,
causing all observations and summaries to be blocked by the 4h wall-clock limit.

Now detects completed sessions on resume and resets started_at_epoch to now.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 19:24:21 +09:00
Alex Newman 70a150db74 docs: update CHANGELOG.md for v12.1.5 2026-04-15 14:41:31 -07:00
Alex Newman d7b4610e27 chore: bump version to 12.1.5
Publish to npm / publish (push) Has been cancelled
2026-04-15 14:40:44 -07:00
Alex Newman 88bb4e589e docs: update CHANGELOG.md for v12.1.4
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:10:29 -07:00
Alex Newman ebefae864e chore: bump version to 12.1.4
Publish to npm / publish (push) Has been cancelled
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:06:52 -07:00
Alex Newman 0cd931bb06 Merge pull request #1865 from thedotmack/thedotmack/find-cmem-refs
fix: revert unauthorized $CMEM branding in context header
2026-04-15 12:06:10 -07:00
Alex Newman 4c792f026d build: rebuild plugin artifacts after $CMEM header revert
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:05:49 -07:00
Alex Newman aa7cdb6d9f fix: revert unauthorized $CMEM branding in context header
A prior Claude instance snuck in a `$CMEM` token branding header
during a context compression refactor (7e072106). Reverts back to
the original descriptive format: `# [project] recent context, datetime`

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 12:04:27 -07:00
Alex Newman 5db90f2ea0 docs: update CHANGELOG.md for v12.1.3 2026-04-15 11:43:49 -07:00
Alex Newman 4ddf57610a chore: bump version to 12.1.3
Publish to npm / publish (push) Has been cancelled
2026-04-15 04:26:29 -07:00
Alex Newman d0fc68c630 revert: remove overengineered summary salvage logic (#1718) (#1850)
The synthetic summary salvage feature created fake summaries from observation
data when the AI returned <observation> instead of <summary> tags. This was
overengineered — missing a summary is preferable to fabricating one from
observation fields that don't map cleanly to summary semantics.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 04:22:41 -07:00
787 changed files with 86551 additions and 46083 deletions
+20
View File
@@ -0,0 +1,20 @@
{
"name": "claude-mem-local",
"interface": {
"displayName": "claude-mem (local)"
},
"plugins": [
{
"name": "claude-mem",
"source": {
"source": "local",
"path": "./plugin"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}
-136
View File
@@ -1,136 +0,0 @@
<claude-mem-context>
# Recent Activity
### Oct 25, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #2374 | 2:55 PM | ✅ | Marketplace metadata version synchronized to 4.2.11 | ~157 |
### Oct 27, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #2757 | 1:23 AM | 🟣 | Released v4.3.3 with Configurable Session Display and First-Time Setup UX | ~391 |
### Nov 4, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #3706 | 9:47 PM | ✅ | Marketplace Plugin Version Synchronized to 5.0.2 | ~162 |
| #3655 | 3:43 PM | ✅ | Version bumped to 5.0.1 across project | ~354 |
### Nov 5, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #4068 | 10:58 PM | ✅ | Committed v5.1.0 release with comprehensive release notes | ~486 |
| #4066 | 10:57 PM | ✅ | Updated marketplace.json version to 5.1.0 | ~192 |
| #3739 | 2:24 PM | ✅ | Updated version to 5.0.3 across project manifests | ~322 |
### Nov 6, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #4099 | 1:13 PM | 🟣 | Theme Toggle for Light/Dark Mode | ~253 |
| #4096 | " | ✅ | Marketplace Metadata Version Sync | ~179 |
| #4092 | 1:12 PM | 🔵 | Marketplace Configuration for Claude-Mem Plugin | ~194 |
| #4078 | 12:50 PM | 🔴 | Fixed PM2 ENOENT error on Windows systems | ~286 |
| #4075 | 12:49 PM | ✅ | Marketplace plugin version synchronized to 5.1.1 | ~189 |
### Nov 7, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #4612 | 6:33 PM | ✅ | Version Bumped to 5.2.0 Across All Package Metadata | ~359 |
| #4598 | 6:31 PM | ✅ | PR #69 Merged: cleanup/worker Branch Integration | ~469 |
| #4298 | 11:54 AM | 🔴 | Fixed PostToolUse Hook Schema Compliance | ~310 |
| #4295 | 11:53 AM | ✅ | Synchronized Plugin Marketplace Version to 5.1.4 | ~188 |
### Nov 8, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #5150 | 7:37 PM | 🟣 | Troubleshooting Skill Added to Claude-Mem Plugin | ~427 |
| #5133 | 7:29 PM | ✅ | Version 5.2.3 Released with Build Process | ~487 |
### Nov 9, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #5941 | 7:14 PM | ✅ | Marketplace Version Updated to 5.4.0 | ~157 |
### Nov 10, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #6341 | 1:49 PM | ✅ | Version Bumped to 5.4.1 | ~239 |
### Nov 11, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #6602 | 1:51 PM | ✅ | Version 5.4.5 Released to GitHub | ~279 |
| #6601 | " | ✅ | Version Patch Bump 5.4.4 to 5.4.5 | ~233 |
### Nov 14, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #8212 | 3:06 PM | 🔵 | Version Consistency Verification Across Multiple Configuration Files | ~238 |
### Nov 25, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #14882 | 1:32 PM | 🔵 | Marketplace Configuration Defines Plugin Version and Source Directory | ~366 |
### Nov 30, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #18064 | 10:52 PM | ✅ | Bumped version to 6.3.7 in marketplace.json | ~179 |
| #18060 | 10:51 PM | 🔵 | Read marketplace.json plugin manifest | ~190 |
### Dec 1, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #18428 | 3:33 PM | 🔵 | Version Conflict in Marketplace Configuration | ~191 |
### Dec 4, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #20049 | 3:23 PM | ✅ | Updated marketplace.json version to 6.5.2 | ~203 |
### Dec 9, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #22559 | 1:08 AM | ✅ | Version 7.0.3 committed to repository | ~261 |
| #22551 | 1:07 AM | ✅ | Marketplace metadata updated to version 7.0.3 | ~179 |
### Dec 10, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #23440 | 2:25 PM | ✅ | Marketplace Configuration Updated to 7.0.8 | ~188 |
### Dec 14, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #26799 | 11:39 PM | ✅ | Marketplace Manifest Version Updated to 7.2.3 | ~248 |
| #26796 | " | ✅ | Version Bumped to 7.2.3 in marketplace.json | ~259 |
| #26792 | 11:38 PM | 🔵 | Current Version Confirmed as 7.2.2 Across All Configuration Files | ~291 |
### Dec 16, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #28306 | 10:08 PM | 🔵 | Marketplace Configuration Also Shows Version 7.3.3 | ~220 |
| #27555 | 4:48 PM | ✅ | Version bump committed to main branch | ~242 |
| #27553 | " | ✅ | Version consistency verified across all configuration files | ~195 |
| #27551 | 4:47 PM | ✅ | Marketplace.json version updated to 7.3.1 | ~207 |
</claude-mem-context>
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "claude-mem",
"version": "12.1.2",
"version": "13.3.0",
"source": "./plugin",
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "claude-mem",
"version": "12.1.1",
"version": "13.3.0",
"description": "Memory compression system for Claude Code - persist context across sessions",
"author": {
"name": "Alex Newman"
},
"repository": "https://github.com/thedotmack/claude-mem",
"license": "AGPL-3.0",
"license": "Apache-2.0",
"keywords": [
"claude",
"claude-code",
-1
View File
@@ -1 +0,0 @@
{"sessionId":"6a00de6e-282e-4cd8-98ec-b5afb73c468d","pid":50072,"acquiredAt":1775678989779}
-29
View File
@@ -1,29 +0,0 @@
# Project-Level Skills
This directory contains skills **for developing and maintaining the claude-mem project itself**, not skills that are released as part of the plugin.
## Distinction
**Project Skills** (`.claude/skills/`):
- Used by developers working on claude-mem
- Not included in the plugin distribution
- Project-specific workflows (version bumps, release management, etc.)
- Not synced to `~/.claude/plugins/marketplaces/thedotmack/`
**Plugin Skills** (`plugin/skills/`):
- Released as part of the claude-mem plugin
- Available to all users who install the plugin
- General-purpose memory search functionality
- Synced to user installations via `npm run sync-marketplace`
## Skills in This Directory
### version-bump
Manages semantic versioning for the claude-mem project itself. Handles updating all three version files (package.json, marketplace.json, plugin.json), creating git tags, and GitHub releases.
**Usage**: Only for claude-mem maintainers releasing new versions.
## Adding New Skills
**For claude-mem development** → Add to `.claude/skills/`
**For end users** → Add to `plugin/skills/` (gets distributed with plugin)
+5 -2
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "12.1.1",
"version": "13.3.0",
"description": "Memory compression system for Claude Code - persist context across sessions",
"author": {
"name": "Alex Newman",
@@ -8,7 +8,7 @@
},
"homepage": "https://github.com/thedotmack/claude-mem#readme",
"repository": "https://github.com/thedotmack/claude-mem",
"license": "AGPL-3.0",
"license": "Apache-2.0",
"keywords": [
"claude",
"claude-code",
@@ -22,6 +22,9 @@
"typescript",
"nodejs"
],
"skills": "./plugin/skills/",
"mcpServers": "./plugin/.mcp.json",
"hooks": "./plugin/hooks/codex-hooks.json",
"interface": {
"displayName": "claude-mem",
"shortDescription": "Persistent memory and context compression across coding sessions.",
+7
View File
@@ -0,0 +1,7 @@
node_modules/
.git/
logs/
evals/swebench/runs/
.docker-claude-mem-data/
.venv
.venv-*
-6
View File
@@ -1,14 +1,8 @@
# Normalize all text files to LF on commit and checkout.
# This prevents CRLF shebang lines in bundled scripts from breaking
# the MCP server on macOS/Linux when built on Windows. Fixes #1342.
* text=auto eol=lf
# Compiled plugin scripts must always be LF — CRLF in the shebang
# causes "env: node\r: No such file or directory" on non-Windows hosts.
plugin/scripts/*.cjs eol=lf
plugin/scripts/*.js eol=lf
# Explicitly mark binary assets so git never modifies them.
*.png binary
*.jpg binary
*.jpeg binary
-57
View File
@@ -1,57 +0,0 @@
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
jobs:
claude-review:
# Optional: Filter by PR author
# if: |
# github.event.pull_request.user.login == 'external-contributor' ||
# github.event.pull_request.user.login == 'new-developer' ||
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
Please review this pull request and provide feedback on:
- Code quality and best practices
- Potential bugs or issues
- Performance considerations
- Security concerns
- Test coverage
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
Use `gh pr comment` with your Bash tool to leave your review as a comment on the PR.
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
claude_args: '--allowed-tools "Bash(gh issue view:*),Bash(gh search:*),Bash(gh issue list:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr list:*)"'
+1 -10
View File
@@ -23,7 +23,7 @@ jobs:
pull-requests: read
issues: read
id-token: write
actions: read # Required for Claude to read CI results on PRs
actions: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
@@ -36,15 +36,6 @@ jobs:
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# This is an optional setting that allows Claude to read CI results on PRs
additional_permissions: |
actions: read
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
# prompt: 'Update the pull request description to include a summary of changes.'
# Optional: Add claude_args to customize behavior and configuration
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
# or https://docs.claude.com/en/docs/claude-code/cli-reference for available options
# claude_args: '--allowed-tools Bash(gh pr:*)'
@@ -13,7 +13,6 @@ on:
jobs:
convert:
runs-on: ubuntu-latest
# Only run on labeled event if the label is 'feature-request', or always run on workflow_dispatch
if: |
(github.event_name == 'issues' && github.event.label.name == 'feature-request') ||
github.event_name == 'workflow_dispatch'
+34
View File
@@ -0,0 +1,34 @@
name: Windows
on:
pull_request:
paths:
- 'src/**'
- 'plugin/scripts/**'
- 'package.json'
- 'bunfig.toml'
- '.github/workflows/windows.yml'
push:
branches: [main]
jobs:
build:
runs-on: windows-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install Bun (worker runtime)
run: |
irm bun.sh/install.ps1 | iex
shell: pwsh
- run: npm install --no-audit --no-fund
# Build only — the build-and-sync script also runs marketplace sync + worker
# restart from a hardcoded ~/.claude/plugins path that doesn't exist on CI.
- run: npm run build
+16 -5
View File
@@ -21,20 +21,31 @@ bun.lock
private/
Auto Run Docs/
# Generated UI files (built from viewer-template.html)
src/ui/viewer.html
# Local MCP server config (for development only)
.mcp.json
.cursor/
# Ignore WebStorm project files (for dinosaur IDE users)
.idea/
.claude-octopus/
.claude/session-intent.md
.claude/session-plan.md
.claude/scheduled_tasks.lock
.octo/
# Local contribution analysis (not part of upstream)
CONTRIB_NOTES.md
plugin/.cli-installed
plugin/scripts/claude-mem
CONTRIB_NOTES.md
.docker-claude-mem-data/
evals/swebench/runs/
claude-opus-4-7+claude-mem.*.json
logs/run_evaluation/
.venv-swebench/
.docker-blowout-data/
.scratch/
docker/install-test/
-3
View File
@@ -1,3 +0,0 @@
{
"mcpServers": {}
}
-6
View File
@@ -1,4 +1,3 @@
# Source code (dist/ and plugin/ are the shipped artifacts)
src/
scripts/
tests/
@@ -7,14 +6,12 @@ datasets/
private/
antipattern-czar/
# Heavy binaries installed at runtime via smart-install.js
plugin/node_modules/
plugin/scripts/claude-mem
plugin/bun.lock
plugin/data/
plugin/data.backup/
# Development files
*.ts
!*.d.ts
tsconfig*.json
@@ -24,7 +21,6 @@ tsconfig*.json
jest.config*
vitest.config*
# Git and CI
.git/
.github/
.gitignore
@@ -33,14 +29,12 @@ vitest.config*
.mcp.json
.plan/
# OS files
.DS_Store
*.log
*.tmp
*.temp
Thumbs.db
# Misc
Auto Run Docs/
~*/
http*/
+100
View File
@@ -0,0 +1,100 @@
# Issue 2341 Reliability Slice Plan
Scope: first PR from the consolidated issue triage. This PR should not try to
solve the full backlog. It should remove a few high-confidence paper cuts from
the first two buckets: install/startup contract and DB/export contract.
## Phase 0: Documentation Discovery
Allowed APIs and patterns:
- Install marker helpers live in `src/npx-cli/install/setup-runtime.ts`.
Existing tests are in `tests/setup-runtime.test.ts`.
- Runtime startup warning logic lives in `plugin/scripts/version-check.js`.
It currently resolves the plugin root from `CLAUDE_PLUGIN_ROOT`, then from the
script directory.
- Export script reads worker settings via `SettingsDefaultsManager.loadFromFile`.
Worker settings must respect `CLAUDE_MEM_DATA_DIR`, because shared path helpers
and settings defaults already expose that environment override.
- `/api/sdk-sessions/batch` is registered in
`src/services/worker/http/routes/DataRoutes.ts` and expects
`memorySessionIds`. Existing coercion tests are in
`tests/worker/http/routes/data-routes-coercion.test.ts`.
- Current `PendingMessageStore` writes and reads `tool_use_id`, but no longer
reads `worker_pid`, `retry_count`, `failed_at_epoch`, or
`completed_at_epoch`. Current schema guardrails should match code that runs
today, not old migration intent.
Anti-pattern guards:
- Do not reintroduce `worker_pid` in `pending_messages` unless the current claim
query starts using it again.
- Do not rely only on `schema_versions` for columns that current SQL references.
- Do not add another install marker format. Read both legacy plain text and the
current JSON format, but keep writing the JSON marker.
- Do not make `export-memories.ts` fall back to `~/.claude-mem` when
`CLAUDE_MEM_DATA_DIR` is set.
## Phase 1: Install Marker Compatibility
What to implement:
- Teach `readInstallMarker()` to parse legacy plain-text marker files that only
contain a version string.
- Teach `plugin/scripts/version-check.js` to accept the same legacy marker shape.
- Keep `writeInstallMarker()` unchanged so new installs write the canonical JSON
schema.
Verification:
- Add `tests/setup-runtime.test.ts` coverage for a plain-text `.install-version`.
- Add a focused test for `plugin/scripts/version-check.js` behavior, or extend an
existing plugin script test if one exists.
- Run `bun test tests/setup-runtime.test.ts`.
## Phase 2: Export Script Contract Repair
What to implement:
- Update `scripts/export-memories.ts` to load settings from
`CLAUDE_MEM_DATA_DIR/settings.json` instead of always using
`~/.claude-mem/settings.json`.
- Change the `/api/sdk-sessions/batch` request body from `sdkSessionIds` to
`memorySessionIds`.
- Optionally allow `DataRoutes` to accept the legacy `sdkSessionIds` alias as a
compatibility bridge, but prefer the canonical field in scripts.
Verification:
- Add or update tests around the SDK-session batch route alias/coercion.
- Add a script-level test if practical; otherwise verify by grep that
`scripts/export-memories.ts` no longer sends `sdkSessionIds` and no longer
hardcodes `homedir(), '.claude-mem'`.
- Run the focused route/export tests.
## Phase 3: Current Pending Queue Shape Guardrails
What to implement:
- Add a regression test that initializes a DB whose `schema_versions` claims old
pending-message migrations are applied while `pending_messages.tool_use_id` is
missing. Constructing `SessionStore` should still add the missing column
because current enqueue SQL requires it.
- Add a regression test asserting the current fresh DB shape does not require
`worker_pid`, since the current claim query does not use it.
- If tests expose a real source/schema mismatch, update docs/schema comments to
match current code rather than reintroducing unused columns.
Verification:
- Run focused sqlite tests for `SessionStore` / `PendingMessageStore`.
- Grep for live `worker_pid` reads in TypeScript before deciding whether it is
still a required current column.
## Final Verification
- Run focused tests changed by this PR.
- Run `npm run typecheck:root` if dependencies are available.
- Run `git diff --check`.
- Open a non-draft PR against the upstream default branch.
- Do not merge, release, or ship without explicit user approval.
@@ -0,0 +1,315 @@
# Plan: Disable Summaries for Subagents + Label Subagent Observations
## Goal
1. **Disable summaries for subagents** — prevent any summary generation path (hook → worker → SDK agent) from firing for events originating in a Claude Code subagent.
2. **Label observations from subagents** — tag every observation with the subagent identity (agent_id + agent_type) so downstream queries can distinguish main-session work from subagent work.
## Phase 0 — Documentation Discovery (COMPLETE)
### Claude Code hook payload fields (source: https://code.claude.com/docs/en/hooks.md)
- `agent_id` — present **only** when the hook fires inside a subagent invocation (e.g., `"agent-def456"`). Absent in the main session.
- `agent_type` — the subagent identifier (built-in like `"Bash"`, `"Explore"`, `"Plan"`, or a custom agent name). Present in subagents **and** when `--agent` flag is used.
- `session_id` — shared across main and subagents in the same session. Cannot distinguish contexts on its own.
- `transcript_path` — shared session transcript. Not a reliable discriminator.
- `SubagentStop` — dedicated event that fires when a subagent finishes. Currently **NOT registered** in `plugin/hooks/hooks.json`.
- `Stop` — fires for the main Claude agent (not subagents). Currently registered → wired to `summarize` handler.
**Discriminator for subagent context**: presence of `agent_id` OR `agent_type` in the hook stdin JSON.
### Current claude-mem architecture (grepped + read)
- `src/cli/types.ts:1-15``NormalizedHookInput` lacks `agentId` / `agentType`.
- `src/cli/adapters/claude-code.ts:5-17` — Claude Code adapter does NOT extract `agent_id` / `agent_type`.
- `src/cli/handlers/summarize.ts:27-143` — Stop-hook handler posts to `/api/sessions/summarize` without guarding on subagent context.
- `src/cli/handlers/observation.ts:51-62` — PostToolUse handler POSTs observation body without subagent fields.
- `src/services/worker/http/routes/SessionRoutes.ts:555-646``handleObservationsByClaudeId` destructures only `{ contentSessionId, tool_name, tool_input, tool_response, cwd }`; `queueObservation` call at line 620 has no subagent field.
- `src/services/sqlite/observations/store.ts:75-80``INSERT INTO observations` column list has no `agent_type` / `agent_id`.
- `src/services/sqlite/migrations.ts:578-588` — migrations array ends with `migration009` (version 26). Next migration slot is `migration010` (version 27).
- `src/utils/logger.ts:195-203` — already reads `input.subagent_type` for formatting Task tool invocations (reference pattern, no downstream storage).
### Allowed APIs / patterns to copy
- **Adapter metadata extension pattern**: `src/cli/adapters/gemini-cli.ts:77-96` already collects platform-specific metadata into `metadata` and returns it on `NormalizedHookInput`. Copy this pattern.
- **Migration pattern**: `src/services/sqlite/migrations.ts:556-573` (migration009) is a copy-ready template for conditional `ALTER TABLE ADD COLUMN` additions.
- **Observation INSERT column extension pattern**: `src/services/sqlite/observations/store.ts:75-98` — add `agent_type`, `agent_id` to the column list and to `stmt.run(...)` bindings.
### Anti-patterns to avoid
- Do NOT assume `agent_id` is present on the main session — it is undefined there. Treat presence as the discriminator.
- Do NOT register SubagentStop as a new hook in `hooks.json` just to "disable" summaries — defensively short-circuiting in the handler is simpler and covers both current and future Claude Code versions where Stop might fire in subagent contexts.
- Do NOT rely on `session_id` to distinguish — it is shared.
- Do NOT invent a `parent_tool_use_id` field in hook input. The Claude Code docs do not expose parent tool use ID on hook payloads. Only use `agent_id` + `agent_type`.
- Do NOT break the existing observation hash-dedup logic in `store.ts:19-28` — leave the hash inputs as-is.
---
## Phase 1 — Extend hook input surface to carry subagent fields
**What to implement** (COPY pattern from gemini-cli adapter metadata handling):
1. Edit `src/cli/types.ts:1-15` — add two optional fields to `NormalizedHookInput`:
```ts
agentId?: string; // Claude Code subagent agent_id (undefined in main session)
agentType?: string; // Claude Code subagent agent_type (undefined in main session)
```
2. Edit `src/cli/adapters/claude-code.ts:5-17` — in `normalizeInput`, extract `r.agent_id` and `r.agent_type`:
```ts
return {
sessionId: r.session_id ?? r.id ?? r.sessionId,
cwd: r.cwd ?? process.cwd(),
prompt: r.prompt,
toolName: r.tool_name,
toolInput: r.tool_input,
toolResponse: r.tool_response,
transcriptPath: r.transcript_path,
agentId: typeof r.agent_id === 'string' ? r.agent_id : undefined,
agentType: typeof r.agent_type === 'string' ? r.agent_type : undefined,
};
```
3. Edit `src/cli/adapters/gemini-cli.ts:88-97` — return matching `undefined` defaults so the interface contract is consistent across adapters. (No behavior change; just explicit `agentId: undefined, agentType: undefined` on the return object, or rely on the optional-field default by leaving it out. Leave it out — TypeScript optional is fine.)
**Documentation references**: Claude Code hooks docs section "Subagent Identification Fields"; gemini-cli adapter metadata pattern at `src/cli/adapters/gemini-cli.ts:77-96`.
**Verification checklist**:
- `grep -n "agentId" src/cli/types.ts` → finds the new field.
- `grep -n "agent_id" src/cli/adapters/claude-code.ts` → finds the extraction.
- `npm run build` succeeds.
**Anti-pattern guards**:
- Do NOT rename `agent_id` / `agent_type` snake_case raw fields. Camel-case only in `NormalizedHookInput`.
- Do NOT default to a sentinel string like `"main"`; leave undefined when absent.
---
## Phase 2 — Short-circuit summary generation in subagent context
**What to implement**:
1. Edit `src/cli/handlers/summarize.ts:27-36`, immediately after the worker-ready check (line 34) and before any processing:
```ts
// Skip summaries in subagent context — subagents do not own the session summary.
// Main Stop hook owns it; SubagentStop (if ever registered) must no-op.
if (input.agentId || input.agentType) {
logger.debug('HOOK', 'Skipping summary: subagent context detected', {
sessionId: input.sessionId,
agentId: input.agentId,
agentType: input.agentType
});
return { continue: true, suppressOutput: true, exitCode: HOOK_EXIT_CODES.SUCCESS };
}
```
2. (Safety) Edit `src/services/worker/http/routes/SessionRoutes.ts` in `handleSummarizeByClaudeId` (around line 655-692): add a defensive guard that rejects the summarize request if the body includes `agentId` or `agentType`. Return `{ status: 'skipped', reason: 'subagent_context' }`. This is belt-and-suspenders in case any caller bypasses the hook layer.
3. Extend the `/api/sessions/summarize` body in `src/cli/handlers/summarize.ts:73-82` to include `agentId` and `agentType` (passthrough) so the worker can make the same decision independently. Only pass fields when defined:
```ts
body: JSON.stringify({
contentSessionId: sessionId,
last_assistant_message: lastAssistantMessage,
platformSource,
...(input.agentId ? { agentId: input.agentId } : {}),
...(input.agentType ? { agentType: input.agentType } : {}),
}),
```
**Documentation references**: summarize.ts handler flow at `src/cli/handlers/summarize.ts:27-143`; summarize route at `src/services/worker/http/routes/SessionRoutes.ts:655-692`.
**Verification checklist**:
- Unit test or manual dispatch with a payload containing `agent_id: "agent-abc"` → summarize handler returns before calling `/api/sessions/summarize`.
- `grep -n "subagent" src/cli/handlers/summarize.ts` → finds the new guard.
- `grep -n "subagent_context\|agentId" src/services/worker/http/routes/SessionRoutes.ts` → finds the server-side guard.
**Anti-pattern guards**:
- Do NOT also short-circuit in `session-complete` or `context` handlers — the session's main Stop still cleans up.
- Do NOT log at info level (spammy); `logger.debug` only.
---
## Phase 3 — Database schema migration for subagent labels on observations
**What to implement** (COPY migration009 pattern from `src/services/sqlite/migrations.ts:556-573`):
1. Append a new migration to `src/services/sqlite/migrations.ts` right after `migration009` (before the `migrations` array at line 578):
```ts
export const migration010: Migration = {
version: 27,
up: (db: Database) => {
const columns = db.prepare('PRAGMA table_info(observations)').all() as any[];
const hasAgentType = columns.some((c: any) => c.name === 'agent_type');
const hasAgentId = columns.some((c: any) => c.name === 'agent_id');
if (!hasAgentType) {
db.run('ALTER TABLE observations ADD COLUMN agent_type TEXT');
}
if (!hasAgentId) {
db.run('ALTER TABLE observations ADD COLUMN agent_id TEXT');
}
db.run('CREATE INDEX IF NOT EXISTS idx_observations_agent_type ON observations(agent_type)');
console.log('[migration010] Added agent_type, agent_id columns to observations');
},
down: (_db: Database) => {
// SQLite DROP COLUMN not fully supported; no-op
}
};
```
2. Add `migration010` to the `migrations` array at `src/services/sqlite/migrations.ts:578-588`.
3. Check `src/services/sqlite/migrations/runner.ts` to see if there's a parallel registration site; if so, mirror the addition there. (Investigation step — if `runner.ts` replicates migration definitions, extend it the same way. Otherwise, importing `migrations` from `migrations.ts` is sufficient.)
**Documentation references**: migration007 and migration009 at `src/services/sqlite/migrations.ts:491-509` and `556-573` as copy-ready templates.
**Verification checklist**:
- Run worker; check logs for `[migration010]`.
- `sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA table_info(observations);"` → shows `agent_type` and `agent_id` columns.
- `sqlite3 ~/.claude-mem/claude-mem.db ".indexes observations"` → shows `idx_observations_agent_type`.
**Anti-pattern guards**:
- Do NOT drop or rename existing columns.
- Do NOT set NOT NULL constraints — main-session rows have NULL for these.
- Do NOT pick a version number that's already used (26 is migration009; use 27).
---
## Phase 4 — Thread subagent fields through hook → worker → SDK → DB
**What to implement**:
### 4a — Hook PostToolUse handler sends fields
Edit `src/cli/handlers/observation.ts:51-62`:
```ts
const response = await workerHttpRequest('/api/sessions/observations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contentSessionId: sessionId,
platformSource,
tool_name: toolName,
tool_input: toolInput,
tool_response: toolResponse,
cwd,
...(input.agentId ? { agentId: input.agentId } : {}),
...(input.agentType ? { agentType: input.agentType } : {}),
})
});
```
### 4b — Worker observations route receives and forwards
Edit `src/services/worker/http/routes/SessionRoutes.ts:555-646`:
- Destructure: `const { contentSessionId, tool_name, tool_input, tool_response, cwd, agentId, agentType } = req.body;`
- Pass to `queueObservation` at line 620:
```ts
this.sessionManager.queueObservation(sessionDbId, {
tool_name,
tool_input: cleanedToolInput,
tool_response: cleanedToolResponse,
prompt_number: promptNumber,
cwd: cwd || ...,
agentId: typeof agentId === 'string' ? agentId : undefined,
agentType: typeof agentType === 'string' ? agentType : undefined,
});
```
### 4c — queueObservation type extension
Investigation: find the `queueObservation` signature in the session manager (likely `src/services/session/` or similar). Add optional `agentId?: string; agentType?: string;` to the payload type. These must ride through to the SDK agent's observation context so they land in `storeObservation()`.
### 4d — Observation input type + store.ts extension
- Edit `src/services/sqlite/observations/types.ts:10-19` — add:
```ts
agent_type?: string | null;
agent_id?: string | null;
```
- Edit `src/services/sqlite/observations/store.ts:75-98`:
- Column list: add `, agent_type, agent_id` before `content_hash`.
- Placeholders: add `, ?, ?`.
- Bindings: add `observation.agent_type ?? null, observation.agent_id ?? null`.
- Verify there are no other `INSERT INTO observations` sites that need updating. Sites already located (to re-check):
- `src/services/sqlite/SessionStore.ts:1755` / `1890` / `2022` / `2623` — each needs the same two columns added. If these are separate insertion paths, extend all of them; pass `null` for fields not available in that path.
### 4e — SDK agent observation parser forwards fields
The SDK agent parses `<observation>` XML into an `ObservationInput` and calls `storeObservation`. The tool_input passed in must carry `agentId`/`agentType` through to here so the row gets labeled. Investigation step: find where `storeObservation()` is called with an `ObservationInput` built from the queued observation, and inject `agent_type`/`agent_id` from the queue item's subagent fields onto the `ObservationInput`. Location likely in `src/services/sdk/` or adjacent.
**Documentation references**:
- observation handler at `src/cli/handlers/observation.ts:51-62`
- SessionRoutes observations endpoint at `src/services/worker/http/routes/SessionRoutes.ts:555-646`
- storeObservation at `src/services/sqlite/observations/store.ts:75-98`
- Existing observation INSERT sites at `src/services/sqlite/SessionStore.ts:1755, 1890, 2022, 2623` (audit required)
**Verification checklist**:
- `grep -rn "agent_type\|agentType" src/` → shows fields threaded through every layer.
- Simulate a Task subagent PostToolUse payload → observation row has non-null `agent_type`.
- Main-session PostToolUse → observation row has NULL `agent_type` (existing behavior preserved).
- No existing test suite breaks: `npm test` passes.
**Anti-pattern guards**:
- Do NOT include `agent_type` / `agent_id` in the content-hash computation (`src/services/sqlite/observations/store.ts:19-28`). The hash identity must remain stable for dedup.
- Do NOT add fields to the FTS5 `observations_fts` virtual table — not searchable text.
- Do NOT backfill — leave existing rows NULL.
---
## Phase 5 — Tests and verification
**What to implement**:
1. Add a unit test at `tests/cli/handlers/summarize-subagent-skip.test.ts` verifying:
- When `input.agentId` is set, handler returns early with `exitCode: SUCCESS` and does NOT call `workerHttpRequest`.
- When `input.agentType` is set, same behavior.
- When both are undefined, handler proceeds (mock worker response).
2. Add a unit test at `tests/cli/adapters/claude-code-subagent.test.ts` verifying:
- `normalizeInput({ agent_id: "agent-abc", agent_type: "Explore" })` returns `{ agentId: "agent-abc", agentType: "Explore" }`.
- `normalizeInput({})` returns `agentId: undefined, agentType: undefined`.
3. Add a unit test at `tests/services/sqlite/observations/store-subagent-label.test.ts` verifying:
- `storeObservation` with `agent_type: "Explore"` inserts row with `agent_type = "Explore"`.
- Omitted `agent_type` → NULL in DB.
- Content-hash dedup still works (two observations with same title/narrative but different `agent_type` should still collide on dedup — verify expected behavior; update test if product intent differs).
4. Manual integration check: start worker, simulate a hook payload with `agent_id`/`agent_type`, observe observation row in DB.
**Verification checklist**:
- `npm test` passes.
- `npm run build` succeeds.
- Database inspection shows expected rows.
**Anti-pattern guards**:
- Do NOT mock the entire storeObservation — use a real in-memory Bun SQLite DB if existing tests do.
- Do NOT add integration tests that require a running worker unless the suite already does.
---
## Phase 6 — Build + autonomous execution pipeline
After Phases 1-5 land and pass verification:
1. **Build**: `npm run build-and-sync`.
2. **Commit**: a single commit titled `feat: disable subagent summaries and label subagent observations` with co-author footer.
3. **Push branch**: push current worktree branch `trail-guarantee` (or a new feature branch — confirm with `git status`). Create PR via `gh pr create` with summary of both features.
4. **Run `/loop 5m`** to continuously re-check PR review comments: as each CodeRabbit/Greptile/human comment arrives, address it in a new commit, push, and re-check. Exit loop only when all actionable review comments are resolved and status checks pass.
5. **Merge to main** via `gh pr merge --squash --auto` (or `--merge` per repo convention — inspect `.github/` first).
6. **Version bump**: `cd ~/Scripts/claude-mem/` and run `/version-bump`.
**Anti-pattern guards for this phase**:
- Do NOT force-push to main.
- Do NOT skip hooks (`--no-verify`).
- Do NOT squash-merge if the repo uses rebase-merge; check `.github/` for branch-protection hints.
- Do NOT resolve a review comment without actually addressing it — every resolved thread must have a corresponding commit or a reply explaining why no change is needed.
---
## Final Verification (end of Phase 5, before Phase 6)
- `grep -rn "agent_id\|agentId" src/` → fields present in: `types.ts`, `claude-code.ts`, `summarize.ts`, `observation.ts`, `SessionRoutes.ts`, observation types, store, migration010.
- `grep -rn "subagent_context" src/services/worker/` → worker-side guard present.
- `sqlite3 ~/.claude-mem/claude-mem.db "PRAGMA table_info(observations);"` → includes `agent_type`, `agent_id`.
- `npm test && npm run build` → both green.
- Smoke test: simulate a subagent hook payload end-to-end → observation labeled, no summary fired.
+570
View File
@@ -0,0 +1,570 @@
# Merged-Worktree Adoption
**Goal**: When a worktree's branch is merged into its parent, the worktree's observations become part of the parent project's observation list — without data movement, destructive schema changes, or lost provenance.
**Approach**: Add a nullable `merged_into_project` column to observations and session_summaries, extend query predicates with `OR merged_into_project = :parent`, propagate the same metadata to Chroma embeddings for semantic-search consistency, detect merges via git (authoritative), run adoption automatically on worker startup, and offer a CLI escape hatch for squash-merges.
**Key design decisions**:
- `observations.project` is **immutable provenance** — never overwritten.
- Merged-status is a **virtual pointer**, not a data move.
- **Chroma metadata stays in lockstep with SQLite** (full consistent sync, not lazy SQL expansion). Single source of truth per row.
- Detection is **git-authoritative** (`git worktree list --porcelain` + `git branch --merged`), with a manual CLI override for squash-merges.
---
## Phase 0 — Documentation Discovery (COMPLETE)
Findings consolidated from three parallel discovery subagents. The following are the ONLY APIs/patterns to copy from. Do not invent alternatives.
### Allowed APIs (copy from these locations)
| Need | File | Lines | What to copy |
|---|---|---|---|
| Migration idempotency via marker file | `src/services/infrastructure/ProcessManager.ts` | 680830 | `runOneTimeCwdRemap` structure, marker file pattern `.cwd-remap-applied-v1` |
| Worker startup wiring | `src/services/worker-service.ts` | 363365 | Call site inside `initializeBackground()`, invoked before `dbManager.initialize()` |
| `ALTER TABLE ADD COLUMN` idempotency | `src/services/sqlite/migrations/runner.ts` | 131141 | `PRAGMA table_info(<table>)` guard before `ALTER TABLE ... ADD COLUMN` |
| Column addition example | `src/services/sqlite/migrations/runner.ts` | 495 | `db.run('ALTER TABLE observations ADD COLUMN discovery_tokens INTEGER DEFAULT 0')` |
| Observations schema | `src/services/sqlite/migrations/runner.ts` | 8296 | Existing columns + indices (do not duplicate) |
| `schema_versions` marker table | `src/services/sqlite/migrations/runner.ts` | 5158 | `INSERT OR IGNORE INTO schema_versions ...` — used only when numbered migration |
| Logger | `src/utils/logger.ts` | 18 | Components: `SYSTEM`, `DB`, `CHROMA_SYNC`. Use `logger.info/warn/error('SYSTEM', ...)` |
| Worktree detection | `src/utils/worktree.ts` | 184 | `detectWorktree(cwd): WorktreeInfo { isWorktree, worktreeName, parentRepoPath, parentProjectName }` |
| Project-name derivation | `src/utils/project-name.ts` | 73119 | `getProjectContext(cwd): ProjectContext { primary, parent, isWorktree, allProjects }` |
| Multi-project read (WHERE to extend) | `src/services/context/ObservationCompiler.ts` | 111160 | `queryObservationsMulti``WHERE o.project IN (${projectPlaceholders})` |
| Same, for summaries | `src/services/context/ObservationCompiler.ts` | 168196 | Parallel summary-fetching query with `ss.project IN (...)` |
| Context injection endpoint | `src/services/worker/http/routes/SearchRoutes.ts` | 211253 | `handleContextInject` wires `projects` comma-separated query param into `generateContext` |
| Context entry point | `src/services/context/ContextBuilder.ts` | 126183 | `generateContext()` picks `queryObservationsMulti` when `projects.length > 1` |
| Chroma metadata attach (observations) | `src/services/sync/ChromaSync.ts` | 132140 | `baseMetadata` object — includes `project`, `sqlite_id`, etc. This is where `merged_into_project` is added. |
| Chroma collection architecture | `src/services/sync/ChromaSync.ts` | 806 (comment) | **Single shared collection `cm__claude-mem`**, scoped by metadata. Do NOT create a per-merged collection. |
| Chroma filter build (read side) | `src/services/sync/SearchManager.ts` | 174177 | `whereFilter = { project: options.project }` — extended with `$or` in Phase 3 |
| Chroma update API | `src/services/sync/ChromaSync.ts` (grep) | — | `chroma_update_documents` via MCP — used by existing sync flows |
| CLI entrypoint switch | `src/npx-cli/index.ts` | 28169 | Plain `switch (command)`, dynamic `import()` of `./commands/<name>.ts`. No commander/cac. |
| Admin-script template | `scripts/cwd-remap.ts` | 1186 | Bun shebang, argv parsing, `--apply` gate, dry-run default |
| UI observation card | `src/ui/viewer/components/ObservationCard.tsx` | 58 | `<span className="card-project">{observation.project}</span>` — where the merged badge is added |
### Anti-patterns (do NOT do these)
- Do NOT overwrite `observations.project` or `session_summaries.project`. These are immutable provenance.
- Do NOT create a new Chroma collection for merged observations. Deployment uses a single shared `cm__claude-mem` collection.
- Do NOT introduce a `gh` CLI dependency. Codebase has no `gh` usage outside `.github/workflows/`. Use `git` subprocesses only.
- Do NOT use SQLite's unsupported `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` syntax. Use the `PRAGMA table_info` guard instead.
- Do NOT use a CLI framework (commander, cac, yargs). The codebase uses hand-rolled `switch (command)` + `process.argv.slice(2)`.
- Do NOT mutate `ProjectContext.allProjects` to inject merged children. The reverse lookup lives in the SQL/Chroma query predicates, not in `ProjectContext`.
- Do NOT run the lazy "SQL-expand projects then filter Chroma" approach. We want Chroma metadata to be the authoritative filter for semantic search.
---
## Phase 1 — Schema migration
**What to implement**: One nullable column + one index on each of `observations` and `session_summaries`. Idempotent via `PRAGMA table_info` guard.
### Files touched
- `src/services/sqlite/migrations/runner.ts`
### Implementation
Add a new method `ensureMergedIntoProjectColumns()` on `MigrationRunner`, modeled on the pattern at lines 131141:
```typescript
private ensureMergedIntoProjectColumns(): void {
const obsCols = this.db
.query('PRAGMA table_info(observations)')
.all() as TableColumnInfo[];
if (!obsCols.some(c => c.name === 'merged_into_project')) {
this.db.run('ALTER TABLE observations ADD COLUMN merged_into_project TEXT');
this.db.run(
'CREATE INDEX IF NOT EXISTS idx_observations_merged_into ON observations(merged_into_project)'
);
}
const sumCols = this.db
.query('PRAGMA table_info(session_summaries)')
.all() as TableColumnInfo[];
if (!sumCols.some(c => c.name === 'merged_into_project')) {
this.db.run('ALTER TABLE session_summaries ADD COLUMN merged_into_project TEXT');
this.db.run(
'CREATE INDEX IF NOT EXISTS idx_summaries_merged_into ON session_summaries(merged_into_project)'
);
}
}
```
Call from `runAllMigrations()` — append immediately after the last existing `ensure*` method so it runs on every worker startup. The `PRAGMA table_info` check is O(1) and makes re-runs cheap.
### Verification
- Start the worker. Migration logs show no error.
- `sqlite3 ~/.claude-mem/claude-mem.db ".schema observations"` shows `merged_into_project TEXT`.
- Same for `session_summaries`.
- Restart worker → no ALTER TABLE error (guard worked).
- `sqlite3 ~/.claude-mem/claude-mem.db ".indices observations"` lists `idx_observations_merged_into`.
### Anti-pattern guards
- Do NOT use `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` — SQLite does not support it.
- Do NOT bump `schema_versions` for this migration. That table is for numbered migration history; the column-existence check is self-idempotent.
---
## Phase 2 — Adoption engine (SQLite + Chroma consistent)
**What to implement**: A single function that, given a parent repo path, detects all merged-worktree branches and stamps `merged_into_project` on both SQLite rows AND Chroma metadata in the same logical operation. Reused by worker startup (Phase 4) and CLI (Phase 5).
### Files touched
- `src/services/infrastructure/WorktreeAdoption.ts` (new)
- `src/services/sync/ChromaSync.ts` — add `updateMergedIntoProject(sqliteIds: number[], mergedIntoProject: string): Promise<void>`
### Public API
```typescript
export interface AdoptionResult {
repoPath: string;
parentProject: string;
scannedWorktrees: number;
mergedBranches: string[]; // branches classified as merged
adoptedObservations: number; // SQLite rows stamped
adoptedSummaries: number;
chromaUpdates: number; // Chroma docs patched
chromaFailed: number;
dryRun: boolean;
errors: Array<{ worktree: string; error: string }>;
}
export async function adoptMergedWorktrees(opts: {
repoPath?: string; // defaults to process.cwd()
dataDirectory?: string; // defaults to DATA_DIR
dryRun?: boolean;
onlyBranch?: string; // manual override for squash-merge case
}): Promise<AdoptionResult>;
```
### Implementation outline
Mirror `runOneTimeCwdRemap` in `ProcessManager.ts:680830` for DB lifecycle (open, transaction, finally-close). Add Chroma sync step after SQL commit.
1. **Resolve main repo path**
- `const mainRepo = execSync('git rev-parse --git-common-dir', { cwd: opts.repoPath ?? process.cwd() })` — strip `/.git` suffix to get the working tree root.
- This pattern is used in `scripts/cwd-remap.ts:4851`. Copy that handling verbatim.
2. **Resolve parent project name**
- `const parentProject = getProjectContext(mainRepo).primary` — imported from `src/utils/project-name.ts`.
3. **Enumerate worktrees**
- `git -C <mainRepo> worktree list --porcelain` → parse `worktree <path>`, `branch refs/heads/<name>` lines.
- Filter out the main worktree entry (its path equals `mainRepo`).
4. **Classify as merged**
- If `opts.onlyBranch` provided: include only that branch (squash-merge escape hatch).
- Else: `git -C <mainRepo> branch --merged HEAD --format='%(refname:short)'` → intersect with worktree branch list.
5. **Resolve worktree project names**
- For each merged worktree path, `const worktreeProject = getProjectContext(worktreePath).primary` → yields the composite `parent/worktree` name.
6. **SQL transaction** (model on `ProcessManager.ts:745760, 808`)
- Open DB via `new Database(dbPath)` (manage own handle — must close before `dbManager.initialize()` runs).
- For each merged worktree:
- `SELECT id FROM observations WHERE project = ? AND merged_into_project IS NULL` → collect sqlite IDs to later push to Chroma.
- `UPDATE observations SET merged_into_project = ? WHERE project = ? AND merged_into_project IS NULL`.
- Same for `session_summaries`.
- Commit transaction.
- If `dryRun`, roll back instead.
7. **Chroma metadata sync** (full consistent — NOT lazy)
- For the set of sqlite IDs just stamped, call `ChromaSync.updateMergedIntoProject(sqliteIds, parentProject)`.
- `ChromaSync.updateMergedIntoProject` implementation:
```typescript
async updateMergedIntoProject(sqliteIds: number[], mergedIntoProject: string): Promise<void> {
if (sqliteIds.length === 0) return;
// Batch: look up Chroma doc IDs via metadata filter on sqlite_id, then patch.
const where = { sqlite_id: { $in: sqliteIds } };
const existing = await chromaMcp.callTool('chroma_get_documents', {
collection_name: this.collectionName,
where,
include: ['metadatas']
});
const docIds: string[] = existing.ids ?? [];
const metadatas: Record<string, unknown>[] = (existing.metadatas ?? []).map(m => ({
...m,
merged_into_project: mergedIntoProject
}));
if (docIds.length === 0) return;
await chromaMcp.callTool('chroma_update_documents', {
collection_name: this.collectionName,
ids: docIds,
metadatas
});
}
```
- On Chroma error: log via `logger.error('CHROMA_SYNC', ...)`, increment `chromaFailed`, but do NOT roll back SQL. SQL is source of truth; a subsequent run will retry the Chroma patch (idempotent — metadata set to same value is a no-op).
8. **Logging**
- `logger.info('SYSTEM', 'Worktree adoption applied', { parentProject, adoptedObservations, adoptedSummaries, chromaUpdates, chromaFailed, mergedBranches })`.
- On per-worktree error: `logger.warn('SYSTEM', 'Worktree adoption skipped branch', { worktree, error })` — collect in `errors[]`, continue.
9. **Re-adoption safety net**
- Because Chroma updates can fail independently, add a secondary SQL-side reconciliation: on each adoption run, also find `observations WHERE merged_into_project IS NOT NULL` whose Chroma metadata lacks the field. Run the same `updateMergedIntoProject` on that delta.
- Keep this bounded: only reconcile rows adopted in the last N days (e.g. 30) to avoid full-table scans.
### Verification
- Dry-run against a repo with one known-merged worktree: result shows correct `adoptedObservations`, DB unchanged, no Chroma writes.
- Real run: `SELECT COUNT(*) FROM observations WHERE merged_into_project IS NOT NULL` matches `adoptedObservations`.
- Chroma: `chroma_get_documents` with `where: { merged_into_project: 'claude-mem' }` returns the same row count.
- Re-run: `adoptedObservations = 0`, `chromaUpdates = 0` (both idempotent).
- Simulate Chroma outage (stop chroma): adoption logs `CHROMA_SYNC` error, `chromaFailed > 0`, SQL still stamps. Next run with Chroma back up reconciles the delta.
### Anti-pattern guards
- Do NOT rollback SQL on Chroma failure. SQL is authoritative; Chroma is a derived index.
- Do NOT call Chroma per-row. Batch by sqlite_id set to minimize round-trips.
- Do NOT adopt branches not in `git branch --merged HEAD` unless `onlyBranch` override is explicit.
- Do NOT touch observations whose `project` is not a composite worktree name. The worktree-name match is the safety gate.
- Do NOT skip the `merged_into_project IS NULL` clause on UPDATE — this is what makes the run idempotent.
---
## Phase 3 — Query plumbing (SQLite + Chroma $or)
**What to implement**: Extend the two multi-project read queries in `ObservationCompiler.ts` and the Chroma filter in `SearchManager.ts` to treat `merged_into_project` as a second match axis. Direct Chroma `$or` filter — no SQL-side expansion dance.
### Files touched
- `src/services/context/ObservationCompiler.ts`
- `src/services/sync/SearchManager.ts`
### 3a. SQLite WHERE-clause extension
`src/services/context/ObservationCompiler.ts:111160` (`queryObservationsMulti`): change
```sql
WHERE o.project IN (${projectPlaceholders})
```
to
```sql
WHERE (o.project IN (${projectPlaceholders})
OR o.merged_into_project IN (${projectPlaceholders}))
```
Double-bind the `projects` array:
```typescript
.all(
...projects, // for o.project IN (...)
...projects, // for o.merged_into_project IN (...)
...typeArray,
...conceptArray,
...(platformSource ? [platformSource] : []),
config.totalObservationCount
)
```
`src/services/context/ObservationCompiler.ts:168196` (summary variant): apply the same extension, using `ss.merged_into_project`.
### 3b. Chroma filter extension
`src/services/sync/SearchManager.ts:174177`:
```typescript
if (options.project) {
const projectFilter = {
$or: [
{ project: options.project },
{ merged_into_project: options.project }
]
};
whereFilter = whereFilter
? { $and: [whereFilter, projectFilter] }
: projectFilter;
}
```
When `options.project` is an array (if that path exists — grep first), build a flat `$or` over both fields × all requested projects.
### 3c. New-observation Chroma metadata
`src/services/sync/ChromaSync.ts:132140` — extend `baseMetadata`:
```typescript
const baseMetadata: Record<string, string | number | null> = {
sqlite_id: obs.id,
doc_type: 'observation',
memory_session_id: obs.memory_session_id,
project: obs.project,
merged_into_project: obs.merged_into_project ?? null, // NEW
created_at_epoch: obs.created_at_epoch,
type: obs.type || 'discovery',
title: obs.title || 'Untitled'
};
```
This makes every new observation Chroma-compatible with the Phase 3b filter from the first sync. For existing rows, Phase 2's adoption engine patches metadata retroactively.
**Check Chroma metadata type constraints**: Chroma rejects `null` in metadata — confirm via a quick test. If `null` is rejected, OMIT the field when unset (use `if (obs.merged_into_project) baseMetadata.merged_into_project = obs.merged_into_project;`).
### 3d. ContextBuilder compatibility check
`src/services/context/ContextBuilder.ts:126183` — no change needed. `projects = input?.projects ?? context.allProjects` stays as-is; the extended WHERE clause in Phase 3a does all the work.
### Verification
- Before adoption: context-inject API for `claude-mem` returns N observations.
- After adoption of `claude-mem/dar-es-salaam`: API returns N + M (M = count of dar-es-salaam's own observations).
- Semantic search via Chroma (`/search` endpoint or MCP) with `project=claude-mem` returns dar-es-salaam-origin rows too.
- Worktree-local queries (`projects=[claude-mem, claude-mem/dar-es-salaam]`) still return `[parent + own]` unchanged.
- SQL EXPLAIN on the extended WHERE shows it uses `idx_observations_project` OR `idx_observations_merged_into` (both indices hit).
### Anti-pattern guards
- Do NOT lose the `o.project` filter — it's still required (merged-row predicate is additive, not a replacement).
- Do NOT forget to double-bind `projects` in the prepared statement — placeholder count must match argument count.
- Do NOT add a subquery or JOIN for merged discovery. A flat `OR` + index is faster.
- Do NOT write `null` into Chroma metadata if Chroma rejects it. Use the "omit if unset" pattern.
---
## Phase 4 — Automatic trigger on worker startup
**What to implement**: Call `adoptMergedWorktrees()` during worker startup, immediately after `runOneTimeCwdRemap()`. **Not** marker-gated — it runs every worker startup because git state evolves and the engine is idempotent.
### Files touched
- `src/services/worker-service.ts`
### Implementation
Import alongside existing `ProcessManager` imports at lines 4153:
```typescript
import { adoptMergedWorktrees } from './infrastructure/WorktreeAdoption.js';
```
Insert immediately after the existing `runOneTimeCwdRemap()` call at lines 363365:
```typescript
runOneTimeCwdRemap();
try {
const result = await adoptMergedWorktrees({});
if (result.adoptedObservations > 0 || result.chromaUpdates > 0) {
logger.info('SYSTEM', 'Merged worktrees adopted on startup', result);
}
if (result.errors.length > 0) {
logger.warn('SYSTEM', 'Worktree adoption had per-branch errors', { errors: result.errors });
}
} catch (err) {
logger.error('SYSTEM', 'Worktree adoption failed (non-fatal)', {}, err as Error);
}
```
**DB lifecycle note**: `adoptMergedWorktrees` must manage its own DB handle (open + close) before `dbManager.initialize()` runs at line 380. Mirror `runOneTimeCwdRemap`'s finally-block pattern.
### Verification
- Restart worker. Log shows "Merged worktrees adopted on startup" only on first run after a new merge lands.
- Subsequent restarts log nothing (idempotent).
- Simulate adoption exception (e.g., rename git temporarily): log shows error, worker startup continues successfully.
- Build-and-sync restart picks up new merges without manual intervention.
### Anti-pattern guards
- Do NOT block worker startup on adoption failure. Wrap in try/catch; swallow + log.
- Do NOT run adoption after `dbManager.initialize()`. The engine manages its own DB handle; two handles at once risk lock contention.
- Do NOT await Chroma sync before returning SQL success. Internally, yes; but don't make worker startup hang on Chroma I/O — cap with a reasonable timeout inside the engine.
---
## Phase 5 — CLI escape hatch
**What to implement**: `claude-mem adopt [--branch <name>] [--dry-run]` — covers squash-merge where `git branch --merged` returns nothing, and provides a manual override for any adoption run.
### Files touched
- `src/npx-cli/commands/adopt.ts` (new)
- `src/npx-cli/index.ts` (add `case 'adopt'`)
- `scripts/adopt-worktrees.ts` (new, optional — admin script for bulk ops)
### 5a. Command module
`src/npx-cli/commands/adopt.ts` — follow shape of sibling commands (dynamic-imported by the switch):
```typescript
import pc from 'picocolors';
import { adoptMergedWorktrees } from '../../services/infrastructure/WorktreeAdoption.js';
export interface AdoptCommandOptions {
dryRun?: boolean;
onlyBranch?: string;
}
export async function runAdoptCommand(opts: AdoptCommandOptions): Promise<void> {
const result = await adoptMergedWorktrees({
dryRun: opts.dryRun,
onlyBranch: opts.onlyBranch
});
console.log(pc.bold(`\nWorktree adoption ${result.dryRun ? pc.yellow('(dry-run)') : pc.green('(applied)')}`));
console.log(` Parent project: ${result.parentProject}`);
console.log(` Worktrees scanned: ${result.scannedWorktrees}`);
console.log(` Merged branches: ${result.mergedBranches.join(', ') || '(none)'}`);
console.log(` Observations adopted: ${result.adoptedObservations}`);
console.log(` Summaries adopted: ${result.adoptedSummaries}`);
console.log(` Chroma docs updated: ${result.chromaUpdates}`);
if (result.chromaFailed > 0) {
console.log(pc.yellow(` Chroma sync failures: ${result.chromaFailed} (will retry on next run)`));
}
for (const err of result.errors) {
console.log(pc.red(` ! ${err.worktree}: ${err.error}`));
}
}
```
### 5b. CLI switch
`src/npx-cli/index.ts` — add between existing cases, following the pattern at lines 28169:
```typescript
case 'adopt': {
const dryRun = args.includes('--dry-run');
const branchIndex = args.indexOf('--branch');
const onlyBranch = branchIndex !== -1 ? args[branchIndex + 1] : undefined;
const { runAdoptCommand } = await import('./commands/adopt.js');
await runAdoptCommand({ dryRun, onlyBranch });
break;
}
```
### 5c. Admin script (optional)
`scripts/adopt-worktrees.ts` — Bun shebang script for users without the plugin installed. Model on `scripts/cwd-remap.ts:1186`. Default: dry-run. Pass `--apply` to commit.
### Verification
- `npx claude-mem adopt --dry-run` in a repo with merged worktrees prints what WOULD be adopted without writing.
- `npx claude-mem adopt` writes + prints counts.
- `npx claude-mem adopt --branch feature/foo` forces adoption of that branch even if `git branch --merged` doesn't include it (squash case).
- `bun scripts/adopt-worktrees.ts --apply` equivalent to the CLI.
- Help text / unknown command still reports the existing error (CLI pattern preserved).
### Anti-pattern guards
- Do NOT require running from the worktree. Detection always resolves up to the common-dir, regardless of cwd.
- Do NOT default to `--apply`. Dry-run first matches `scripts/cwd-remap.ts` ergonomics.
- Do NOT introduce `commander`, `yargs`, `cac`. Stay with the existing hand-rolled parser.
---
## Phase 6 — UI surfacing
**What to implement**: When the viewer shows an observation in a parent-project context that originated in a merged worktree, display a "merged from <worktree>" badge so provenance is visible. Keep the original `project` field rendered too.
### Files touched
- `src/ui/viewer/components/ObservationCard.tsx`
- Type definition for `Observation` — wherever `.project` is declared, add `merged_into_project?: string | null`.
- Observation serializer on the worker → UI path (grep for `doc_type: 'observation'` or `serializeObservation` to find it).
- CSS file for ObservationCard styles.
### Implementation
Locate the current label render at `src/ui/viewer/components/ObservationCard.tsx:58`:
```tsx
<span className="card-project">{observation.project}</span>
```
Extend to:
```tsx
<span className="card-project">{observation.project}</span>
{observation.merged_into_project && (
<span className="card-merged-badge" title={`Merged into ${observation.merged_into_project}`}>
merged → {observation.merged_into_project}
</span>
)}
```
Add CSS for `.card-merged-badge` — subtle secondary chip style (muted color, smaller font). Match existing `.card-source` / `.card-project` aesthetics.
### Verification
- After adoption, open viewer at `http://localhost:37777`, select the parent project. Merged observations show both their origin worktree name AND the "merged →" badge.
- Worktree view (if still addressable) shows no badge (badge only renders when `merged_into_project` is set; a worktree viewing its own observations would not see it, since in that view `merged_into_project` is the PARENT name, not the current project).
- Hover tooltip shows full target project name.
### Anti-pattern guards
- Do NOT hide merged observations in the parent view. The goal is visibility.
- Do NOT replace `project` display with `merged_into_project`. Both are meaningful: `project` = origin, `merged_into_project` = current home.
- Do NOT require a UI setting toggle to show the badge. Default on.
---
## Phase 7 — Verification pass
### Unit tests
- `adoptMergedWorktrees({ dryRun: true })` against a fixture repo with `[merged, unmerged, squash-merged]` worktrees → classification matches expectation.
- `ChromaSync.updateMergedIntoProject` on an empty `sqliteIds` array → no-op, no Chroma call.
- Extended `queryObservationsMulti` with a mixed set of `project` and `merged_into_project` matches → returns union, sorted by `created_at_epoch DESC`.
### Integration tests
- Start worker → create synthetic observations under `claude-mem/test-wt` → simulate branch merge (`git merge`) → restart worker → context-inject API for `claude-mem` returns test-wt observations.
- Same flow with a squash-merge → auto-adoption misses → run `claude-mem adopt --branch test-wt` → API now returns them.
- Re-run `claude-mem adopt` twice: second run reports `adoptedObservations: 0, chromaUpdates: 0`.
### Anti-pattern grep checks
Run before landing:
```bash
# No one renamed the project field
rg "UPDATE observations SET project" src/
# (Expected: zero hits other than the existing CWD remap)
# Adoption only touches via IS NULL guard
rg "merged_into_project" src/ -C2
# (Expected: all UPDATE sites include "IS NULL" predicate)
# CLI registered
rg "case 'adopt'" src/npx-cli/index.ts
# (Expected: one hit)
# Chroma metadata extension present
rg "merged_into_project" src/services/sync/ChromaSync.ts
# (Expected: hits in baseMetadata and updateMergedIntoProject)
# No gh CLI introduced
rg "\\bgh\\s+(pr|issue|api)" src/ scripts/
# (Expected: zero hits outside .github/workflows/)
```
### Documentation cross-check
- ObservationCompiler WHERE clause matches the shape used by the shipped worktree-reads-parent feature — both clauses symmetric, visible in a single read of the file.
- Chroma metadata field name `merged_into_project` matches SQLite column name exactly (no `mergedIntoProject`, `merged_project`, etc.).
- CLI `--branch` flag accepts the same format as worktree composite names.
---
## Summary
| Phase | Files touched | New LOC (approx.) |
|---|---|---|
| 1. Schema | `src/services/sqlite/migrations/runner.ts` | ~25 |
| 2. Adoption engine | `src/services/infrastructure/WorktreeAdoption.ts` (new), `src/services/sync/ChromaSync.ts` (new method) | ~200 |
| 3. Query plumbing | `src/services/context/ObservationCompiler.ts`, `src/services/sync/SearchManager.ts`, `src/services/sync/ChromaSync.ts` | ~40 |
| 4. Auto-trigger | `src/services/worker-service.ts` | ~15 |
| 5. CLI | `src/npx-cli/commands/adopt.ts` (new), `src/npx-cli/index.ts`, `scripts/adopt-worktrees.ts` (new) | ~100 |
| 6. UI | `src/ui/viewer/components/ObservationCard.tsx`, Observation type, serializer, CSS | ~20 |
| 7. Tests + verification | scattered | — |
| **Total** | | **~400 LOC** |
**Reversibility**: `UPDATE observations SET merged_into_project = NULL` + a Chroma `update_documents` call with the field omitted restores pre-adoption state completely. Nothing is destroyed.
**Architecture fit**: Mirrors the just-shipped CWD remap migration (`runOneTimeCwdRemap`) for structure, lifecycle, and logging conventions. Chroma metadata sync matches the existing per-observation attach pattern.
**Blast radius**: Zero risk to existing data (no writes to `project` field). Chroma additions are metadata-only (embeddings untouched). Query extensions are additive OR clauses — existing queries still return what they did.
+137
View File
@@ -0,0 +1,137 @@
# 내 저장소 설정
- Git 서버: Gitea (자체 NAS 운영)
- Gitea URL: https://gitea.gru.farm/
- 계정: airkjw
- 저장소: Codex-mem
- Remote: https://gitea.gru.farm/airkjw/Codex-mem
- 토큰: 83b491bd64ffbf32c88e6e93abe5482422dc1488
## 업데이트
- AGENTS.md 파일 로드시, 원본 레포 체크해서 업데이트 확인
- 원본 레포: https://github.com/thedotmack/Codex-mem
---
# Codex-Mem: AI Development Instructions
Codex-mem is a Codex plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Codex Agent SDK, and injects relevant context into future sessions.
## Architecture
**6 Lifecycle Hooks**: Setup → SessionStart → UserPromptSubmit → PreToolUse (Read) → PostToolUse → Stop
**Hooks** - Entries in `plugin/hooks/hooks.json` dispatch to the unified worker (`plugin/scripts/worker-service.cjs`, built from `src/services/worker-service.ts` via `scripts/build-hooks.js`) through `bun-runner.js`, invoking subcommands like `context`, `session-init`, `observation`, `file-context`, and `summarize`. The Setup-phase `version-check.js` is the only standalone hook script.
**Worker Service** (`src/services/worker-service.ts`) - Express API on the per-user worker port (default `37700 + (uid % 100)`, configurable via `CLAUDE_MEM_WORKER_PORT`), Bun-managed, handles AI processing asynchronously
**Database** (`src/services/sqlite/`) - SQLite3 at `~/.Codex-mem/Codex-mem.db`
**Search Skill** (`plugin/skills/mem-search/SKILL.md`) - HTTP API for searching past work, auto-invoked when users ask about history
**Planning Skill** (`plugin/skills/make-plan/SKILL.md`) - Orchestrator instructions for creating phased implementation plans with documentation discovery
**Execution Skill** (`plugin/skills/do/SKILL.md`) - Orchestrator instructions for executing phased plans using subagents
**Chroma** (`src/services/sync/ChromaSync.ts`) - Vector embeddings for semantic search
**Viewer UI** (`src/ui/viewer/`) - React interface served by the worker on its configured port (default `http://127.0.0.1:<worker-port>`), built to `plugin/ui/viewer.html`
## Privacy Tags
- `<private>content</private>` - User-level privacy control (manual, prevents storage)
**Implementation**: Tag stripping happens at hook layer (edge processing) before data reaches worker/database. See `src/utils/tag-stripping.ts` for shared utilities.
## Build Commands
```bash
npm run build-and-sync # Build, sync to marketplace, restart worker
```
## Configuration
Settings are managed in `~/.Codex-mem/settings.json`. The file is auto-created with defaults on first run.
## Multi-account
Codex-mem supports running multiple isolated profiles on the same machine (e.g. work vs personal accounts) via environment variables. No CLI subcommand needed — set the env vars in the shell where you run Codex.
- **Switch profiles per shell:** Set `CLAUDE_MEM_DATA_DIR=<path>` and every Codex-mem path (database, chroma, logs, settings.json, worker.pid, transcripts config) derives from it. Example:
```bash
export CLAUDE_MEM_DATA_DIR="$HOME/.Codex-mem-work"
```
- **Port collisions are auto-handled:** The default worker port is `37700 + (uid % 100)`, so two different OS users on the same box get different ports for free. If you want fixed ports per profile (e.g. you run two profiles as the same UID), set `CLAUDE_MEM_WORKER_PORT` too:
```bash
export CLAUDE_MEM_WORKER_PORT=37800
```
- **All paths and ports derive from these two env vars.** Hooks, npx-cli (`install`/`uninstall`/`start`/`search`), the OpenCode plugin, the OpenClaw installer, and the timeline-report skill all honor them. The settings file itself lives at `$CLAUDE_MEM_DATA_DIR/settings.json`.
- See `src/shared/SettingsDefaultsManager.ts` for the canonical port/data-dir defaults and `plugin/skills/timeline-report/SKILL.md` for the shell snippet that resolves the port for arbitrary skills.
## File Locations
- **Source**: `<project-root>/src/`
- **Built Plugin**: `<project-root>/plugin/`
- **Installed Plugin**: `~/.Codex/plugins/marketplaces/thedotmack/`
- **Database**: `~/.Codex-mem/Codex-mem.db`
- **Chroma**: `~/.Codex-mem/chroma/`
## Exit Code Strategy
Codex-mem hooks use specific exit codes per Codex's hook contract:
- **Exit 0**: Success or graceful shutdown (Windows Terminal closes tabs)
- **Exit 1**: Non-blocking error (stderr shown to user, continues)
- **Exit 2**: Blocking error (stderr fed to Codex for processing)
**Philosophy**: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics.
## Requirements
- **Bun** (all platforms - auto-installed if missing)
- **uv** (all platforms - auto-installed if missing, provides Python for Chroma)
- Node.js
## Documentation
**Public Docs**: https://docs.Codex-mem.ai (Mintlify)
**Source**: `docs/public/` - MDX files, edit `docs.json` for navigation
**Deploy**: Auto-deploys from GitHub on push to main
## Pro Features Architecture
Codex-mem is designed with a clean separation between open-source core functionality and optional Pro features.
**Open-Source Core** (this repository):
- All local worker HTTP API endpoints (per-user port — see Architecture above) remain fully open and accessible
- Pro features are headless - no proprietary UI elements in this codebase
- Pro integration points are minimal: settings for license keys, tunnel provisioning logic
- The architecture ensures Pro features extend rather than replace core functionality
**Pro Features** (coming soon, external):
- Enhanced UI (Memory Stream) connects to the same local worker endpoints as the open viewer
- Additional features like advanced filtering, timeline scrubbing, and search tools
- Access gated by license validation, not by modifying core endpoints
- Users without Pro licenses continue using the full open-source viewer UI without limitation
This architecture preserves the open-source nature of the project while enabling sustainable development through optional paid features.
## Important
No need to edit the changelog ever, it's generated automatically.
## Daily Maintenance
Run a daily version check across all package manifests and upgrade every dependency to its latest version — including major version bumps. Staying on the latest is the goal; do not skip majors.
- Check `package.json` (root) and all nested `package.json` files (e.g. `plugin/`, `openclaw/`) for outdated dependencies via `npm outdated`.
- Upgrade every package to `latest` (use `npm install <pkg>@latest` for each, or `npx npm-check-updates -u && npm install`). Bump majors too.
- Run `npm audit fix` to resolve advisories.
- After upgrades, run `npm run build-and-sync` and verify the worker starts and tests pass. Fix any breakage caused by major bumps in the same change.
- Commit the updated `package.json` and `package-lock.json` files.
+1042 -109
View File
File diff suppressed because it is too large Load Diff
+51 -8
View File
@@ -1,14 +1,29 @@
# 내 저장소 설정
- Git 서버: Gitea (자체 NAS 운영)
- Gitea URL: https://gitea.gru.farm/
- 계정: airkjw
- 저장소: claude-mem
- Remote: https://gitea.gru.farm/airkjw/claude-mem
- 토큰: 83b491bd64ffbf32c88e6e93abe5482422dc1488
## 업데이트
- claude.md 파일 로드시, 원본 레포 체크해서 업데이트 확인
- 원본 레포: https://github.com/thedotmack/claude-mem
---
# Claude-Mem: AI Development Instructions
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.
## Architecture
**5 Lifecycle Hooks**: SessionStart → UserPromptSubmit → PostToolUse → Summary → SessionEnd
**6 Lifecycle Hooks**: Setup → SessionStart → UserPromptSubmit → PreToolUse (Read) → PostToolUse → Stop
**Hooks** (`src/hooks/*.ts`) - TypeScript → ESM, built to `plugin/scripts/*-hook.js`
**Hooks** - Entries in `plugin/hooks/hooks.json` dispatch to the unified worker (`plugin/scripts/worker-service.cjs`, built from `src/services/worker-service.ts` via `scripts/build-hooks.js`) through `bun-runner.js`, invoking subcommands like `context`, `session-init`, `observation`, `file-context`, and `summarize`. The Setup-phase `version-check.js` is the only standalone hook script.
**Worker Service** (`src/services/worker-service.ts`) - Express API on port 37777, Bun-managed, handles AI processing asynchronously
**Worker Service** (`src/services/worker-service.ts`) - Express API on the per-user worker port (default `37700 + (uid % 100)`, configurable via `CLAUDE_MEM_WORKER_PORT`), Bun-managed, handles AI processing asynchronously
**Database** (`src/services/sqlite/`) - SQLite3 at `~/.claude-mem/claude-mem.db`
@@ -20,7 +35,7 @@ Claude-mem is a Claude Code plugin providing persistent memory across sessions.
**Chroma** (`src/services/sync/ChromaSync.ts`) - Vector embeddings for semantic search
**Viewer UI** (`src/ui/viewer/`) - React interface at http://localhost:37777, built to `plugin/ui/viewer.html`
**Viewer UI** (`src/ui/viewer/`) - React interface served by the worker on its configured port (default `http://127.0.0.1:<worker-port>`), built to `plugin/ui/viewer.html`
## Privacy Tags
- `<private>content</private>` - User-level privacy control (manual, prevents storage)
@@ -37,6 +52,26 @@ npm run build-and-sync # Build, sync to marketplace, restart worker
Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created with defaults on first run.
## Multi-account
Claude-mem supports running multiple isolated profiles on the same machine (e.g. work vs personal accounts) via environment variables. No CLI subcommand needed — set the env vars in the shell where you run Claude Code.
- **Switch profiles per shell:** Set `CLAUDE_MEM_DATA_DIR=<path>` and every claude-mem path (database, chroma, logs, settings.json, worker.pid, transcripts config) derives from it. Example:
```bash
export CLAUDE_MEM_DATA_DIR="$HOME/.claude-mem-work"
```
- **Port collisions are auto-handled:** The default worker port is `37700 + (uid % 100)`, so two different OS users on the same box get different ports for free. If you want fixed ports per profile (e.g. you run two profiles as the same UID), set `CLAUDE_MEM_WORKER_PORT` too:
```bash
export CLAUDE_MEM_WORKER_PORT=37800
```
- **All paths and ports derive from these two env vars.** Hooks, npx-cli (`install`/`uninstall`/`start`/`search`), the OpenCode plugin, the OpenClaw installer, and the timeline-report skill all honor them. The settings file itself lives at `$CLAUDE_MEM_DATA_DIR/settings.json`.
- See `src/shared/SettingsDefaultsManager.ts` for the canonical port/data-dir defaults and `plugin/skills/timeline-report/SKILL.md` for the shell snippet that resolves the port for arbitrary skills.
## File Locations
- **Source**: `<project-root>/src/`
@@ -55,8 +90,6 @@ Claude-mem hooks use specific exit codes per Claude Code's hook contract:
**Philosophy**: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics.
See `private/context/claude-code/exit-codes.md` for full hook behavior matrix.
## Requirements
- **Bun** (all platforms - auto-installed if missing)
@@ -75,14 +108,14 @@ Claude-mem is designed with a clean separation between open-source core function
**Open-Source Core** (this repository):
- All worker API endpoints on localhost:37777 remain fully open and accessible
- All local worker HTTP API endpoints (per-user port — see Architecture above) remain fully open and accessible
- Pro features are headless - no proprietary UI elements in this codebase
- Pro integration points are minimal: settings for license keys, tunnel provisioning logic
- The architecture ensures Pro features extend rather than replace core functionality
**Pro Features** (coming soon, external):
- Enhanced UI (Memory Stream) connects to the same localhost:37777 endpoints as the open viewer
- Enhanced UI (Memory Stream) connects to the same local worker endpoints as the open viewer
- Additional features like advanced filtering, timeline scrubbing, and search tools
- Access gated by license validation, not by modifying core endpoints
- Users without Pro licenses continue using the full open-source viewer UI without limitation
@@ -92,3 +125,13 @@ This architecture preserves the open-source nature of the project while enabling
## Important
No need to edit the changelog ever, it's generated automatically.
## Daily Maintenance
Run a daily version check across all package manifests and upgrade every dependency to its latest version — including major version bumps. Staying on the latest is the goal; do not skip majors.
- Check `package.json` (root) and all nested `package.json` files (e.g. `plugin/`, `openclaw/`) for outdated dependencies via `npm outdated`.
- Upgrade every package to `latest` (use `npm install <pkg>@latest` for each, or `npx npm-check-updates -u && npm install`). Bump majors too.
- Run `npm audit fix` to resolve advisories.
- After upgrades, run `npm run build-and-sync` and verify the worker starts and tests pass. Fix any breakage caused by major bumps in the same change.
- Commit the updated `package.json` and `package-lock.json` files.
+59
View File
@@ -0,0 +1,59 @@
FROM ubuntu:24.04
ARG NODE_VERSION=20
ENV DEBIAN_FRONTEND=noninteractive
ENV TERM=xterm-256color
ENV COLORTERM=truecolor
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
bash \
git \
build-essential \
python3 \
unzip \
jq \
sudo \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
RUN node -v && npm -v
WORKDIR /workspace
RUN cat > /root/.bashrc <<'EOF'
export PS1='\[\033[1;36m\]cmem-test\[\033[0m\]:\[\033[1;33m\]\w\[\033[0m\]\$ '
cat <<'BANNER'
=====================================================================
claude-mem installer test sandbox (clean Linux, no Bun, no uv)
=====================================================================
Try the new installer interactively:
node dist/npx-cli/index.js install --no-auto-start
Or just the runtime setup module via repair:
node dist/npx-cli/index.js repair
After install, verify the Setup hook is fast:
time node ~/.claude/plugins/cache/thedotmack/claude-mem/$(jq -r .version package.json)/scripts/version-check.js
Container HOME=/root is isolated — nothing here touches your real
~/.claude or ~/.claude-mem. Exit with Ctrl-D.
=====================================================================
BANNER
EOF
# bash --login reads ~/.bash_profile (or ~/.profile), not ~/.bashrc, so
# without this the banner above never runs in the container's CMD shell.
RUN printf '%s\n' '[[ -f ~/.bashrc ]] && . ~/.bashrc' > /root/.bash_profile
CMD ["bash", "--login"]
+202 -630
View File
@@ -1,630 +1,202 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+8
View File
@@ -0,0 +1,8 @@
Claude-Mem
Copyright 2026 Alex Newman
This product includes software developed for the Claude-Mem project.
Licensed under the Apache License, Version 2.0.
If other attributions are required by dependencies or included code, add them here.
+10 -13
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -385,20 +385,17 @@ See [Development Guide](https://docs.claude-mem.ai/development) for contribution
## License
This project is licensed under the **GNU Affero General Public License v3.0** (AGPL-3.0).
Claude-Mem is licensed under the Apache License 2.0.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
We chose Apache-2.0 because durable agentic memory should be easy to embed in
developer tools, local agents, MCP servers, enterprise systems, robotics stacks,
and production agent harnesses.
See the [LICENSE](LICENSE) file for full details.
See the [LICENSE](LICENSE) file for full details. See [docs/license.md](docs/license.md)
and [docs/ip-boundary.md](docs/ip-boundary.md) for licensing scope and the
open/commercial boundary.
**What This Means:**
- You can use, modify, and distribute this software freely
- If you modify and deploy on a network server, you must make your source code available
- Derivative works must also be licensed under AGPL-3.0
- There is NO WARRANTY for this software
**Note on Ragtime**: The `ragtime/` directory is licensed separately under the **PolyForm Noncommercial License 1.0.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
**Note on Ragtime**: The `ragtime/` directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
@@ -413,7 +410,7 @@ See the [LICENSE](LICENSE) file for full details.
---
**Built with Claude Agent SDK** | **Powered by Claude Code** | **Made with TypeScript**
**Built with Claude Agent SDK** | **Works with Claude Code** | **Made with TypeScript**
---
+206
View File
@@ -0,0 +1,206 @@
# Security Policy
## Supported Versions
Only the latest released version of `claude-mem` receives security updates. Please upgrade to the latest version before reporting a vulnerability.
| Version | Supported |
| ------- | ------------------ |
| latest | :white_check_mark: |
| older | :x: |
## Reporting a Vulnerability
If you discover a security vulnerability in claude-mem, please report it by:
1. **DO NOT** create a public GitHub issue, pull request, or discussion
2. Email **alex@cmem.ai** with details, OR use GitHub's "Report a vulnerability" button under the Security tab to open a private security advisory
3. Include steps to reproduce, impact assessment, affected version(s), and suggested fixes if possible
**Scope:** This policy covers the `claude-mem` plugin and its bundled components (hooks, worker service, SQLite/Chroma sync, viewer UI, search/planning skills). Issues in upstream dependencies should be reported to those projects directly, but feel free to flag them to us as well.
We take security seriously, will acknowledge valid reports within 48 hours, and aim to ship a fix in the next release.
## Security Measures
### Command Injection Prevention
Claude-mem executes system commands for git operations and process management. We have implemented comprehensive protections against command injection:
#### Safe Command Execution
- **Array-based Arguments:** All commands use array-based arguments to prevent shell interpretation
- **No Shell Execution:** `shell: false` is explicitly set for all spawn operations involving user input
- **Input Validation:** All user-controlled parameters are validated before use
#### Example Safe Pattern
```typescript
// ✅ SAFE: Array-based arguments with validation
if (!isValidBranchName(userInput)) {
throw new Error('Invalid input');
}
spawnSync('git', ['checkout', userInput], { shell: false });
// ❌ UNSAFE: Never do this
execSync(`git checkout ${userInput}`);
```
### Input Validation
All user-controlled inputs are validated using whitelists and strict patterns:
- **Branch Names:** Must match `/^[a-zA-Z0-9][a-zA-Z0-9._/-]*$/` and not contain `..`
- **Port Numbers:** Must be numeric and within range 1024-65535
- **File Paths:** All paths are joined using `path.join()` to prevent traversal
### Process Management
- **PID File Protection:** Process IDs are stored in user's data directory (`~/.claude-mem/`)
- **Port Validation:** Worker port is validated before binding
- **Health Checks:** Worker health is verified before processing requests
### Privacy Controls
Claude-mem includes dual-tag system for content privacy:
- `<private>content</private>` - User-level privacy (prevents storage)
- `<claude-mem-context>content</claude-mem-context>` - System-level tag (prevents recursive storage)
Tags are stripped at the hook layer before data reaches worker/database.
## Security Audit History
### 2025-12-16: Command Injection Vulnerability (Issue #354)
- **Severity:** CRITICAL
- **Status:** RESOLVED
- **Affected Versions:** All versions prior to fix
- **Fixed In:** Current version
- **Vulnerabilities Found:** 3
- **Vulnerabilities Fixed:** 3
**Summary of Fixes:**
1. Replaced string interpolation with array-based arguments in `BranchManager.ts`
2. Added `isValidBranchName()` validation function
3. Removed unnecessary shell usage in `bun-path.ts`
4. Created comprehensive security test suite
## Security Best Practices for Contributors
### When Adding Command Execution
1. **NEVER use shell with user input:**
```typescript
// ❌ NEVER
execSync(`command ${userInput}`);
spawn('command', [...], { shell: true });
// ✅ ALWAYS
spawnSync('command', [userInput], { shell: false });
```
2. **ALWAYS validate user input:**
```typescript
if (!isValidInput(userInput)) {
throw new Error('Invalid input');
}
```
3. **Use array-based arguments:**
```typescript
// ❌ NEVER
execSync(`git ${command} ${arg}`);
// ✅ ALWAYS
spawnSync('git', [command, arg], { shell: false });
```
4. **Explicitly set shell: false:**
```typescript
spawnSync('command', args, { shell: false });
```
### When Adding User Input
1. **Whitelist validation** over blacklist
2. **Strict regex patterns** for format validation
3. **Type checking** for expected data types
4. **Range validation** for numeric inputs
5. **Length limits** for string inputs
### Code Review Checklist
Before submitting a PR with command execution or user input handling:
- [ ] No `execSync` with string interpolation or template literals
- [ ] No `shell: true` when user input is involved
- [ ] All spawn/spawnSync calls use array arguments
- [ ] Input validation is present for all user-controlled parameters
- [ ] Security tests are added for new attack vectors
- [ ] Code follows the safe patterns described above
## Dependencies
We regularly audit dependencies for vulnerabilities:
- **npm audit:** Run before each release
- **Dependabot:** Enabled for automatic security updates
- **Manual Review:** Critical dependencies reviewed quarterly
## Data Storage
Claude-mem stores data locally in `~/.claude-mem/`:
- **Database:** SQLite3 at `~/.claude-mem/claude-mem.db`
- **Vector Store:** Chroma at `~/.claude-mem/chroma/`
- **Logs:** `~/.claude-mem/logs/`
- **Settings:** `~/.claude-mem/settings.json`
All claude-mem state files (database, vector store, logs, settings, supervisor and PID files) are written to the local user directory and are not uploaded by claude-mem itself. Claude-mem does not collect telemetry.
However, by design claude-mem invokes upstream model providers and optional integrations to do its work, so observation/transcript/prompt content can leave the machine through those channels:
- **Claude Agent SDK** (default summarization/observation path): sends prompts and transcript context to Anthropic's API.
- **Alternate providers** (`gemini`, `openrouter`): when configured, send the same context to those providers instead.
- **Chroma MCP / `chroma-mcp`**: when enabled, computes embeddings via the configured embedding backend, which may be a remote API depending on the user's chroma-mcp configuration.
- **OAuth / keychain reads**: claude-mem reads the Claude Code OAuth token from the platform-native credential store at spawn time. The token is injected into worker subprocesses but is not transmitted by claude-mem.
- **GitHub releases / npm registry**: version-check and self-update flows fetch metadata from public registries.
Review your provider/Chroma configuration in `~/.claude-mem/settings.json` and `~/.claude-mem/.env` before sending sensitive content. Use `<private>...</private>` tags to keep specific content out of the local store.
## Permissions
Claude-mem requires:
- **File System:** Read/write to `~/.claude-mem/` and `~/.claude/plugins/`
- **Network:** HTTP server on localhost (default port 37777)
- **Process Management:** Spawn worker processes, manage PIDs
No elevated privileges (root/administrator) are required.
## Secure Defaults
- **Worker Host:** Binds to `127.0.0.1` by default (localhost only)
- **Worker Port:** User-configurable, validates range 1024-65535
- **Log Level:** INFO by default (no sensitive data in logs)
- **Privacy Tags:** Auto-strips private content before storage
## Updates
Security patches are released as soon as possible after discovery. Users should:
1. Keep claude-mem updated to the latest version
2. Monitor GitHub releases for security announcements
3. Review [CHANGELOG.md](./CHANGELOG.md) for security-related changes
## Questions?
For security-related questions (non-vulnerabilities), please:
1. Review code comments in security-critical files
2. Open a GitHub Discussion (not an Issue) for general security questions
3. For sensitive questions, email **alex@cmem.ai**
---
**Last Updated:** 2026-05-03
**Last Audit:** 2025-12-16 (Issue #354)
**Next Scheduled Audit:** 2026-09-16
-5
View File
@@ -1,7 +1,2 @@
[test]
# Force each test file into its own worker process.
# Prevents mock.module() calls (which are permanent within a worker)
# from leaking across test files in parallel runs.
# Note: smol=true increases test startup time by spawning one Bun process per file.
# See: https://github.com/thedotmack/claude-mem/issues/1299
smol = true
-6
View File
@@ -1,6 +0,0 @@
{
"scripts": {
"setup": "cp ../settings.local.json .claude/settings.local.json && npm install",
"run": "npm run build-and-sync"
}
}
-1
View File
@@ -1,3 +1,2 @@
# Ignore backup files created by sed
*.bak
+23
View File
@@ -0,0 +1,23 @@
# Phase 10 — E2E driver overlay. Adds a one-shot Node container that hits
# the server-beta HTTP service across the compose network. Pairs with
# scripts/e2e-server-beta-docker.sh.
services:
server-beta-e2e:
image: node:20-alpine
depends_on:
claude-mem-server:
condition: service_healthy
valkey:
condition: service_healthy
postgres:
condition: service_healthy
environment:
E2E_BASE_URL: http://claude-mem-server:37877
E2E_REDIS_HOST: valkey
E2E_REDIS_PORT: 6379
E2E_POSTGRES_HOST: postgres
E2E_POSTGRES_PORT: 5432
volumes:
- ./docker/e2e:/e2e:ro
working_dir: /e2e
command: ["node", "/e2e/server-beta-e2e.mjs"]
+144
View File
@@ -0,0 +1,144 @@
# Phase 10 — server-beta deployable runtime.
#
# Stack: Postgres (canonical storage) + Valkey (BullMQ queue) +
# claude-mem-server (HTTP, no generation) +
# claude-mem-worker (BullMQ generation consumer).
#
# SECURITY: This file MUST NOT be deployed unmodified to any environment
# that is reachable from the public internet, including staging behind a
# VPN where lateral movement is possible. The Postgres credentials are
# required env vars (no defaults) — start the stack with a `.env` file or
# inline `POSTGRES_USER=... POSTGRES_PASSWORD=... docker compose up`. The
# stack will refuse to start if any required secret is missing.
#
# The legacy `worker-service.cjs` runtime is NEVER spawned in this stack.
# `claude-mem-server` runs `server-beta-service.cjs --daemon`; the
# `claude-mem-worker` service runs `server-beta-service.cjs worker start`
# from the same image. Scale generation via:
# docker compose up -d --scale claude-mem-worker=N
#
# Required env vars (validated at startup by validateServerBetaEnv()):
# CLAUDE_MEM_RUNTIME=server-beta
# CLAUDE_MEM_QUEUE_ENGINE=bullmq
# CLAUDE_MEM_SERVER_DATABASE_URL=postgres://...
# CLAUDE_MEM_REDIS_URL=redis://valkey:6379
# CLAUDE_MEM_AUTH_MODE=api-key (local-dev is REJECTED inside Docker)
#
# Required secrets (no defaults — must be supplied in env or .env):
# POSTGRES_USER
# POSTGRES_PASSWORD
# POSTGRES_DB
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_USER: ${POSTGRES_USER:?POSTGRES_USER is required}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}
POSTGRES_DB: ${POSTGRES_DB:?POSTGRES_DB is required}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U \"$$POSTGRES_USER\" -d \"$$POSTGRES_DB\""]
interval: 5s
timeout: 3s
retries: 12
start_period: 5s
valkey:
image: valkey/valkey:8-alpine
# BullMQ requires noeviction; AOF gives durability across restarts.
command:
- valkey-server
- --appendonly
- "yes"
- --appendfsync
- everysec
- --maxmemory-policy
- noeviction
volumes:
- valkey-data:/data
healthcheck:
test: ["CMD", "valkey-cli", "ping"]
interval: 5s
timeout: 3s
retries: 12
claude-mem-server:
build:
context: .
dockerfile: docker/claude-mem/Dockerfile
depends_on:
postgres:
condition: service_healthy
valkey:
condition: service_healthy
environment:
CLAUDE_MEM_CONTAINER_MODE: server
CLAUDE_MEM_DOCKER: "1"
CLAUDE_MEM_RUNTIME: server-beta
CLAUDE_MEM_HOST: 0.0.0.0
CLAUDE_MEM_SERVER_HOST: 0.0.0.0
CLAUDE_MEM_SERVER_PORT: "37877"
# Legacy var some libraries still read; keep aligned with server port
# so the existing E2E driver and viewer continue to work.
CLAUDE_MEM_WORKER_HOST: 0.0.0.0
CLAUDE_MEM_WORKER_PORT: "37877"
CLAUDE_MEM_DATA_DIR: /data/claude-mem
CLAUDE_MEM_QUEUE_ENGINE: bullmq
CLAUDE_MEM_REDIS_URL: redis://valkey:6379
CLAUDE_MEM_REDIS_MODE: docker
CLAUDE_MEM_SERVER_DATABASE_URL: postgres://${POSTGRES_USER:?POSTGRES_USER is required}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@postgres:5432/${POSTGRES_DB:?POSTGRES_DB is required}
CLAUDE_MEM_AUTH_MODE: api-key
CLAUDE_MEM_CHROMA_ENABLED: "false"
# The HTTP service does not consume BullMQ jobs; the worker container
# does. This split keeps HTTP latency unaffected by provider calls.
CLAUDE_MEM_GENERATION_DISABLED: "true"
ports:
- "37877:37877"
volumes:
- claude-mem-data:/data/claude-mem
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:37877/healthz"]
interval: 10s
timeout: 3s
retries: 12
start_period: 20s
claude-mem-worker:
build:
context: .
dockerfile: docker/claude-mem/Dockerfile
depends_on:
postgres:
condition: service_healthy
valkey:
condition: service_healthy
claude-mem-server:
condition: service_healthy
environment:
CLAUDE_MEM_CONTAINER_MODE: worker
CLAUDE_MEM_DOCKER: "1"
CLAUDE_MEM_RUNTIME: server-beta
CLAUDE_MEM_DATA_DIR: /data/claude-mem
CLAUDE_MEM_QUEUE_ENGINE: bullmq
CLAUDE_MEM_REDIS_URL: redis://valkey:6379
CLAUDE_MEM_REDIS_MODE: docker
CLAUDE_MEM_SERVER_DATABASE_URL: postgres://${POSTGRES_USER:?POSTGRES_USER is required}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@postgres:5432/${POSTGRES_DB:?POSTGRES_DB is required}
CLAUDE_MEM_AUTH_MODE: api-key
CLAUDE_MEM_CHROMA_ENABLED: "false"
# Provider configuration. ANTHROPIC_API_KEY (or
# CLAUDE_MEM_ANTHROPIC_API_KEY) is required for real generation; the
# worker stays running but never produces observations without one.
CLAUDE_MEM_SERVER_PROVIDER: ${CLAUDE_MEM_SERVER_PROVIDER:-claude}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
CLAUDE_MEM_ANTHROPIC_API_KEY: ${CLAUDE_MEM_ANTHROPIC_API_KEY:-}
GEMINI_API_KEY: ${GEMINI_API_KEY:-}
OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
volumes:
- claude-mem-data:/data/claude-mem
volumes:
claude-mem-data:
postgres-data:
valkey-data:
+67
View File
@@ -0,0 +1,67 @@
FROM node:20
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
curl \
ca-certificates \
unzip \
jq \
less \
procps \
uuid-runtime \
sqlite3 \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
ARG BUN_VERSION=1.3.12
ENV BUN_INSTALL="/usr/local/bun"
RUN curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" \
&& chmod -R a+rX /usr/local/bun
ENV PATH="/usr/local/bun/bin:${PATH}"
ARG UV_VERSION=0.11.7
ENV UV_INSTALL_DIR="/usr/local/bin"
RUN set -eux \
&& curl -LsSf "https://astral.sh/uv/${UV_VERSION}/install.sh" | sh \
&& { chmod a+rX /usr/local/bin/uv /usr/local/bin/uvx 2>/dev/null || true; }
RUN mkdir -p /usr/local/share/npm-global \
&& chown -R node:node /usr/local/share/npm-global
ENV NPM_CONFIG_PREFIX=/usr/local/share/npm-global
ENV PATH="/usr/local/share/npm-global/bin:${PATH}"
ARG CLAUDE_CODE_VERSION=latest
USER node
RUN npm install -g @anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}
USER root
COPY plugin/ /opt/claude-mem/
RUN chown -R node:node /opt/claude-mem
USER node
RUN cd /opt/claude-mem \
&& npm install --omit=dev --legacy-peer-deps
USER root
RUN mkdir -p /home/node/.claude /home/node/.claude-mem /data/claude-mem \
&& chown -R node:node /home/node/.claude /home/node/.claude-mem /data/claude-mem
COPY --chown=node:node docker/claude-mem/entrypoint.sh /usr/local/bin/claude-mem-entrypoint
RUN chmod +x /usr/local/bin/claude-mem-entrypoint
USER node
WORKDIR /home/node
# Phase 10 — server-beta runtime is the only foregrounded process. The legacy
# worker binaries remain available for tooling but are NEVER spawned by the
# entrypoint. Mode selection happens via CLAUDE_MEM_CONTAINER_MODE
# (server | worker | shell). `docker run ... bash` works because shell mode
# falls through to "$@".
ENV CLAUDE_MEM_CONTAINER_MODE=server
ENV CLAUDE_MEM_RUNTIME=server-beta
ENTRYPOINT ["/usr/local/bin/claude-mem-entrypoint"]
CMD []
+135
View File
@@ -0,0 +1,135 @@
# claude-mem Docker harness
A minimal container for exercising claude-mem end-to-end without polluting your
host. Not a dev environment — just enough to boot `claude` with the locally-built
plugin and capture observations into a throwaway SQLite DB you can inspect
afterwards.
## Files
| File | Purpose |
|------|---------|
| `Dockerfile` | Image definition (node:20 + Bun + uv + Claude Code CLI + local `plugin/`) |
| `build.sh` | Runs `npm run build` then `docker build`. Tag defaults to `claude-mem:basic`. |
| `entrypoint.sh` | Runs inside the container. Seeds OAuth creds into `$HOME/.claude/` if mounted, then `exec "$@"`. |
| `run.sh` | Host-side launcher. Extracts creds (Keychain → file → env), mounts a persistent data dir, drops you into an interactive shell. |
## Quick start
```bash
# From the repo root:
docker/claude-mem/build.sh
docker/claude-mem/run.sh
```
`run.sh` drops you into `bash` inside the container with `claude` on `PATH` and
the plugin pre-staged at `/opt/claude-mem`. Launch it with:
```bash
claude --plugin-dir /opt/claude-mem
```
On exit, the SQLite DB survives at `./.docker-claude-mem-data/claude-mem.db` on
the host — inspect with:
```bash
sqlite3 .docker-claude-mem-data/claude-mem.db 'select count(*) from observations'
```
## What's in the image
Mirrors the layout of [anthropics/claude-code's devcontainer](https://github.com/anthropics/claude-code/blob/main/.devcontainer/Dockerfile):
`FROM node:20`, non-root `node` user, global `npm install -g @anthropic-ai/claude-code`.
Skips the firewall/zsh/fzf/delta/git-hist tooling since this image is about
running claude-mem, not editing code.
On top of that:
- **Bun** (`/usr/local/bun`) — claude-mem's worker service runtime
- **uv** (`/usr/local/bin/uv`) — provides Python for Chroma per `CLAUDE.md`
- **`plugin/`** copied to `/opt/claude-mem` — the locally-built plugin tree
- **`/home/node/.claude`** and **`/home/node/.claude-mem`** — pre-created mount points
Layer ordering is deliberate: plugin files are copied **after** the `npm install`
layer so iterating on the plugin doesn't bust the CLI install cache.
## Pinning versions
Everything that matters is a `--build-arg` — pin for reproducibility, omit for
latest:
```bash
docker build \
-f docker/claude-mem/Dockerfile \
--build-arg BUN_VERSION=1.3.12 \
--build-arg UV_VERSION=0.11.7 \
--build-arg CLAUDE_CODE_VERSION=1.2.3 \
-t claude-mem:basic .
```
| Arg | Default | Notes |
|-----|---------|-------|
| `BUN_VERSION` | `1.3.12` | Installed via the official `bun.sh/install` script, tag `bun-v${BUN_VERSION}`. |
| `UV_VERSION` | `0.11.7` | Installed via the versioned `astral.sh/uv/${UV_VERSION}/install.sh`. |
| `CLAUDE_CODE_VERSION` | `latest` | npm tag or exact version. Pin in CI, let it float locally. |
## Authentication
`run.sh` picks the first auth source that works, in this order:
1. **`ANTHROPIC_API_KEY`** env var — mounted straight into the container.
2. **macOS Keychain**`security find-generic-password -s 'Claude Code-credentials'`.
3. **`~/.claude/.credentials.json`** — legacy on-disk form, still present on some
older CLI installs and migrated machines.
If a credentials file is used, it's written to a `mktemp` file with `chmod 600`,
mounted read-only at `/auth/.credentials.json`, and the container's entrypoint
copies it to `$HOME/.claude/.credentials.json` before exec. An `EXIT` trap
deletes the temp file when `run.sh` returns — `docker run` is deliberately **not**
`exec`'d so the trap gets a chance to fire.
If no auth source is found, `run.sh` exits with an error pointing you at
`claude login` or `ANTHROPIC_API_KEY`.
## Manual invocation (without `run.sh`)
```bash
docker run --rm -it \
-v $(mktemp -d):/home/node/.claude-mem \
-e CLAUDE_MEM_CREDENTIALS_FILE=/auth/.credentials.json \
-v /path/to/creds.json:/auth/.credentials.json:ro \
claude-mem:basic
```
Or with API key auth:
```bash
docker run --rm -it \
-v $(mktemp -d):/home/node/.claude-mem \
-e ANTHROPIC_API_KEY \
claude-mem:basic
```
## Environment variables
| Var | Where | Purpose |
|-----|-------|---------|
| `TAG` | `build.sh`, `run.sh` | Override image tag (default `claude-mem:basic`). |
| `HOST_MEM_DIR` | `run.sh` | Override host path for the persistent `.claude-mem` volume (default `$REPO_ROOT/.docker-claude-mem-data`). |
| `ANTHROPIC_API_KEY` | `run.sh`, entrypoint | API-key auth. Skips the OAuth creds extraction. |
| `CLAUDE_MEM_CREDENTIALS_FILE` | entrypoint | Path (inside the container) to a mounted OAuth creds JSON. Copied to `$HOME/.claude/.credentials.json` at startup. |
## Passing args through
Anything after `run.sh` is forwarded to the container as the command:
```bash
docker/claude-mem/run.sh claude --plugin-dir /opt/claude-mem --print "what did we learn yesterday?"
```
## Cleanup
```bash
rm -rf .docker-claude-mem-data # wipes the persistent DB + Chroma store
docker rmi claude-mem:basic # removes the image
```
+19
View File
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
TAG="${TAG:-claude-mem:basic}"
cd "$REPO_ROOT"
echo "[build] npm run build"
npm run build
echo "[build] docker build -t $TAG"
docker build \
-f docker/claude-mem/Dockerfile \
-t "$TAG" \
"$REPO_ROOT"
echo "[build] done: $TAG"
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Phase 10 — server-beta container entrypoint. The container ALWAYS runs the
# server-beta runtime; the legacy worker is never started here. Generation can
# be split into a separate `claude-mem server worker start` process by setting
# CLAUDE_MEM_GENERATION_DISABLED=true on this service and running the worker
# command in a sibling container.
set -euo pipefail
mkdir -p "$HOME/.claude" "$HOME/.claude-mem"
if [[ -n "${CLAUDE_MEM_CREDENTIALS_FILE:-}" ]]; then
if [[ ! -f "$CLAUDE_MEM_CREDENTIALS_FILE" ]]; then
echo "ERROR: CLAUDE_MEM_CREDENTIALS_FILE set but file missing: $CLAUDE_MEM_CREDENTIALS_FILE" >&2
exit 1
fi
cp "$CLAUDE_MEM_CREDENTIALS_FILE" "$HOME/.claude/.credentials.json"
chmod 600 "$HOME/.claude/.credentials.json"
fi
export PATH="/usr/local/bun/bin:/usr/local/share/npm-global/bin:$PATH"
# Mark this process tree as running inside Docker so server-beta env
# validation can refuse local-dev auth and require the full Postgres+Valkey
# configuration. /.dockerenv is also detected automatically; this is belt-
# and-suspenders for runtimes that don't expose it.
export CLAUDE_MEM_DOCKER=1
export CLAUDE_MEM_RUNTIME="${CLAUDE_MEM_RUNTIME:-server-beta}"
SERVER_BETA_SCRIPT="/opt/claude-mem/scripts/server-beta-service.cjs"
# Mode selection:
# CLAUDE_MEM_CONTAINER_MODE=server (default) — HTTP server-beta, no worker
# CLAUDE_MEM_CONTAINER_MODE=worker — BullMQ generation worker only
# CLAUDE_MEM_CONTAINER_MODE=shell — fall through to "$@" for tooling
MODE="${CLAUDE_MEM_CONTAINER_MODE:-server}"
case "$MODE" in
server)
echo "[claude-mem] starting server-beta runtime (HTTP, no legacy worker)" >&2
exec bun "$SERVER_BETA_SCRIPT" --daemon
;;
worker)
echo "[claude-mem] starting server-beta generation worker (no HTTP)" >&2
# Force generation enabled in the worker process even if the env var was
# set on the shared compose file; the worker IS the generation process.
unset CLAUDE_MEM_GENERATION_DISABLED
exec bun "$SERVER_BETA_SCRIPT" worker start
;;
shell|tooling)
if [[ $# -eq 0 ]]; then
exec bash
fi
exec "$@"
;;
*)
echo "ERROR: unknown CLAUDE_MEM_CONTAINER_MODE=$MODE (expected: server, worker, shell)" >&2
exit 1
;;
esac
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
TAG="${TAG:-claude-mem:basic}"
HOST_MEM_DIR="${HOST_MEM_DIR:-$REPO_ROOT/.docker-claude-mem-data}"
mkdir -p "$HOST_MEM_DIR"
echo "[run] host .claude-mem dir: $HOST_MEM_DIR" >&2
CREDS_FILE=""
CREDS_MOUNT_ARGS=()
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
CREDS_FILE="$(mktemp -t claude-mem-creds.XXXXXX.json)"
trap 'rm -f "$CREDS_FILE"' EXIT
creds_obtained=0
if [[ "$(uname)" == "Darwin" ]]; then
if security find-generic-password -s 'Claude Code-credentials' -w > "$CREDS_FILE" 2>/dev/null \
&& [[ -s "$CREDS_FILE" ]]; then
creds_obtained=1
fi
fi
if [[ "$creds_obtained" -eq 0 && -f "$HOME/.claude/.credentials.json" ]]; then
cp "$HOME/.claude/.credentials.json" "$CREDS_FILE"
creds_obtained=1
fi
if [[ "$creds_obtained" -eq 0 ]]; then
echo "ERROR: no ANTHROPIC_API_KEY set and no Claude OAuth credentials found." >&2
echo " Tried: macOS Keychain ('Claude Code-credentials') and ~/.claude/.credentials.json." >&2
echo " Run \`claude login\` on the host first, or set ANTHROPIC_API_KEY." >&2
exit 1
fi
chmod 600 "$CREDS_FILE"
CREDS_MOUNT_ARGS=(
-e CLAUDE_MEM_CREDENTIALS_FILE=/auth/.credentials.json
-v "$CREDS_FILE:/auth/.credentials.json:ro"
)
else
CREDS_MOUNT_ARGS=(-e ANTHROPIC_API_KEY)
fi
TTY_ARGS=()
[[ -t 0 && -t 1 ]] && TTY_ARGS=(-it)
docker run --rm ${TTY_ARGS[@]+"${TTY_ARGS[@]}"} \
"${CREDS_MOUNT_ARGS[@]}" \
-v "$HOST_MEM_DIR:/home/node/.claude-mem" \
"$TAG" \
"$@"
+302
View File
@@ -0,0 +1,302 @@
// Phase 10 — Docker E2E driver for server-beta. Verifies the
// runtime-relevant slice of the API actually shipped in the Postgres routes:
//
// - GET /healthz — server is alive
// - GET /api/readiness — Postgres bootstrap completed
// - GET /api/health — BullMQ queue engine is bullmq + redis ok
// - POST /v1/sessions/start, /v1/sessions/:id/end
// - POST /v1/events?wait=true (returns generationJob descriptor)
// - GET /v1/events/:id — read-back via team scope
// - GET /v1/jobs/:id — generation job status
// - 401/403 paths for missing/invalid/revoked keys
import net from 'node:net';
const baseUrl = process.env.E2E_BASE_URL ?? 'http://claude-mem-server:37877';
const redisHost = process.env.E2E_REDIS_HOST ?? 'valkey';
const redisPort = Number.parseInt(process.env.E2E_REDIS_PORT ?? '6379', 10);
const phase = process.env.E2E_PHASE ?? 'phase1';
const apiKey = requiredEnv('E2E_API_KEY');
const readOnlyKey = process.env.E2E_READ_ONLY_API_KEY ?? '';
const revokedKey = process.env.E2E_REVOKED_API_KEY ?? '';
const runId = process.env.E2E_RUN_ID ?? `e2e-${Date.now()}`;
const projectIdFromEnv = process.env.E2E_PROJECT_ID ?? '';
function requiredEnv(key) {
const value = process.env[key];
if (!value) {
throw new Error(`${key} is required`);
}
return value;
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
async function sleep(ms) {
await new Promise(resolve => setTimeout(resolve, ms));
}
async function request(path, options = {}) {
const headers = {
...(options.json !== undefined ? { 'content-type': 'application/json' } : {}),
...(options.apiKey ? { authorization: `Bearer ${options.apiKey}` } : {}),
...(options.headers ?? {}),
};
return fetch(`${baseUrl}${path}`, {
method: options.method ?? (options.json === undefined ? 'GET' : 'POST'),
headers,
body: options.json === undefined ? undefined : JSON.stringify(options.json),
});
}
async function json(response) {
const text = await response.text();
try {
return text ? JSON.parse(text) : null;
} catch (error) {
throw new Error(`Invalid JSON response (${response.status}): ${text}\n${error instanceof Error ? error.message : String(error)}`);
}
}
async function requestJson(path, options = {}) {
const response = await request(path, options);
const body = await json(response);
return { response, body };
}
async function expectStatus(path, status, options = {}) {
const response = await request(path, options);
assert(response.status === status, `${path} expected HTTP ${status}, got ${response.status}: ${await response.text()}`);
}
async function waitForReadiness() {
const deadline = Date.now() + 120_000;
let lastError = '';
while (Date.now() < deadline) {
try {
const health = await request('/healthz');
const readiness = await request('/api/readiness');
if (health.ok && readiness.ok) {
return;
}
lastError = `health=${health.status} readiness=${readiness.status}`;
} catch (error) {
lastError = error instanceof Error ? error.message : String(error);
}
await sleep(1000);
}
throw new Error(`Server did not become ready: ${lastError}`);
}
async function assertRedisPing() {
const result = await new Promise((resolve, reject) => {
const socket = net.createConnection({ host: redisHost, port: redisPort });
socket.setTimeout(3000);
let data = '';
socket.on('connect', () => socket.write('*1\r\n$4\r\nPING\r\n'));
socket.on('data', chunk => {
data += chunk.toString('utf8');
if (data.includes('PONG')) {
socket.end();
resolve(data);
}
});
socket.on('timeout', () => {
socket.destroy();
reject(new Error('Redis PING timed out'));
});
socket.on('error', reject);
socket.on('close', () => {
if (!data.includes('PONG')) {
reject(new Error(`Redis PING failed: ${data}`));
}
});
});
assert(String(result).includes('PONG'), `Redis did not return PONG: ${result}`);
}
async function assertQueueHealth() {
const { response, body } = await requestJson('/api/health');
assert(response.ok, `/api/health expected OK, got ${response.status}`);
assert(body.queue?.engine === 'bullmq', `expected BullMQ queue engine, got ${JSON.stringify(body.queue)}`);
assert(body.queue?.redis?.status === 'ok', `expected Redis health ok, got ${JSON.stringify(body.queue?.redis)}`);
}
async function assertInfoEndpoint() {
const { response, body } = await requestJson('/v1/info');
assert(response.ok, `/v1/info expected OK, got ${response.status}`);
assert(body.runtime === 'server-beta', `expected runtime=server-beta, got ${body.runtime}`);
assert(body.postgres?.initialized === true, `expected postgres.initialized=true, got ${JSON.stringify(body.postgres)}`);
assert(body.boundaries?.queueManager?.status === 'active', `expected queue manager active, got ${JSON.stringify(body.boundaries?.queueManager)}`);
}
async function phase1() {
console.log(`[e2e] phase1 starting (${runId})`);
await waitForReadiness();
await assertQueueHealth();
await assertInfoEndpoint();
await assertRedisPing();
// Auth — missing key returns 401, invalid key returns 403. Auth runs
// before body validation, so the body content is irrelevant here.
await expectStatus('/v1/sessions/start', 401, {
method: 'POST',
json: { projectId: projectIdFromEnv, contentSessionId: 'unauth' },
});
await expectStatus('/v1/sessions/start', 403, {
method: 'POST',
apiKey: 'cmem_invalid_key_for_e2e',
json: { projectId: projectIdFromEnv, contentSessionId: 'invalid' },
});
// Read-only key cannot write.
if (readOnlyKey) {
await expectStatus('/v1/sessions/start', 403, {
method: 'POST',
apiKey: readOnlyKey,
json: { projectId: projectIdFromEnv, contentSessionId: `readonly-${runId}` },
});
}
// Open a session. projectId is required in the body and must match the
// project the api-key is scoped to (passed in via E2E_PROJECT_ID).
assert(projectIdFromEnv, 'E2E_PROJECT_ID is required for phase1');
const sessionRes = await requestJson('/v1/sessions/start', {
apiKey,
json: {
projectId: projectIdFromEnv,
contentSessionId: `content-${runId}`,
platformSource: 'docker-e2e',
},
});
assert(sessionRes.response.status === 201, `session create failed: ${sessionRes.response.status} ${JSON.stringify(sessionRes.body)}`);
const session = sessionRes.body.session;
assert(session?.id, `session response missing id: ${JSON.stringify(sessionRes.body)}`);
const projectId = session.projectId;
assert(projectId, `session missing projectId: ${JSON.stringify(session)}`);
// POST /v1/events?wait=true — returns a generationJob descriptor on
// success. This is the Phase 10 contract: HTTP path returns the queued
// job, and the worker process generates the observation later.
const createdEvent = await requestJson('/v1/events?wait=true', {
apiKey,
json: {
projectId,
serverSessionId: session.id,
sourceType: 'api',
eventType: 'observation.created',
contentSessionId: `content-${runId}`,
memorySessionId: `memory-${runId}`,
payload: { tool_name: 'Read', runId },
occurredAtEpoch: Date.now(),
},
});
assert(
createdEvent.response.status === 201,
`event create failed: ${createdEvent.response.status} ${JSON.stringify(createdEvent.body)}`,
);
const event = createdEvent.body.event;
assert(event?.id, `event response missing id: ${JSON.stringify(createdEvent.body)}`);
// wait=true MUST include a generationJob descriptor (queued or generated).
// Its absence indicates the queue path was bypassed.
assert(
createdEvent.body.generationJob !== undefined && createdEvent.body.generationJob !== null,
`wait=true response missing generationJob: ${JSON.stringify(createdEvent.body)}`,
);
// Read-back through the team-scoped GET /v1/events/:id route.
const fetched = await requestJson(`/v1/events/${event.id}`, { apiKey });
assert(fetched.response.ok, `event fetch failed: ${fetched.response.status} ${JSON.stringify(fetched.body)}`);
// Poll the generation job — it MUST exist in Postgres regardless of
// whether a provider is configured. Without a provider, status stays at
// `queued`; with one, it eventually becomes `generated`. Either way the
// job row is observable via GET /v1/jobs/:id.
const jobId = createdEvent.body.generationJob.id;
if (jobId) {
const jobRes = await requestJson(`/v1/jobs/${jobId}`, { apiKey });
assert(jobRes.response.ok, `job fetch failed: ${jobRes.response.status} ${JSON.stringify(jobRes.body)}`);
}
// Close the session.
const ended = await requestJson(`/v1/sessions/${session.id}/end`, {
method: 'POST',
apiKey,
json: {},
});
assert(ended.response.ok, `session end failed: ${ended.response.status} ${JSON.stringify(ended.body)}`);
console.log(`[e2e] phase1 passed session=${session.id} event=${event.id} job=${jobId ?? 'none'}`);
}
async function phase2() {
console.log(`[e2e] phase2 after restart starting (${runId})`);
await waitForReadiness();
await assertQueueHealth();
await assertInfoEndpoint();
await assertRedisPing();
// Revoked key MUST fail on every authenticated route. The restart between
// phase1 and phase2 specifically asserts the revocation lives in Postgres,
// not an in-memory cache.
if (revokedKey) {
await expectStatus('/v1/sessions/start', 403, {
method: 'POST',
apiKey: revokedKey,
json: { projectId: projectIdFromEnv, contentSessionId: `revoked-${runId}` },
});
}
// Full key still works after restart — durable session creation + event
// ingest path through Postgres.
assert(projectIdFromEnv, 'E2E_PROJECT_ID is required for phase2');
const sessionRes = await requestJson('/v1/sessions/start', {
apiKey,
json: {
projectId: projectIdFromEnv,
contentSessionId: `content-after-restart-${runId}`,
platformSource: 'docker-e2e',
},
});
assert(
sessionRes.response.status === 201,
`session create after restart failed: ${sessionRes.response.status} ${JSON.stringify(sessionRes.body)}`,
);
const session = sessionRes.body.session;
const projectId = session.projectId;
const createdEvent = await requestJson('/v1/events?wait=true', {
apiKey,
json: {
projectId,
serverSessionId: session.id,
sourceType: 'api',
eventType: 'observation.created',
contentSessionId: `content-after-restart-${runId}`,
payload: { tool_name: 'Edit', runId, after: 'restart' },
occurredAtEpoch: Date.now(),
},
});
assert(
createdEvent.response.status === 201,
`event after restart failed: ${createdEvent.response.status} ${JSON.stringify(createdEvent.body)}`,
);
assert(
createdEvent.body.generationJob !== undefined && createdEvent.body.generationJob !== null,
`wait=true after restart missing generationJob: ${JSON.stringify(createdEvent.body)}`,
);
console.log(`[e2e] phase2 passed session=${session.id} event=${createdEvent.body.event.id}`);
}
if (phase === 'phase1') {
await phase1();
} else if (phase === 'phase2') {
await phase2();
} else {
throw new Error(`Unknown E2E_PHASE: ${phase}`);
}
-83
View File
@@ -1,83 +0,0 @@
<claude-mem-context>
# Recent Activity
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
### Nov 6, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #4241 | 11:19 PM | 🟣 | Object-Oriented Architecture Design Document Created | ~662 |
| #4240 | 11:11 PM | 🟣 | Worker Service Rewrite Blueprint Created | ~541 |
| #4239 | 11:07 PM | 🟣 | Comprehensive Worker Service Performance Analysis Document Created | ~541 |
| #4238 | 10:59 PM | 🔵 | Overhead Analysis Document Checked | ~203 |
### Nov 7, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #4609 | 6:33 PM | ✅ | PR #69 Successfully Merged to Main Branch | ~516 |
| #4600 | 6:31 PM | 🟣 | Added Worker Service Documentation Suite | ~441 |
| #4597 | " | 🔄 | Worker Service Refactored to Object-Oriented Architecture | ~473 |
### Nov 8, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #5539 | 10:20 PM | 🔵 | Harsh critical audit of context-hook reveals systematic anti-patterns | ~3154 |
| #5497 | 9:29 PM | 🔵 | Harsh critical audit of context-hook reveals systematic anti-patterns | ~2815 |
| #5495 | 9:28 PM | 🔵 | Context Hook Audit Reveals Project Anti-Patterns | ~660 |
| #5476 | 9:17 PM | 🔵 | Critical Code Audit Identified 14 Anti-Patterns in Context Hook | ~887 |
| #5391 | 8:45 PM | 🔵 | Critical Code Quality Audit of Context Hook Implementation | ~720 |
| #5150 | 7:37 PM | 🟣 | Troubleshooting Skill Added to Claude-Mem Plugin | ~427 |
### Nov 9, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #6161 | 11:55 PM | 🔵 | YC W26 Application Research and Preparation Completed for Claude-Mem | ~1628 |
| #6155 | 11:47 PM | ✅ | Comprehensive Y Combinator Winter 2026 Application Notes Created | ~1045 |
| #5979 | 7:58 PM | 🔵 | Smart Contextualization Feature Architecture | ~560 |
| #5971 | 7:49 PM | 🔵 | Hooks Reference Documentation Structure | ~448 |
| #5929 | 7:08 PM | ✅ | Documentation Updates for v5.4.0 Skill-Based Search Migration | ~604 |
| #5927 | " | ✅ | Updated Configuration Documentation for Skill-Based Search | ~497 |
| #5920 | 7:05 PM | ✅ | Renamed Architecture Documentation File Reference | ~271 |
### Nov 18, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #11515 | 8:22 PM | 🔵 | Smart Contextualization Architecture Retrieved with Command Hook Pattern Details | ~502 |
### Dec 8, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #22294 | 9:43 PM | 🔵 | Documentation Site Structure Located | ~359 |
### Dec 12, 2025
| ID | Time | T | Title | Read |
|----|------|---|-------|------|
| #24430 | 8:27 PM | ✅ | Removed Final Platform Check Reference from Linux Section | ~320 |
| #24429 | " | ✅ | Final Platform Check Reference Removal from Linux Section | ~274 |
| #24428 | " | ✅ | Corrected Second Line Number Reference for Migration Marker Logic | ~267 |
| #24427 | 8:26 PM | ✅ | Updated Line Number Reference for PM2 Cleanup Implementation | ~260 |
| #24426 | " | ✅ | Removed Platform Check from Manual Marker Deletion Scenario | ~338 |
| #24425 | " | ✅ | Removed Platform Check from Fresh Install Scenario Flow | ~314 |
| #24424 | 8:25 PM | ✅ | Renumbered Manual Marker Deletion Scenario | ~285 |
| #24423 | " | ✅ | Renumbered Fresh Install Scenario | ~243 |
| #24422 | " | ✅ | Removed Obsolete Windows Platform Detection Scenario | ~311 |
| #24421 | " | ✅ | Removed Platform Check from macOS Migration Documentation | ~294 |
| #24420 | 8:24 PM | ✅ | Platform Check Removed from Migration Documentation | ~288 |
| #24417 | 8:16 PM | ✅ | Code Reference Example Updated to Reflect Actual Cross-Platform Implementation | ~366 |
| #24416 | " | ✅ | Architecture Decision Documentation Updated to Reflect Cross-Platform PM2 Cleanup Rationale | ~442 |
| #24415 | 8:15 PM | ✅ | Migration Marker Lifecycle Documentation Updated for Unified Cross-Platform Behavior | ~463 |
| #24414 | " | ✅ | Platform Comparison Table Updated to Reflect Unified Cross-Platform Migration | ~351 |
| #24413 | " | ✅ | Windows Platform-Specific Documentation Completely Rewritten for Unified Migration | ~428 |
| #24412 | " | ✅ | User Experience Timeline Updated for Cross-Platform PM2 Cleanup | ~291 |
| #24411 | 8:14 PM | ✅ | Migration Marker Lifecycle Documentation Updated for All Platforms | ~277 |
| #24410 | " | ✅ | Marker File Platform Behavior Documentation Updated for Unified Migration | ~282 |
| #24409 | " | ✅ | Migration Steps Documentation Updated for Cross-Platform PM2 Cleanup | ~278 |
| #24408 | 8:13 PM | ✅ | PM2 Migration Documentation Updated to Remove Windows Platform Check | ~280 |
</claude-mem-context>
-213
View File
@@ -1,213 +0,0 @@
# Claude-Mem PR Shipping Report
*Generated: 2026-02-04*
## Executive Summary
6 PRs analyzed for shipping readiness. **1 is ready to merge**, 4 have conflicts, 1 is too large for easy review.
| PR | Title | Status | Recommendation |
|----|-------|--------|----------------|
| **#856** | Idle timeout for zombie processes | ✅ **MERGEABLE** | **Ship it** |
| #700 | Windows Terminal popup fix | ⚠️ Conflicts | Rebase, then ship |
| #722 | In-process worker architecture | ⚠️ Conflicts | Rebase, high impact |
| #657 | generate/clean CLI commands | ⚠️ Conflicts | Rebase, then ship |
| #863 | Ragtime email investigation | 🔍 Needs review | Research pending |
| #464 | Sleep Agent Pipeline (contributor) | 🔴 Too large | Request split or dedicated review |
---
## Ready to Ship
### PR #856: Idle Timeout for Zombie Observer Processes
**Status:** ✅ MERGEABLE (no conflicts)
| Metric | Value |
|--------|-------|
| Additions | 928 |
| Deletions | 171 |
| Files | 8 |
| Risk | Low-Medium |
**What it does:**
- Adds 3-minute idle timeout to `SessionQueueProcessor`
- Prevents zombie observer processes that were causing 13.4GB swap usage
- Processes exit gracefully after inactivity instead of waiting forever
**Why ship it:**
- Fixes real user-reported issue (79 zombie processes)
- Well-tested (11 new tests, 440 lines of test coverage)
- Clean implementation, preventive approach
- Supersedes PR #848's reactive cleanup
- No conflicts, ready to merge
**Review notes:**
- 1 Greptile bot comment (addressed)
- Race condition fix included
- Enhanced logging added
---
## Needs Rebase (Have Conflicts)
### PR #700: Windows Terminal Popup Fix
**Status:** ⚠️ CONFLICTING
| Metric | Value |
|--------|-------|
| Additions | 187 |
| Deletions | 399 |
| Files | 8 |
| Risk | Medium |
**What it does:**
- Eliminates Windows Terminal popup by removing spawn-based daemon
- Worker `start` command becomes daemon directly (no child spawn)
- Removes `restart` command (users do `stop` then `start`)
- Net simplification: -212 lines
**Breaking changes:**
- `restart` command removed
**Review status:**
- ✅ 1 APPROVAL from @volkanfirat (Jan 15, 2026)
**Action needed:** Resolve conflicts, then ready to ship.
---
### PR #722: In-Process Worker Architecture
**Status:** ⚠️ CONFLICTING
| Metric | Value |
|--------|-------|
| Additions | 869 |
| Deletions | 4,658 |
| Files | 112 |
| Risk | High |
**What it does:**
- Hook processes become the worker (no separate daemon spawning)
- First hook that needs worker becomes the worker
- Eliminates Windows spawn issues ("NO SPAWN" rule)
- 761 tests pass
**Architectural impact:** HIGH
- Fundamentally changes worker lifecycle
- Hook processes stay alive (they ARE the worker)
- First hook wins port 37777, others use HTTP
**Action needed:** Resolve conflicts. Consider relationship with PR #700 (both touch worker architecture).
---
### PR #657: Generate/Clean CLI Commands
**Status:** ⚠️ CONFLICTING
| Metric | Value |
|--------|-------|
| Additions | 1,184 |
| Deletions | 5,057 |
| Files | 104 |
| Risk | Medium |
**What it does:**
- Adds `claude-mem generate` and `claude-mem clean` CLI commands
- Fixes validation bugs (deleted folders recreated from stale DB)
- Fixes Windows path handling
- Adds automatic shell alias installation
- Disables subdirectory CLAUDE.md files by default
**Breaking changes:**
- Default behavior change: folder CLAUDE.md now disabled by default
**Action needed:** Resolve conflicts, complete Windows testing.
---
## Needs Attention
### PR #863: Ragtime Email Investigation
**Status:** 🔍 Research pending
Research agent did not return results. Manual review needed.
---
### PR #464: Sleep Agent Pipeline (Contributor: @laihenyi)
**Status:** 🔴 Too large for effective review
| Metric | Value |
|--------|-------|
| Additions | 15,430 |
| Deletions | 469 |
| Files | 73 |
| Wait time | 37+ days |
| Risk | High |
**What it does:**
- Sleep Agent Pipeline with memory tiering
- Supersession detection
- Session Statistics API (`/api/session/:id/stats`)
- StatusLine + PreCompact hooks
- Context Generator improvements
- Self-healing CI workflow
**Concerns:**
| Issue | Details |
|-------|---------|
| 🔴 Size | 15K+ lines is too large for effective review |
| 🔴 SupersessionDetector | Single file with 1,282 additions |
| 🟡 No tests visible | Test plan checkboxes unchecked |
| 🟡 Self-healing CI | Auto-fix workflow could cause infinite commit loops |
| 🟡 Serena config | Adds `.serena/` tooling |
**Recommendation:**
1. **Option A:** Request contributor split into 4-5 smaller PRs
2. **Option B:** Allocate dedicated review time (several hours)
3. **Option C:** Cherry-pick specific features (hooks, stats API)
**Note:** Contributor has been waiting 37+ days. Deserves response either way.
---
## Shipping Strategy
### Phase 1: Quick Wins (This Week)
1. **Merge #856** — Ready now, fixes real user issue
2. **Rebase #700** — Has approval, Windows fix needed
3. **Rebase #657** — Useful CLI commands
### Phase 2: Architecture (Careful Review)
4. **Review #722** — High impact, conflicts with #700 approach?
- Both PRs eliminate spawning but in different ways
- May need to pick one approach
### Phase 3: Contributor PR
5. **Respond to #464** — Options:
- Ask for split
- Schedule dedicated review
- Cherry-pick subset
### Phase 4: Investigation
6. **Manual review #863** — Ragtime email feature
---
## Conflict Resolution Order
Since multiple PRs have conflicts, suggested rebase order:
1. **#856** (merge first — no conflicts)
2. **#700** (rebase onto main after #856)
3. **#657** (rebase onto main after #700)
4. **#722** (rebase last — may conflict with #700 architecturally)
---
## Summary
| Ready | Conflicts | Needs Work |
|-------|-----------|------------|
| 1 PR (#856) | 3 PRs (#700, #722, #657) | 2 PRs (#464, #863) |
**Immediate action:** Merge #856, then rebase the conflict PRs in order.
-79
View File
@@ -1,79 +0,0 @@
# Version Consistency Fix (Issue #XXX)
## Problem
Version mismatch between plugin and worker caused infinite restart loop:
- Plugin version: 9.0.0 (from plugin/.claude-plugin/plugin.json)
- Worker binary version: 8.5.9 (hardcoded in bundled worker-service.cjs)
This triggered the auto-restart mechanism on every hook call, which killed the SDK generator before it could complete the Claude API call to generate observations. Result: 0 observations were ever saved to the database despite hooks firing successfully.
## Root Cause
The `plugin/package.json` file had version `8.5.10` instead of `9.0.0`. When the project was last built, the build script correctly injected the version from root `package.json` into the bundled worker service. However, the `plugin/package.json` was manually created/edited and fell out of sync.
At runtime:
1. Worker service reads version from `~/.claude/plugins/marketplaces/thedotmack/package.json` → gets `8.5.10`
2. Running worker returns built-in version via `/api/version` → returns `8.5.9` (from old build)
3. Version check in `worker-service.ts` start command detects mismatch
4. Auto-restart triggered on every hook call
5. Observations never saved
## Solution
1. Updated `plugin/package.json` from version `8.5.10` to `9.0.0`
2. Rebuilt all hooks and worker service to inject correct version (`9.0.0`) into bundled artifacts
3. Added comprehensive test suite to prevent future version mismatches
## Verification
All versions now match:
```
Root package.json: 9.0.0 ✓
plugin/package.json: 9.0.0 ✓
plugin.json: 9.0.0 ✓
marketplace.json: 9.0.0 ✓
worker-service.cjs: 9.0.0 ✓
```
## Prevention
To prevent this issue in the future:
1. **Automated Build Process**: The `scripts/build-hooks.js` now regenerates `plugin/package.json` automatically with the correct version from root `package.json`
2. **Version Consistency Tests**: Added `tests/infrastructure/version-consistency.test.ts` to verify all version sources match
3. **Version Management Best Practices**:
- NEVER manually edit `plugin/package.json` - it's auto-generated during build
- Always update version in root `package.json` only
- Run `npm run build` after version changes
- The build script will sync the version to all necessary locations
## Files Changed
- `plugin/package.json` - Updated version from 8.5.10 to 9.0.0
- `plugin/scripts/worker-service.cjs` - Rebuilt with version 9.0.0 injected
- `plugin/scripts/mcp-server.cjs` - Rebuilt with version 9.0.0 injected
- `plugin/scripts/*.js` (hooks) - Rebuilt with version 9.0.0 injected
- `tests/infrastructure/version-consistency.test.ts` - New test suite
## Testing
Run the version consistency test:
```bash
npm run test:infra
```
Or manually verify:
```bash
node -e "
const fs = require('fs');
const rootPkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
const pluginPkg = JSON.parse(fs.readFileSync('plugin/package.json', 'utf-8'));
const workerContent = fs.readFileSync('plugin/scripts/worker-service.cjs', 'utf-8');
const workerMatch = workerContent.match(/Bre=\"([0-9.]+)\"/);
console.log('Root:', rootPkg.version);
console.log('Plugin:', pluginPkg.version);
console.log('Worker:', workerMatch ? workerMatch[1] : 'NOT_FOUND');
"
```
## Related Code Locations
- **Version Injection**: `scripts/build-hooks.js` line 43-45, 105, 130, 155, 178
- **Version Check**: `src/services/infrastructure/HealthMonitor.ts` line 133-143
- **Auto-Restart Logic**: `src/services/worker-service.ts` line 627-645
- **Runtime Version Read**: `src/shared/worker-utils.ts` line 73-76, 82-91
+5
View File
@@ -0,0 +1,5 @@
# Adapters
Claude Code hook payloads are mapped through `src/adapters/claude-code/mapper.ts` into `AgentEvent` records. The mapper preserves legacy fields such as `contentSessionId`, `tool_name`, `tool_input`, `tool_response`, `cwd`, `agentId`, `agentType`, `platformSource`, and both `tool_use_id` and `toolUseId`.
Generic agent examples live in `src/adapters/generic-rest/examples.ts` for Codex, OpenCode, and custom REST ingestion. New adapters should emit the REST V1 event shape instead of coupling their payloads to Claude Code internals.
+40
View File
@@ -0,0 +1,40 @@
# Server API
REST V1 is mounted under `/v1`; legacy worker routes remain under `/api`.
Available beta endpoints:
- `GET /healthz`
- `GET /v1/info`
- `GET /v1/projects`
- `POST /v1/projects`
- `GET /v1/projects/:id`
- `POST /v1/sessions/start`
- `POST /v1/sessions/:id/end`
- `GET /v1/sessions/:id`
- `POST /v1/events`
- `POST /v1/events/batch`
- `GET /v1/events/:id`
- `POST /v1/memories`
- `GET /v1/memories/:id`
- `PATCH /v1/memories/:id`
- `POST /v1/search`
- `POST /v1/context`
- `GET /v1/audit?projectId=<id>`
When `CLAUDE_MEM_AUTH_MODE=api-key`, send `Authorization: Bearer <key>`. Read endpoints require `memories:read`; write endpoints require `memories:write`.
## Event generation semantics
`POST /v1/events` accepts two query flags that control observation generation:
- `generate=false` — write the event but do not enqueue a generation job.
- `wait=true` — return the `generationJob` descriptor in the response, so
callers can poll `GET /v1/jobs/:id` for completion.
Without `wait=true`, the response includes the new event row and a best-
effort `generationJob` field. With `wait=true`, the `generationJob` field is
always populated (or `null` only when generation was explicitly disabled).
The actual provider call happens in a separate BullMQ worker process
(`claude-mem server worker start`); the HTTP path never blocks on a
provider response.
+20 -16
View File
@@ -14,7 +14,7 @@
| +-- handlers/ (context, session-init, observation, |
| summarize, session-complete) |
+-----------------------------------------------------------+
| Worker Daemon (Express, port 37777) |
| Worker Daemon (Express, per-user port 37700+(uid%100)) |
| +-- SessionManager (session lifecycle) |
| +-- SDKAgent (Claude Agent SDK) |
| +-- SearchManager (search orchestration) |
@@ -32,13 +32,15 @@
| Event | Handler | What it does | Timeout |
|-------|---------|-------------|---------|
| Setup | setup.sh | Install system dependencies | 300s |
| SessionStart | smart-install.js + context | Install deps + start worker + inject context | 60s |
| Setup | version-check.js | Sub-100ms version-marker check; prompts `npx claude-mem repair` on mismatch | 60s |
| SessionStart | worker start + context | Start worker service and inject context | 60s |
| UserPromptSubmit | session-init | Register session + start SDK agent + semantic injection | 60s |
| PostToolUse | observation | Capture tool usage -> enqueue in worker | 120s |
| Summary | summarize | Request session summary from SDK agent | 120s |
| SessionEnd | session-complete | End session + drain pending messages | 30s |
On first install, `npx claude-mem install` sets up Bun and uv globally, runs `bun install` in the plugin cache, and writes an `.install-version` marker — all behind a visible clack spinner. The Setup hook then runs `version-check.js` on every Claude Code startup; if the plugin was upgraded externally (e.g. `claude plugin update`), it writes a hint to stderr asking the user to run `npx claude-mem repair`. The hook always exits 0 (non-blocking).
## Data Flow
```text
@@ -62,27 +64,29 @@ Stop -> summarize -> /api/sessions/summarize
## Key Patterns
### CLAIM-CONFIRM (PendingMessageStore)
### Pending Queue (PendingMessageStore)
```text
enqueue() -> INSERT status='pending'
claimNextMessage() -> UPDATE status='processing' (atomic)
confirmProcessed() -> DELETE (success)
markFailed() -> UPDATE status='failed' (retry < 3)
Self-healing: messages in 'processing' for >60s reset to 'pending'
enqueue() -> INSERT row with `pending` status
clearPendingForSession() -> DELETE all pending rows for session
(called whenever the parser returns
a parseable response, regardless of
whether observations were extracted)
```
### Circuit-Breaker (SessionRoutes)
Parser is binary: `{ valid: true, observations, summary }` or `{ valid: false }`.
Unparseable responses leave the queue untouched and the session iterator continues.
### Generator restart loop (SessionRoutes)
```text
Generator crash -> retry 1 (1s) -> retry 2 (2s) -> retry 3 (4s)
-> consecutiveRestarts > 3 -> CIRCUIT-BREAKER
-> markAllSessionMessagesAbandoned(sessionDbId)
-> Stop. No infinite loop.
-> consecutiveRestarts > 3 -> stop and let the iterator end
```
Counter resets to 0 when generator completes work naturally.
Counter resets to 0 when generator completes work naturally. Pending
messages remain in the queue across restarts and are cleared by the
parser path on the next valid response.
### Graceful Degradation (hook-command.ts)
@@ -117,7 +121,7 @@ The conversion between them is handled by SessionStore and is critical for FK co
| observations | memory_session_id, type, title, narrative, content_hash | Tool usage observations |
| session_summaries | memory_session_id, request, learned, completed | Session summaries |
| user_prompts | content_session_id, prompt_text | User prompt history |
| pending_messages | session_db_id, status, message_type | CLAIM-CONFIRM queue |
| pending_messages | session_db_id, message_type | Per-session pending queue |
| observation_feedback | observation_id, signal_type | Usage tracking |
### ChromaDB (chroma.sqlite3)
-14
View File
@@ -1,9 +1,3 @@
/**
* Claude Agent SDK V2 Examples
*
* The V2 API provides a session-based interface with separate send()/receive(),
* ideal for multi-turn conversations. Run with: npx tsx v2-examples.ts
*/
import {
unstable_v2_createSession,
@@ -32,7 +26,6 @@ async function main() {
}
}
// Basic session with send/receive pattern
async function basicSession() {
console.log('=== Basic Session ===\n');
@@ -47,13 +40,11 @@ async function basicSession() {
}
}
// Multi-turn conversation - V2's key advantage
async function multiTurn() {
console.log('=== Multi-Turn Conversation ===\n');
await using session = unstable_v2_createSession({ model: 'sonnet' });
// Turn 1
await session.send('What is 5 + 3? Just the number.');
for await (const msg of session.receive()) {
if (msg.type === 'assistant') {
@@ -62,7 +53,6 @@ async function multiTurn() {
}
}
// Turn 2 - Claude remembers context
await session.send('Multiply that by 2. Just the number.');
for await (const msg of session.receive()) {
if (msg.type === 'assistant') {
@@ -72,7 +62,6 @@ async function multiTurn() {
}
}
// One-shot convenience function
async function oneShot() {
console.log('=== One-Shot Prompt ===\n');
@@ -84,13 +73,11 @@ async function oneShot() {
}
}
// Session resume - persist context across sessions
async function sessionResume() {
console.log('=== Session Resume ===\n');
let sessionId: string | undefined;
// First session - establish a memory
{
await using session = unstable_v2_createSession({ model: 'sonnet' });
console.log('[Session 1] Telling Claude my favorite color...');
@@ -110,7 +97,6 @@ async function sessionResume() {
console.log('--- Session closed. Time passes... ---\n');
// Resume and verify Claude remembers
{
await using session = unstable_v2_resumeSession(sessionId!, { model: 'sonnet' });
console.log('[Session 2] Resuming and asking Claude...');
-338
View File
@@ -1,338 +0,0 @@
# Get started with Claude Code hooks
> Learn how to customize and extend Claude Code's behavior by registering shell commands
Claude Code hooks are user-defined shell commands that execute at various points
in Claude Code's lifecycle. Hooks provide deterministic control over Claude
Code's behavior, ensuring certain actions always happen rather than relying on
the LLM to choose to run them.
<Tip>
For reference documentation on hooks, see [Hooks reference](/en/hooks).
</Tip>
Example use cases for hooks include:
* **Notifications**: Customize how you get notified when Claude Code is awaiting
your input or permission to run something.
* **Automatic formatting**: Run `prettier` on .ts files, `gofmt` on .go files,
etc. after every file edit.
* **Logging**: Track and count all executed commands for compliance or
debugging.
* **Feedback**: Provide automated feedback when Claude Code produces code that
does not follow your codebase conventions.
* **Custom permissions**: Block modifications to production files or sensitive
directories.
By encoding these rules as hooks rather than prompting instructions, you turn
suggestions into app-level code that executes every time it is expected to run.
<Warning>
You must consider the security implication of hooks as you add them, because hooks run automatically during the agent loop with your current environment's credentials.
For example, malicious hooks code can exfiltrate your data. Always review your hooks implementation before registering them.
For full security best practices, see [Security Considerations](/en/hooks#security-considerations) in the hooks reference documentation.
</Warning>
## Hook Events Overview
Claude Code provides several hook events that run at different points in the
workflow:
* **PreToolUse**: Runs before tool calls (can block them)
* **PermissionRequest**: Runs when a permission dialog is shown (can allow or deny)
* **PostToolUse**: Runs after tool calls complete
* **UserPromptSubmit**: Runs when the user submits a prompt, before Claude processes it
* **Notification**: Runs when Claude Code sends notifications
* **Stop**: Runs when Claude Code finishes responding
* **SubagentStop**: Runs when subagent tasks complete
* **PreCompact**: Runs before Claude Code is about to run a compact operation
* **SessionStart**: Runs when Claude Code starts a new session or resumes an existing session
* **SessionEnd**: Runs when Claude Code session ends
Each event receives different data and can control Claude's behavior in
different ways.
## Quickstart
In this quickstart, you'll add a hook that logs the shell commands that Claude
Code runs.
### Prerequisites
Install `jq` for JSON processing in the command line.
### Step 1: Open hooks configuration
Run the `/hooks` [slash command](/en/slash-commands) and select
the `PreToolUse` hook event.
`PreToolUse` hooks run before tool calls and can block them while providing
Claude feedback on what to do differently.
### Step 2: Add a matcher
Select `+ Add new matcher…` to run your hook only on Bash tool calls.
Type `Bash` for the matcher.
<Note>You can use `*` to match all tools.</Note>
### Step 3: Add the hook
Select `+ Add new hook…` and enter this command:
```bash theme={null}
jq -r '"\(.tool_input.command) - \(.tool_input.description // "No description")"' >> ~/.claude/bash-command-log.txt
```
### Step 4: Save your configuration
For storage location, select `User settings` since you're logging to your home
directory. This hook will then apply to all projects, not just your current
project.
Then press `Esc` until you return to the REPL. Your hook is now registered.
### Step 5: Verify your hook
Run `/hooks` again or check `~/.claude/settings.json` to see your configuration:
```json theme={null}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt"
}
]
}
]
}
}
```
### Step 6: Test your hook
Ask Claude to run a simple command like `ls` and check your log file:
```bash theme={null}
cat ~/.claude/bash-command-log.txt
```
You should see entries like:
```
ls - Lists files and directories
```
## More Examples
<Note>
For a complete example implementation, see the [bash command validator example](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py) in our public codebase.
</Note>
### Code Formatting Hook
Automatically format TypeScript files after editing:
```json theme={null}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.ts$'; then npx prettier --write \"$file_path\"; fi; }"
}
]
}
]
}
}
```
### Markdown Formatting Hook
Automatically fix missing language tags and formatting issues in markdown files:
```json theme={null}
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_formatter.py"
}
]
}
]
}
}
```
Create `.claude/hooks/markdown_formatter.py` with this content:
````python theme={null}
#!/usr/bin/env python3
"""
Markdown formatter for Claude Code output.
Fixes missing language tags and spacing issues while preserving code content.
"""
import json
import sys
import re
import os
def detect_language(code):
"""Best-effort language detection from code content."""
s = code.strip()
# JSON detection
if re.search(r'^\s*[{\[]', s):
try:
json.loads(s)
return 'json'
except:
pass
# Python detection
if re.search(r'^\s*def\s+\w+\s*\(', s, re.M) or \
re.search(r'^\s*(import|from)\s+\w+', s, re.M):
return 'python'
# JavaScript detection
if re.search(r'\b(function\s+\w+\s*\(|const\s+\w+\s*=)', s) or \
re.search(r'=>|console\.(log|error)', s):
return 'javascript'
# Bash detection
if re.search(r'^#!.*\b(bash|sh)\b', s, re.M) or \
re.search(r'\b(if|then|fi|for|in|do|done)\b', s):
return 'bash'
# SQL detection
if re.search(r'\b(SELECT|INSERT|UPDATE|DELETE|CREATE)\s+', s, re.I):
return 'sql'
return 'text'
def format_markdown(content):
"""Format markdown content with language detection."""
# Fix unlabeled code fences
def add_lang_to_fence(match):
indent, info, body, closing = match.groups()
if not info.strip():
lang = detect_language(body)
return f"{indent}```{lang}\n{body}{closing}\n"
return match.group(0)
fence_pattern = r'(?ms)^([ \t]{0,3})```([^\n]*)\n(.*?)(\n\1```)\s*$'
content = re.sub(fence_pattern, add_lang_to_fence, content)
# Fix excessive blank lines (only outside code fences)
content = re.sub(r'\n{3,}', '\n\n', content)
return content.rstrip() + '\n'
# Main execution
try:
input_data = json.load(sys.stdin)
file_path = input_data.get('tool_input', {}).get('file_path', '')
if not file_path.endswith(('.md', '.mdx')):
sys.exit(0) # Not a markdown file
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
formatted = format_markdown(content)
if formatted != content:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(formatted)
print(f"✓ Fixed markdown formatting in {file_path}")
except Exception as e:
print(f"Error formatting markdown: {e}", file=sys.stderr)
sys.exit(1)
````
Make the script executable:
```bash theme={null}
chmod +x .claude/hooks/markdown_formatter.py
```
This hook automatically:
* Detects programming languages in unlabeled code blocks
* Adds appropriate language tags for syntax highlighting
* Fixes excessive blank lines while preserving code content
* Only processes markdown files (`.md`, `.mdx`)
### Custom Notification Hook
Get desktop notifications when Claude needs input:
```json theme={null}
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "notify-send 'Claude Code' 'Awaiting your input'"
}
]
}
]
}
}
```
### File Protection Hook
Block edits to sensitive files:
```json theme={null}
{
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "python3 -c \"import json, sys; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(p in path for p in ['.env', 'package-lock.json', '.git/']) else 0)\""
}
]
}
]
}
}
```
## Learn more
* For reference documentation on hooks, see [Hooks reference](/en/hooks).
* For comprehensive security best practices and safety guidelines, see [Security Considerations](/en/hooks#security-considerations) in the hooks reference documentation.
* For troubleshooting steps and debugging techniques, see [Debugging](/en/hooks#debugging) in the hooks reference
documentation.
---
> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt
+18
View File
@@ -0,0 +1,18 @@
# Docker
The root `docker-compose.yml` starts Claude-Mem Server beta with a persistent Valkey sidecar.
```sh
docker compose up --build
curl http://127.0.0.1:37777/healthz
```
The server container uses:
- `CLAUDE_MEM_WORKER_HOST=0.0.0.0`
- `CLAUDE_MEM_DATA_DIR=/data/claude-mem`
- `CLAUDE_MEM_QUEUE_ENGINE=bullmq`
- `CLAUDE_MEM_REDIS_URL=redis://valkey:6379`
- `CLAUDE_MEM_AUTH_MODE=api-key`
Create an API key inside the container before using protected V1 write routes.
+8 -13
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -82,7 +82,6 @@
</a>
</p>
<p align="center">
<a href="#بداية-سريعة">بداية سريعة</a> •
<a href="#كيف-يعمل">كيف يعمل</a> •
@@ -276,25 +275,21 @@ npm run bug-report
---
## الترخيص (License)
## License
هذا المشروع مرخص بموجب **ترخيص GNU Affero العام الإصدار 3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
حقوق النشر (C) 2025 Alex Newman (@thedotmack). جميع الحقوق محفوظة.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
انظر ملف [LICENSE](LICENSE) للتفاصيل الكاملة.
See the [LICENSE](LICENSE) file for full details.
**ماذا يعني هذا:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- يمكنك استخدام وتعديل وتوزيع هذا البرنامج بحرية
- إذا قمت بتعديل ونشر على خادم شبكة، يجب أن تتيح كود المصدر الخاص بك
- الأعمال المشتقة يجب أن تكون مرخصة أيضًا تحت AGPL-3.0
- لا يوجد ضمان لهذا البرنامج
**ملاحظة حول Ragtime**: دليل `ragtime/` مرخص بشكل منفصل تحت **ترخيص PolyForm Noncommercial 1.0.0**. انظر [ragtime/LICENSE](ragtime/LICENSE) للتفاصيل.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## الدعم
- **التوثيق**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## লাইসেন্স
## License
এই প্রকল্পটি **GNU Affero General Public License v3.0** (AGPL-3.0) এর অধীনে লাইসেন্সপ্রাপ্ত।
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). সর্বস্বত্ব সংরক্ষিত।
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
সম্পূর্ণ বিবরণের জন্য [LICENSE](LICENSE) ফাইল দেখুন।
See the [LICENSE](LICENSE) file for full details.
**এর অর্থ কী:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- আপনি এই সফটওয়্যারটি অবাধে ব্যবহার, পরিবর্তন এবং বিতরণ করতে পারেন
- যদি আপনি পরিবর্তন করেন এবং একটি নেটওয়ার্ক সার্ভারে ডিপ্লয় করেন, তাহলে আপনাকে আপনার সোর্স কোড উপলব্ধ করতে হবে
- ডেরিভেটিভ কাজগুলিও AGPL-3.0 এর অধীনে লাইসেন্সপ্রাপ্ত হতে হবে
- এই সফটওয়্যারের জন্য কোনও ওয়ারেন্টি নেই
**Ragtime সম্পর্কে নোট**: `ragtime/` ডিরেক্টরি আলাদাভাবে **PolyForm Noncommercial License 1.0.0** এর অধীনে লাইসেন্সপ্রাপ্ত। বিস্তারিত জানতে [ragtime/LICENSE](ragtime/LICENSE) দেখুন।
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## সাপোর্ট
- **ডকুমেন্টেশন**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Pracovní postup pro přispívání najdete v [Průvodci vývojem](https://docs.
---
## Licence
## License
Tento projekt je licencován pod **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Všechna práva vyhrazena.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Úplné podrobnosti najdete v souboru [LICENSE](LICENSE).
See the [LICENSE](LICENSE) file for full details.
**Co to znamená:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Software můžete volně používat, upravovat a distribuovat
- Pokud jej upravíte a nasadíte na síťovém serveru, musíte zpřístupnit svůj zdrojový kód
- Odvozená díla musí být také licencována pod AGPL-3.0
- Pro tento software neexistuje ŽÁDNÁ ZÁRUKA
**Poznámka k Ragtime**: Adresář `ragtime/` je licencován samostatně pod **PolyForm Noncommercial License 1.0.0**. Podrobnosti najdete v [ragtime/LICENSE](ragtime/LICENSE).
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Podpora
- **Dokumentace**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Se [Udviklingsguide](https://docs.claude-mem.ai/development) for bidragsworkflow
---
## Licens
## License
Dette projekt er licenseret under **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Alle rettigheder forbeholdes.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Se [LICENSE](LICENSE)-filen for fulde detaljer.
See the [LICENSE](LICENSE) file for full details.
**Hvad Dette Betyder:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Du kan bruge, modificere og distribuere denne software frit
- Hvis du modificerer og implementerer på en netværksserver, skal du gøre din kildekode tilgængelig
- Afledte værker skal også licenseres under AGPL-3.0
- Der er INGEN GARANTI for denne software
**Bemærkning om Ragtime**: `ragtime/`-kataloget er licenseret separat under **PolyForm Noncommercial License 1.0.0**. Se [ragtime/LICENSE](ragtime/LICENSE) for detaljer.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Support
- **Dokumentation**: [docs/](docs/)
+9 -13
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Siehe [Entwicklungsanleitung](https://docs.claude-mem.ai/development) für den B
---
## Lizenz
## License
Dieses Projekt ist unter der **GNU Affero General Public License v3.0** (AGPL-3.0) lizenziert.
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Alle Rechte vorbehalten.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Siehe die [LICENSE](LICENSE)-Datei für vollständige Details.
See the [LICENSE](LICENSE) file for full details.
**Was das bedeutet:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Sie können diese Software frei verwenden, modifizieren und verteilen
- Wenn Sie sie modifizieren und auf einem Netzwerkserver bereitstellen, müssen Sie Ihren Quellcode verfügbar machen
- Abgeleitete Werke müssen ebenfalls unter AGPL-3.0 lizenziert werden
- Es gibt KEINE GARANTIE für diese Software
**Hinweis zu Ragtime**: Das `ragtime/`-Verzeichnis ist separat unter der **PolyForm Noncommercial License 1.0.0** lizenziert. Siehe [ragtime/LICENSE](ragtime/LICENSE) für Details.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Support
- **Dokumentation**: [docs/](docs/)
@@ -301,4 +297,4 @@ Siehe die [LICENSE](LICENSE)-Datei für vollständige Details.
---
**Erstellt mit Claude Agent SDK** | **Powered by Claude Code** | **Made with TypeScript**
**Erstellt mit Claude Agent SDK** | **Works with Claude Code** | **Made with TypeScript**
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## Άδεια Χρήσης
## License
Αυτό το έργο διατίθεται με άδεια **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Με επιφύλαξη παντός δικαιώματος.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Δείτε το αρχείο [LICENSE](LICENSE) για πλήρεις λεπτομέρειες.
See the [LICENSE](LICENSE) file for full details.
**Τι Σημαίνει Αυτό:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Μπορείτε να χρησιμοποιήσετε, να τροποποιήσετε και να διανείμετε ελεύθερα αυτό το λογισμικό
- Εάν τροποποιήσετε και αναπτύξετε σε διακομιστή δικτύου, πρέπει να καταστήσετε διαθέσιμο τον πηγαίο κώδικά σας
- Τα παράγωγα έργα πρέπει επίσης να διατίθενται με άδεια AGPL-3.0
- ΔΕΝ υπάρχει ΕΓΓΥΗΣΗ για αυτό το λογισμικό
**Σημείωση για το Ragtime**: Ο κατάλογος `ragtime/` διατίθεται χωριστά με άδεια **PolyForm Noncommercial License 1.0.0**. Δείτε το [ragtime/LICENSE](ragtime/LICENSE) για λεπτομέρειες.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Υποστήριξη
- **Τεκμηρίωση**: [docs/](docs/)
+8 -12
View File
@@ -51,7 +51,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -274,25 +274,21 @@ Ver [Guía de Desarrollo](https://docs.claude-mem.ai/development) para el flujo
---
## Licencia
## License
Este proyecto está licenciado bajo la **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Todos los derechos reservados.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Ver el archivo [LICENSE](LICENSE) para detalles completos.
See the [LICENSE](LICENSE) file for full details.
**Lo Que Esto Significa:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Puedes usar, modificar y distribuir este software libremente
- Si modificas y despliegas en un servidor de red, debes hacer tu código fuente disponible
- Los trabajos derivados también deben estar licenciados bajo AGPL-3.0
- NO hay GARANTÍA para este software
**Nota sobre Ragtime**: El directorio `ragtime/` está licenciado por separado bajo la **PolyForm Noncommercial License 1.0.0**. Ver [ragtime/LICENSE](ragtime/LICENSE) para detalles.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Soporte
- **Documentación**: [docs/](docs/)
+8 -12
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -272,25 +272,21 @@ Katso [Kehitysopas](https://docs.claude-mem.ai/development) osallistumisen työn
---
## Lisenssi
## License
Tämä projekti on lisensoitu **GNU Affero General Public License v3.0** (AGPL-3.0) -lisenssillä.
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Kaikki oikeudet pidätetään.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Katso [LICENSE](LICENSE)-tiedosto täydellisistä yksityiskohdista.
See the [LICENSE](LICENSE) file for full details.
**Mitä tämä tarkoittaa:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Voit käyttää, muokata ja jakaa tätä ohjelmistoa vapaasti
- Jos muokkaat ja otat käyttöön verkkopalvelimella, sinun on asetettava lähdekoodisi saataville
- Johdannaisten teosten on myös oltava AGPL-3.0-lisensoituja
- Tälle ohjelmistolle EI OLE TAKUUTA
**Huomautus Ragtimesta**: `ragtime/`-hakemisto on erikseen lisensoitu **PolyForm Noncommercial License 1.0.0** -lisenssillä. Katso [ragtime/LICENSE](ragtime/LICENSE) yksityiskohdista.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Tuki
- **Dokumentaatio**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Voir le [Guide de développement](https://docs.claude-mem.ai/development) pour l
---
## Licence
## License
Ce projet est sous licence **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Tous droits réservés.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Voir le fichier [LICENSE](LICENSE) pour tous les détails.
See the [LICENSE](LICENSE) file for full details.
**Ce que cela signifie :**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Vous pouvez utiliser, modifier et distribuer ce logiciel librement
- Si vous modifiez et déployez sur un serveur réseau, vous devez rendre votre code source disponible
- Les œuvres dérivées doivent également être sous licence AGPL-3.0
- Il n'y a AUCUNE GARANTIE pour ce logiciel
**Note sur Ragtime** : Le répertoire `ragtime/` est sous licence séparée sous la **PolyForm Noncommercial License 1.0.0**. Voir [ragtime/LICENSE](ragtime/LICENSE) pour plus de détails.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Support
- **Documentation** : [docs/](docs/)
+8 -12
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -272,25 +272,21 @@ npm run bug-report
---
## רישיון
## License
פרויקט זה מורשה תחת **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
זכויות יוצרים (C) 2025 Alex Newman (@thedotmack). כל הזכויות שמורות.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
ראה את קובץ [LICENSE](LICENSE) לפרטים מלאים.
See the [LICENSE](LICENSE) file for full details.
**משמעות הדבר:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- אתה יכול לשימוש, שינוי והפצה של תוכנה זו בחופשיות
- אם אתה משנה ופורס על שרת רשת, עליך להנגיש את קוד המקור שלך
- עבודות נגזרות חייבות להיות מורשות גם כן תחת AGPL-3.0
- אין אחריות לתוכנה זו
**הערה על Ragtime**: ספריית `ragtime/` מורשית בנפרד תחת **PolyForm Noncommercial License 1.0.0**. ראה [ragtime/LICENSE](ragtime/LICENSE) לפרטים.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## תמיכה
- **תיעוד**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## लाइसेंस
## License
यह प्रोजेक्ट **GNU Affero General Public License v3.0** (AGPL-3.0) के तहत लाइसेंस प्राप्त है।
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack)। सर्वाधिकार सुरक्षित।
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
पूर्ण विवरण के लिए [LICENSE](LICENSE) फ़ाइल देखें।
See the [LICENSE](LICENSE) file for full details.
**इसका क्या अर्थ है:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- आप इस सॉफ़्टवेयर को स्वतंत्र रूप से उपयोग, संशोधित और वितरित कर सकते हैं
- यदि आप नेटवर्क सर्वर पर संशोधित और तैनात करते हैं, तो आपको अपना स्रोत कोड उपलब्ध कराना होगा
- व्युत्पन्न कार्यों को भी AGPL-3.0 के तहत लाइसेंस प्राप्त होना चाहिए
- इस सॉफ़्टवेयर के लिए कोई वारंटी नहीं है
**Ragtime पर नोट**: `ragtime/` डायरेक्टरी को **PolyForm Noncommercial License 1.0.0** के तहत अलग से लाइसेंस प्राप्त है। विवरण के लिए [ragtime/LICENSE](ragtime/LICENSE) देखें।
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## समर्थन
- **दस्तावेज़ीकरण**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ A hozzájárulási munkafolyamatért lásd a [Fejlesztési útmutatót](https://
---
## Licenc
## License
Ez a projekt a **GNU Affero General Public License v3.0** (AGPL-3.0) alatt licencelt.
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Minden jog fenntartva.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
A teljes részletekért lásd a [LICENSE](LICENSE) fájlt.
See the [LICENSE](LICENSE) file for full details.
**Mit jelent ez:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Szabadon használhatja, módosíthatja és terjesztheti ezt a szoftvert
- Ha módosítja és hálózati szerveren telepíti, elérhetővé kell tennie a forráskódot
- A származékos munkáknak szintén AGPL-3.0 alatt kell licencelve lenniük
- Ehhez a szoftverhez NINCS GARANCIA
**Megjegyzés a Ragtime-ról**: A `ragtime/` könyvtár külön licencelt a **PolyForm Noncommercial License 1.0.0** alatt. Részletekért lásd a [ragtime/LICENSE](ragtime/LICENSE) fájlt.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Támogatás
- **Dokumentáció**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Lihat [Panduan Pengembangan](https://docs.claude-mem.ai/development) untuk alur
---
## Lisensi
## License
Proyek ini dilisensikan di bawah **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Lihat file [LICENSE](LICENSE) untuk detail lengkap.
See the [LICENSE](LICENSE) file for full details.
**Apa Artinya:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Anda dapat menggunakan, memodifikasi, dan mendistribusikan perangkat lunak ini dengan bebas
- Jika Anda memodifikasi dan men-deploy di server jaringan, Anda harus membuat kode sumber Anda tersedia
- Karya turunan juga harus dilisensikan di bawah AGPL-3.0
- TIDAK ADA JAMINAN untuk perangkat lunak ini
**Catatan tentang Ragtime**: Direktori `ragtime/` dilisensikan secara terpisah di bawah **PolyForm Noncommercial License 1.0.0**. Lihat [ragtime/LICENSE](ragtime/LICENSE) untuk detail.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Dukungan
- **Dokumentasi**: [docs/](docs/)
@@ -301,6 +297,6 @@ Lihat file [LICENSE](LICENSE) untuk detail lengkap.
---
**Built with Claude Agent SDK** | **Powered by Claude Code** | **Made with TypeScript**
**Built with Claude Agent SDK** | **Works with Claude Code** | **Made with TypeScript**
---
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Vedi [Guida allo Sviluppo](https://docs.claude-mem.ai/development) per il flusso
---
## Licenza
## License
Questo progetto è rilasciato sotto la **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Tutti i diritti riservati.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Vedi il file [LICENSE](LICENSE) per i dettagli completi.
See the [LICENSE](LICENSE) file for full details.
**Cosa Significa:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Puoi usare, modificare e distribuire questo software liberamente
- Se modifichi e distribuisci su un server di rete, devi rendere disponibile il tuo codice sorgente
- Le opere derivate devono anche essere rilasciate sotto AGPL-3.0
- NON c'è GARANZIA per questo software
**Nota su Ragtime**: La directory `ragtime/` è rilasciata separatamente sotto la **PolyForm Noncommercial License 1.0.0**. Vedi [ragtime/LICENSE](ragtime/LICENSE) per i dettagli.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Supporto
- **Documentazione**: [docs/](docs/)
+7 -11
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## ライセンス
## License
このプロジェクトは**GNU Affero General Public License v3.0**(AGPL-3.0)の下でライセンスされています。
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
詳細は[LICENSE](LICENSE)ファイルを参照してください。
See the [LICENSE](LICENSE) file for full details.
**これが意味すること:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- このソフトウェアを自由に使用、変更、配布できます
- ネットワークサーバーで変更して展開する場合、ソースコードを利用可能にする必要があります
- 派生作品もAGPL-3.0の下でライセンスする必要があります
- このソフトウェアには保証がありません
**Ragtimeに関する注意**: `ragtime/`ディレクトリは **PolyForm Noncommercial License 1.0.0** の下で個別にライセンスされています。詳細は[ragtime/LICENSE](ragtime/LICENSE)を参照してください。
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## サポート
- **ドキュメント**: [docs/](docs/)
+7 -11
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## 라이선스
## License
이 프로젝트는 **GNU Affero General Public License v3.0** (AGPL-3.0)에 따라 라이선스가 부여됩니다.
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
전체 세부 정보는 [LICENSE](LICENSE) 파일을 참조하세요.
See the [LICENSE](LICENSE) file for full details.
**의미:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- 이 소프트웨어를 자유롭게 사용, 수정 및 배포할 수 있습니다
- 수정하여 네트워크 서버에 배포하는 경우 소스 코드를 공개해야 합니다
- 파생 작업물도 AGPL-3.0에 따라 라이선스가 부여되어야 합니다
- 이 소프트웨어에는 보증이 없습니다
**Ragtime에 대한 참고 사항**: `ragtime/` 디렉토리는 **PolyForm Noncommercial License 1.0.0**에 따라 별도로 라이선스가 부여됩니다. 자세한 내용은 [ragtime/LICENSE](ragtime/LICENSE)를 참조하세요.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## 지원
- **문서**: [docs/](docs/)
+8 -12
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -272,25 +272,21 @@ Zie [Ontwikkelingsgids](https://docs.claude-mem.ai/development) voor bijdragewor
---
## Licentie
## License
Dit project is gelicentieerd onder de **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Alle rechten voorbehouden.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Zie het [LICENSE](LICENSE) bestand voor volledige details.
See the [LICENSE](LICENSE) file for full details.
**Wat Dit Betekent:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Je kunt deze software vrijelijk gebruiken, aanpassen en distribueren
- Als je aanpast en implementeert op een netwerkserver, moet je je broncode beschikbaar maken
- Afgeleide werken moeten ook gelicentieerd zijn onder AGPL-3.0
- Er is GEEN GARANTIE voor deze software
**Opmerking over Ragtime**: De `ragtime/` directory is afzonderlijk gelicentieerd onder de **PolyForm Noncommercial License 1.0.0**. Zie [ragtime/LICENSE](ragtime/LICENSE) voor details.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Ondersteuning
- **Documentatie**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Se [Utviklingsveiledning](https://docs.claude-mem.ai/development) for bidragsfly
---
## Lisens
## License
Dette prosjektet er lisensiert under **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Alle rettigheter reservert.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Se [LICENSE](LICENSE)-filen for fullstendige detaljer.
See the [LICENSE](LICENSE) file for full details.
**Hva Dette Betyr:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Du kan bruke, modifisere og distribuere denne programvaren fritt
- Hvis du modifiserer og distribuerer på en nettverkstjener, må du gjøre kildekoden din tilgjengelig
- Avledede verk må også være lisensiert under AGPL-3.0
- Det er INGEN GARANTI for denne programvaren
**Merknad om Ragtime**: `ragtime/`-katalogen er lisensiert separat under **PolyForm Noncommercial License 1.0.0**. Se [ragtime/LICENSE](ragtime/LICENSE) for detaljer.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Støtte
- **Dokumentasjon**: [docs/](docs/)
+8 -12
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -272,25 +272,21 @@ Zobacz [Przewodnik Rozwoju](https://docs.claude-mem.ai/development) dla przepły
---
## Licencja
## License
Ten projekt jest licencjonowany na podstawie **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Wszelkie prawa zastrzeżone.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Zobacz plik [LICENSE](LICENSE) dla pełnych szczegółów.
See the [LICENSE](LICENSE) file for full details.
**Co To Oznacza:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Możesz używać, modyfikować i dystrybuować to oprogramowanie swobodnie
- Jeśli zmodyfikujesz i wdrożysz na serwerze sieciowym, musisz udostępnić swój kod źródłowy
- Dzieła pochodne muszą być również licencjonowane na podstawie AGPL-3.0
- Nie ma GWARANCJI dla tego oprogramowania
**Uwaga o Ragtime**: Katalog `ragtime/` jest licencjonowany osobno na podstawie **PolyForm Noncommercial License 1.0.0**. Zobacz [ragtime/LICENSE](ragtime/LICENSE) dla szczegółów.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Wsparcie
- **Dokumentacja**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Veja [Guia de Desenvolvimento](https://docs.claude-mem.ai/development) para o fl
---
## Licença
## License
Este projeto está licenciado sob a **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Todos os direitos reservados.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Veja o arquivo [LICENSE](LICENSE) para detalhes completos.
See the [LICENSE](LICENSE) file for full details.
**O Que Isso Significa:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Você pode usar, modificar e distribuir este software livremente
- Se você modificar e implantar em um servidor de rede, você deve disponibilizar seu código-fonte
- Trabalhos derivados também devem ser licenciados sob AGPL-3.0
- NÃO HÁ GARANTIA para este software
**Nota sobre Ragtime**: O diretório `ragtime/` é licenciado separadamente sob a **PolyForm Noncommercial License 1.0.0**. Veja [ragtime/LICENSE](ragtime/LICENSE) para detalhes.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Suporte
- **Documentação**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Consultați [Ghidul de Dezvoltare](https://docs.claude-mem.ai/development) pentr
---
## Licență
## License
Acest proiect este licențiat sub **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Toate drepturile rezervate.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Consultați fișierul [LICENSE](LICENSE) pentru detalii complete.
See the [LICENSE](LICENSE) file for full details.
**Ce Înseamnă Asta:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Puteți folosi, modifica și distribui acest software liber
- Dacă modificați și implementați pe un server de rețea, trebuie să faceți disponibil codul sursă
- Lucrările derivate trebuie să fie licențiate și ele sub AGPL-3.0
- NU EXISTĂ NICIO GARANȚIE pentru acest software
**Notă despre Ragtime**: Directorul `ragtime/` este licențiat separat sub **PolyForm Noncommercial License 1.0.0**. Consultați [ragtime/LICENSE](ragtime/LICENSE) pentru detalii.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Suport
- **Documentație**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## Лицензия
## License
Этот проект лицензирован под **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Все права защищены.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Полные сведения см. в файле [LICENSE](LICENSE).
See the [LICENSE](LICENSE) file for full details.
**Что это означает:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Вы можете свободно использовать, модифицировать и распространять это программное обеспечение
- Если вы модифицируете и развертываете на сетевом сервере, вы должны сделать свой исходный код доступным
- Производные работы также должны быть лицензированы под AGPL-3.0
- Для этого программного обеспечения НЕТ ГАРАНТИЙ
**Примечание о Ragtime**: Директория `ragtime/` лицензирована отдельно под **PolyForm Noncommercial License 1.0.0**. Подробности см. в [ragtime/LICENSE](ragtime/LICENSE).
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Поддержка
- **Документация**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Se [Utvecklingsguide](https://docs.claude-mem.ai/development) för bidragsarbets
---
## Licens
## License
Detta projekt är licensierat under **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Alla rättigheter förbehållna.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Se [LICENSE](LICENSE)-filen för fullständiga detaljer.
See the [LICENSE](LICENSE) file for full details.
**Vad detta betyder:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Du kan använda, modifiera och distribuera denna programvara fritt
- Om du modifierar och distribuerar på en nätverksserver måste du göra din källkod tillgänglig
- Härledda verk måste också licensieras under AGPL-3.0
- Det finns INGEN GARANTI för denna programvara
**Notering om Ragtime**: Katalogen `ragtime/` är licensierad separat under **PolyForm Noncommercial License 1.0.0**. Se [ragtime/LICENSE](ragtime/LICENSE) för detaljer.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Support
- **Dokumentation**: [docs/](docs/)
+8 -12
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -272,25 +272,21 @@ npm run bug-report
---
## ใบอนุญาต
## License
โปรเจกต์นี้ได้รับอนุญาตภายใต้ **GNU Affero General Public License v3.0** (AGPL-3.0)
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack) สงวนลิขสิทธิ์ทั้งหมด
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
ดูไฟล์ [LICENSE](LICENSE) สำหรับรายละเอียดทั้งหมด
See the [LICENSE](LICENSE) file for full details.
**ความหมาย:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- คุณสามารถใช้ ดัดแปลง และแจกจ่ายซอฟต์แวร์นี้ได้อย่างอิสระ
- หากคุณดัดแปลงและปรับใช้บนเซิร์ฟเวอร์เครือข่าย คุณต้องทำให้ซอร์สโค้ดของคุณพร้อมใช้งาน
- งานที่เป็นอนุพันธ์ต้องได้รับอนุญาตภายใต้ AGPL-3.0 ด้วย
- ไม่มีการรับประกันสำหรับซอฟต์แวร์นี้
**หมายเหตุเกี่ยวกับ Ragtime**: ไดเรกทอรี `ragtime/` ได้รับอนุญาตแยกต่างหากภายใต้ **PolyForm Noncommercial License 1.0.0** ดู [ragtime/LICENSE](ragtime/LICENSE) สำหรับรายละเอียด
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## การสนับสนุน
- **เอกสาร**: [docs/](docs/)
+8 -12
View File
@@ -51,7 +51,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -297,25 +297,21 @@ Tingnan ang [Gabay nang pagbuo](https://docs.claude-mem.ai/development) para sa
---
## Lisensya
## License
Ang proyektong ito ay licensed sa ilalim ng **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Tingnan ang [LICENSE](LICENSE) file para sa buong detalye.
See the [LICENSE](LICENSE) file for full details.
**Ano ang ibig sabihin nito:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Maaari mong gamitin, baguhin, at ipamahagi ang software na ito nang libre
- Kung babaguhin mo at i-deploy sa isang network server, kailangan mong gawing available ang iyong source code
- Dapat ding naka-license sa AGPL-3.0 ang mga derivative works
- WALANG WARRANTY para sa software na ito
**Tala tungkol sa Ragtime**: Ang `ragtime/` directory ay may hiwalay na lisensya sa ilalim ng **PolyForm Noncommercial License 1.0.0**. Tingnan ang [ragtime/LICENSE](ragtime/LICENSE) para sa detalye.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Suporta
- **Dokumentasyon**: [docs/](docs/)
@@ -325,4 +321,4 @@ Tingnan ang [LICENSE](LICENSE) file para sa buong detalye.
---
**Built with Claude Agent SDK** | **Powered by Claude Code** | **Made with TypeScript**
**Built with Claude Agent SDK** | **Works with Claude Code** | **Made with TypeScript**
+8 -12
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -272,25 +272,21 @@ Katkı iş akışı için [Geliştirme Kılavuzu](https://docs.claude-mem.ai/dev
---
## Lisans
## License
Bu proje **GNU Affero General Public License v3.0** (AGPL-3.0) altında lisanslanmıştır.
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Telif Hakkı (C) 2025 Alex Newman (@thedotmack). Tüm hakları saklıdır.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Tam detaylar için [LICENSE](LICENSE) dosyasına bakın.
See the [LICENSE](LICENSE) file for full details.
**Bu Ne Anlama Gelir:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Bu yazılımı özgürce kullanabilir, değiştirebilir ve dağıtabilirsiniz
- Değiştirip bir ağ sunucusunda dağıtırsanız, kaynak kodunuzu kullanılabilir hale getirmelisiniz
- Türev çalışmalar da AGPL-3.0 altında lisanslanmalıdır
- Bu yazılım için HİÇBİR GARANTİ yoktur
**Ragtime Hakkında Not**: `ragtime/` dizini ayrı olarak **PolyForm Noncommercial License 1.0.0** altında lisanslanmıştır. Detaylar için [ragtime/LICENSE](ragtime/LICENSE) dosyasına bakın.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Destek
- **Dokümantasyon**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## Ліцензія
## License
Цей проєкт ліцензовано під **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Авторське право (C) 2025 Alex Newman (@thedotmack). Всі права захищені.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Дивіться файл [LICENSE](LICENSE) для повних деталей.
See the [LICENSE](LICENSE) file for full details.
**Що це означає:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Ви можете використовувати, модифікувати та поширювати це програмне забезпечення вільно
- Якщо ви модифікуєте та розгортаєте на мережевому сервері, ви повинні зробити свій вихідний код доступним
- Похідні роботи також повинні бути ліцензовані під AGPL-3.0
- Для цього програмного забезпечення НЕМАЄ ГАРАНТІЇ
**Примітка про Ragtime**: Каталог `ragtime/` ліцензовано окремо під **PolyForm Noncommercial License 1.0.0**. Дивіться [ragtime/LICENSE](ragtime/LICENSE) для деталей.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Підтримка
- **Документація**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -278,25 +278,21 @@ npm run bug-report
---
## لائسنس
## License
یہ منصوبہ **GNU Affero General Public License v3.0** (AGPL-3.0) کے تحت لائسنس ہے۔
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack)۔ تمام حقوق محفوظ ہیں۔
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
مکمل تفصیلات کے لیے [LICENSE](LICENSE) فائل دیکھیں۔
See the [LICENSE](LICENSE) file for full details.
**اس کا مطلب کیا ہے:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- آپ اس سافٹ ویئر کو آزادی سے استعمال، تبدیل اور تقسیم کر سکتے ہیں
- اگر آپ اسے تبدیل کریں اور نیٹ ورک سرور میں نشر کریں تو آپ کو اپنا سورس کوڈ دستیاب کرنا ہوگا
- ماخوذ کام بھی AGPL-3.0 کے تحت لائسنس ہونے چاہیں
- اس سافٹ ویئر کے لیے کوئی وارنٹی نہیں
**Ragtime کے بارے میں نوٹ**: `ragtime/` ڈائریکٹری الگ سے **PolyForm Noncommercial License 1.0.0** کے تحت لائسنس ہے۔ تفصیلات کے لیے [ragtime/LICENSE](ragtime/LICENSE) دیکھیں۔
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## معاونت
- **دستاویزات**: [docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ Xem [Hướng Dẫn Phát Triển](https://docs.claude-mem.ai/development) để
---
## Giấy Phép
## License
Dự án này được cấp phép theo **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Bảo lưu mọi quyền.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Xem tệp [LICENSE](LICENSE) để biết chi tiết đầy đủ.
See the [LICENSE](LICENSE) file for full details.
**Điều Này Có Nghĩa Là:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Bạn có thể sử dụng, sửa đổi và phân phối phần mềm này tự do
- Nếu bạn sửa đổi và triển khai trên máy chủ mạng, bạn phải cung cấp mã nguồn của mình
- Các tác phẩm phái sinh cũng phải được cấp phép theo AGPL-3.0
- KHÔNG CÓ BẢO HÀNH cho phần mềm này
**Lưu Ý Về Ragtime**: Thư mục `ragtime/` được cấp phép riêng theo **PolyForm Noncommercial License 1.0.0**. Xem [ragtime/LICENSE](ragtime/LICENSE) để biết chi tiết.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Hỗ Trợ
- **Tài Liệu**: [docs/](docs/)
+7 -11
View File
@@ -49,7 +49,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -278,25 +278,21 @@ npm run bug-report
---
## 授權條款
## License
本專案採用 **GNU Affero 通用公共授權條款 v3.0**AGPL-3.0)授權。
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
完整詳情請參閱 [LICENSE](LICENSE) 檔案。
See the [LICENSE](LICENSE) file for full details.
**這代表什麼:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- 您可以自由使用、修改與散佈此軟體
- 如果您修改並部署於網路伺服器上,您必須公開您的原始碼
- 衍生作品也必須採用 AGPL-3.0 授權
- 本軟體不提供任何擔保
**關於 Ragtime 的說明**`ragtime/` 目錄採用 **PolyForm Noncommercial License 1.0.0** 另行授權。詳情請參閱 [ragtime/LICENSE](ragtime/LICENSE)。
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## 支援
- **文件**[docs/](docs/)
+8 -12
View File
@@ -50,7 +50,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -273,25 +273,21 @@ npm run bug-report
---
## 许可证
## License
本项目采用 **GNU Affero General Public License v3.0** (AGPL-3.0) 许可。
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack)。保留所有权利。
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
详见 [LICENSE](LICENSE) 文件了解完整详情。
See the [LICENSE](LICENSE) file for full details.
**这意味着什么:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- 您可以自由使用、修改和分发本软件
- 如果您修改并部署到网络服务器上,必须公开您的源代码
- 衍生作品也必须采用 AGPL-3.0 许可
- 本软件不提供任何保证
**关于 Ragtime 的说明**: `ragtime/` 目录单独采用 **PolyForm Noncommercial License 1.0.0** 许可。详见 [ragtime/LICENSE](ragtime/LICENSE)。
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## 支持
- **文档**: [docs/](docs/)
+8 -12
View File
@@ -51,7 +51,7 @@
<p align="center">
<a href="LICENSE">
<img src="https://img.shields.io/badge/License-AGPL%203.0-blue.svg" alt="License">
<img src="https://img.shields.io/badge/License-Apache--2.0-blue.svg" alt="License">
</a>
<a href="package.json">
<img src="https://img.shields.io/badge/version-6.5.0-green.svg" alt="Version">
@@ -274,25 +274,21 @@ Veja [Guia de Desenvolvimento](https://docs.claude-mem.ai/development) para o fl
---
## Licença
## License
Este projeto está licenciado sob a **GNU Affero General Public License v3.0** (AGPL-3.0).
This project is licensed under the **Apache License 2.0** (Apache-2.0).
Copyright (C) 2025 Alex Newman (@thedotmack). Todos os direitos reservados.
Copyright (C) 2025 Alex Newman (@thedotmack). All rights reserved.
Veja o arquivo [LICENSE](LICENSE) para detalhes completos.
See the [LICENSE](LICENSE) file for full details.
**O Que Isso Significa:**
Apache-2.0 allows broad use, modification, distribution, and commercial use, subject to its terms.
- Você pode usar, modificar e distribuir este software livremente
- Se você modificar e implantar em um servidor de rede, você deve disponibilizar seu código-fonte
- Trabalhos derivados também devem ser licenciados sob AGPL-3.0
- NÃO HÁ GARANTIA para este software
**Nota sobre Ragtime**: O diretório `ragtime/` é licenciado separadamente sob a **PolyForm Noncommercial License 1.0.0**. Veja [ragtime/LICENSE](ragtime/LICENSE) para detalhes.
**Ragtime note**: The ragtime/ directory is licensed under the **Apache License 2.0**. See [ragtime/LICENSE](ragtime/LICENSE) for details.
---
## Suporte
- **Documentação**: [docs/](docs/)
+45
View File
@@ -0,0 +1,45 @@
# IP Boundary
Claude-Mem uses an open-core structure.
## Apache-2.0 components
- Core memory engine
- Claude-Mem Server
- CLI
- SDKs
- REST API schemas
- MCP tools/resources/prompts
- Claude Code adapter
- Generic agent adapters
- Storage adapters
- Reference knowledge agents
- Tests
- Examples
- Public documentation
## Reserved commercial/private areas
These areas are not shipped by Claude-Mem Server v0.1 and should remain outside
the Apache-2.0 public implementation unless maintainers explicitly open-source
them later.
- Magic Recall hosted cloud
- Team/org memory sync
- Admin dashboard
- SSO/SAML/SCIM
- Enterprise RBAC
- Enterprise audit log UI
- DLP/policy engine
- Premium knowledge agents
- Managed evals
- Customer deployment tooling
- Enterprise observability
- Support/SLA workflows
- Internal eval datasets
- Private customer connectors
## Rule
Do not put commercial/private implementation code into the Apache-2.0 public repo
unless the maintainers intentionally decide to open-source it.
+29
View File
@@ -0,0 +1,29 @@
# License
Claude-Mem is licensed under the Apache License 2.0.
The Apache-2.0 license applies to the open-source core, including the memory
engine, Claude-Mem Server, CLI, SDKs, adapters, MCP tools, schemas, tests,
examples, and public documentation.
Apache-2.0 allows broad use, modification, distribution, and commercial use,
subject to the license terms.
## Why Apache-2.0?
Claude-Mem is intended to be embedded broadly inside developer tools, local
agents, MCP clients, enterprise systems, robotics stacks, and production agent
harnesses. Apache-2.0 supports that goal while preserving attribution and
including explicit patent license terms.
## Reserved commercial/private areas
Claude-Mem Server v0.1 does not ship hosted cloud, team sync, enterprise
features, premium knowledge agents, private evals, or customer deployment
tooling. Those areas are reserved outside the Apache-2.0 public implementation
unless maintainers explicitly open-source them later.
## Third-party marks
Apache-2.0 licenses code. It does not grant rights to third-party trademarks or
brand names.
+13
View File
@@ -0,0 +1,13 @@
# Worker To Server Migration
Claude-Mem 13 keeps the worker path in place. Server beta is an additional runtime option for teams, deployable containers, API keys, and BullMQ/Valkey queues.
Compatibility commands remain available:
```sh
claude-mem start
claude-mem worker start
claude-mem server start
```
The server storage boundary reads legacy worker data while adding server-owned projects, sessions, agent events, memory items, teams, API keys, and audit logs. Migrate adapters gradually by writing to `/v1/events` and `/v1/memories`; keep existing `/api/*` hook routes enabled until all clients move.
-88
View File
@@ -1,88 +0,0 @@
# Claude-Mem Public Documentation
## What This Folder Is
This `docs/public/` folder contains the **Mintlify documentation site** - the official user-facing documentation for claude-mem. It's a structured documentation platform with a specific file format and organization.
## Folder Structure
```
docs/
├── public/ ← You are here (Mintlify MDX files)
│ ├── *.mdx - User-facing documentation pages
│ ├── docs.json - Mintlify configuration and navigation
│ ├── architecture/ - Technical architecture docs
│ ├── usage/ - User guides and workflows
│ └── *.webp, *.gif - Assets (logos, screenshots)
└── context/ ← Internal documentation (DO NOT put here)
└── *.md - Planning docs, audits, references
```
## File Requirements
### Mintlify Documentation Files (.mdx)
All official documentation files must be:
- Written in `.mdx` format (Markdown with JSX support)
- Listed in `docs.json` navigation structure
- Follow Mintlify's schema and conventions
The documentation is organized into these sections:
- **Get Started**: Introduction, installation, usage guides
- **Best Practices**: Context engineering, progressive disclosure
- **Configuration & Development**: Settings, dev workflow, troubleshooting
- **Architecture**: System design, components, technical details
### Configuration File
`docs.json` defines:
- Site metadata (name, description, theme)
- Navigation structure
- Branding (logos, colors)
- Footer links and social media
## What Does NOT Belong Here
**Planning documents, design docs, and reference materials go in `/docs/context/` instead:**
Files that belong in `/docs/context/` (NOT here):
- Planning documents (`*-plan.md`, `*-outline.md`)
- Implementation analysis (`*-audit.md`, `*-code-reference.md`)
- Error tracking (`typescript-errors.md`)
- Internal design documents
- PR review responses
- Reference materials (like `agent-sdk-ref.md`)
- Work-in-progress documentation
## How to Add Official Documentation
1. Create a new `.mdx` file in the appropriate subdirectory
2. Add the file path to `docs.json` navigation
3. Use Mintlify's frontmatter and components
4. Follow the existing documentation style
5. Test locally: `npx mintlify dev`
## Development Workflow
**For contributors working on claude-mem:**
- Read `/CLAUDE.md` in the project root for development instructions
- Place planning/design docs in `/docs/context/`
- Only add user-facing documentation to `/docs/public/`
- Test documentation locally with Mintlify CLI before committing
## Testing Documentation
```bash
# Validate docs structure
npx mintlify validate
# Check for broken links
npx mintlify broken-links
# Run local dev server
npx mintlify dev
```
## Summary
**Simple Rule**:
- `/docs/public/` = Official user documentation (Mintlify .mdx files) ← YOU ARE HERE
- `/docs/context/` = Internal docs, plans, references, audits
+11 -7
View File
@@ -190,13 +190,16 @@ Hooks are configured in `plugin/hooks/hooks.json`:
```json
{
"hooks": {
"Setup": [{
"hooks": [{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/version-check.js",
"timeout": 60
}]
}],
"SessionStart": [{
"matcher": "startup|clear|compact",
"hooks": [{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js",
"timeout": 300
}, {
"type": "command",
"command": "bun ${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs start",
"timeout": 60
@@ -246,9 +249,10 @@ Hooks are configured in `plugin/hooks/hooks.json`:
**Timing**: When user opens Claude Code or resumes session
**Hooks Triggered** (in order):
1. `smart-install.js` - Ensures dependencies are installed
2. `worker-service.cjs start` - Starts the worker service
3. `context-hook.js` - Fetches and silently injects prior session context
1. `worker-service.cjs start` - Starts the worker service
2. `context-hook.js` - Fetches and silently injects prior session context
(Runtime setup is handled out-of-band by `npx claude-mem install` / `npx claude-mem repair`. The Setup phase runs a sub-100ms `version-check.js` that prompts the user to repair if the `.install-version` marker is stale.)
<Note>
As of Claude Code 2.1.0 (ultrathink update), SessionStart hooks no longer display user-visible messages. Context is silently injected via `hookSpecificOutput.additionalContext`.
+55 -95
View File
@@ -7,30 +7,29 @@ description: "System components and data flow in Claude-Mem"
## System Components
Claude-Mem operates as a Claude Code plugin with five core components:
Claude-Mem operates as a Claude Code plugin with the following core components:
1. **Plugin Hooks** - Capture lifecycle events (6 hook files)
2. **Smart Install** - Cached dependency checker (pre-hook script, runs before context-hook)
3. **Worker Service** - Process observations via Claude Agent SDK + HTTP API (10 search endpoints)
4. **Database Layer** - Store sessions and observations (SQLite + FTS5 + ChromaDB)
5. **mem-search Skill** - Skill-based search with progressive disclosure (v5.4.0+)
6. **Viewer UI** - Web-based real-time memory stream visualization
1. **Plugin Hooks** - Lifecycle events (Setup version-check + 5 lifecycle hooks: SessionStart, UserPromptSubmit, PreToolUse for `Read`, PostToolUse, Stop)
2. **Worker Service** - Express HTTP API on a per-user port; processes observations via the Claude Agent SDK (or Gemini / OpenRouter)
3. **Database Layer** - SQLite + FTS5 (and optional Chroma for semantic search)
4. **Search Tools** - HTTP API + the `mem-search` skill / MCP server for progressive disclosure search
5. **Viewer UI** - React-based real-time memory stream served by the worker
## Technology Stack
| Layer | Technology |
|------------------------|-------------------------------------------|
| **Language** | TypeScript (ES2022, ESNext modules) |
| **Runtime** | Node.js 18+ |
| **Runtime** | Node.js 20+ and Bun ≥ 1.0 |
| **Database** | SQLite 3 with bun:sqlite driver |
| **Vector Store** | ChromaDB (optional, for semantic search) |
| **HTTP Server** | Express.js 4.18 |
| **Vector Store** | Chroma (optional, for semantic search) |
| **HTTP Server** | Express.js 5 |
| **Real-time** | Server-Sent Events (SSE) |
| **UI Framework** | React + TypeScript |
| **AI SDK** | @anthropic-ai/claude-agent-sdk |
| **AI SDK** | @anthropic-ai/claude-agent-sdk (or Gemini / OpenRouter) |
| **Build Tool** | esbuild (bundles TypeScript) |
| **Process Manager** | Bun |
| **Testing** | Node.js built-in test runner |
| **Testing** | `bun test` |
## Data Flow
@@ -63,13 +62,13 @@ Uses 3-layer progressive disclosure: search → timeline → get_observations
```
┌─────────────────────────────────────────────────────────────────┐
│ 0. Smart Install Pre-Hook Fires
Checks dependencies (cached), only runs on version changes
Not a lifecycle hook - runs before context-hook starts │
│ 0. Setup Hook Fires (version-check.js)
Sub-100ms read of .install-version; on mismatch prints
"run: npx claude-mem repair" to stderr. Always exits 0.
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 1. Session Starts → Context Hook Fires
│ 1. Session Starts → Worker-start, then Context Hook
│ Starts Bun worker if needed, injects context from previous │
│ sessions (configurable observation count) │
└─────────────────────────────────────────────────────────────────┘
@@ -106,99 +105,60 @@ Uses 3-layer progressive disclosure: search → timeline → get_observations
```
claude-mem/
├── src/
│ ├── hooks/ # Hook implementations (6 hooks)
│ │ ├── context-hook.ts # SessionStart
│ │ ├── user-message-hook.ts # UserMessage (for debugging)
│ │ ├── new-hook.ts # UserPromptSubmit
│ │ ├── save-hook.ts # PostToolUse
│ │ ├── summary-hook.ts # Stop
│ │ ├── cleanup-hook.ts # SessionEnd
│ │ └── hook-response.ts # Hook response utilities
│ │
│ ├── hooks/ # TypeScript hook implementations (built via esbuild)
│ ├── sdk/ # Claude Agent SDK integration
│ │ ├── prompts.ts # XML prompt builders
│ │ ├── parser.ts # XML response parser
│ │ └── worker.ts # Main SDK agent loop
│ │
│ ├── services/
│ │ ├── worker-service.ts # Express HTTP + SSE service
│ │ ── sqlite/ # Database layer
│ │ ├── SessionStore.ts # CRUD operations
│ ├── SessionSearch.ts # FTS5 search service
│ ├── migrations.ts
│ └── types.ts
│ │
│ ├── ui/ # Viewer UI
│ │ └── viewer/ # React + TypeScript web interface
│ │ ├── components/ # UI components
│ │ ├── hooks/ # React hooks
│ │ ├── utils/ # Utilities
│ │ └── assets/ # Fonts, logos
│ │
│ ├── shared/ # Shared utilities
│ │ ├── config.ts
│ │ ├── paths.ts
│ │ └── storage.ts
│ │
│ └── utils/
│ ├── logger.ts
│ ├── platform.ts
│ └── port-allocator.ts
│ │ ├── worker-service.ts # Express HTTP + SSE service (worker entry point)
│ │ ── sync/ChromaSync.ts # Optional Chroma vector index
│ │ └── sqlite/ # SQLite + FTS5 storage layer
├── ui/viewer/ # React + TypeScript web viewer
├── shared/ # Shared utilities (paths, settings defaults)
└── utils/ # Logging, platform, tag-stripping helpers
├── scripts/ # Build and utility scripts
│ └── smart-install.js # Cached dependency checker (pre-hook)
├── scripts/ # Build + utility scripts
├── plugin/ # Plugin distribution
│ ├── .claude-plugin/
│ └── plugin.json
│ ├── hooks/
│ │ └── hooks.json
├── plugin/ # Plugin distribution (synced to marketplace)
│ ├── .claude-plugin/plugin.json
├── hooks/hooks.json # Hook registration (Setup + 5 lifecycle hooks)
│ ├── scripts/ # Built executables
│ │ ├── context-hook.js
│ │ ├── user-message-hook.js
│ │ ├── new-hook.js
│ │ ├── save-hook.js
│ │ ├── summary-hook.js
│ │ ├── cleanup-hook.js
│ │ ── worker-service.cjs # Background worker + HTTP API
│ │
│ ├── skills/ # Agent skills (v5.4.0+)
│ ├── mem-search/ # Search skill with progressive disclosure (v5.5.0)
│ │ │ ├── SKILL.md # Skill frontmatter (~250 tokens)
│ │ │ ├── operations/ # 12 detailed operation docs
│ │ │ └── principles/ # 2 principle guides
│ │ ├── troubleshoot/ # Troubleshooting skill
│ │ │ ├── SKILL.md
│ │ │ └── operations/ # 6 operation docs
│ │ └── version-bump/ # Version management skill (deprecated)
│ │
│ └── ui/ # Built viewer UI
│ └── viewer.html # Self-contained bundle
│ │ ├── version-check.js # Setup-phase marker check (sub-100ms)
│ │ ├── bun-runner.js # Resolves Bun and runs worker-service.cjs
│ │ ├── worker-service.cjs # Worker daemon + lifecycle hook dispatcher
│ │ ├── worker-cli.js # CLI shim
│ │ ├── worker-wrapper.cjs # Process wrapper
│ │ ├── mcp-server.cjs # MCP search server
│ │ ── statusline-counts.js
│ │ └── context-generator.cjs
│ ├── skills/ # Agent skills (mem-search, make-plan, do, etc.)
└── ui/viewer.html # Self-contained React bundle
├── tests/ # Test suite
├── docs/ # Documentation
└── ecosystem.config.cjs # Process configuration (deprecated)
├── tests/ # Test suite (`bun test`)
├── docs/ # Mintlify documentation
└── openclaw/ # OpenClaw integration plugin
```
## Component Details
### 1. Plugin Hooks (6 Hooks)
- **context-hook.js** - SessionStart: Starts Bun worker, injects context
- **user-message-hook.js** - UserMessage: Debugging hook
- **new-hook.js** - UserPromptSubmit: Creates session, saves prompt
- **save-hook.js** - PostToolUse: Captures tool executions
- **summary-hook.js** - Stop: Generates session summary
- **cleanup-hook.js** - SessionEnd: Marks session complete
### 1. Plugin Hooks
**Note**: smart-install.js is a pre-hook dependency checker (not a lifecycle hook). It's called before context-hook via command chaining in hooks.json and only runs when dependencies need updating.
The plugin registers a Setup-phase `version-check.js` plus five lifecycle hooks. Each lifecycle event invokes `bun-runner.js` to spawn `worker-service.cjs` with a `hook claude-code <event>` argument; the worker process is the single dispatcher for all hook logic. Events:
- **Setup** → `version-check.js` (sub-100ms marker check; never installs anything)
- **SessionStart** → start worker, then `hook claude-code context` (context injection)
- **UserPromptSubmit** → `hook claude-code session-init`
- **PreToolUse** (matcher `Read`) → `hook claude-code file-context`
- **PostToolUse** (matcher `*`) → `hook claude-code observation`
- **Stop** → `hook claude-code summarize` (summary generation)
The actual runtime install (Bun, uv, `bun install`) is performed by `npx claude-mem install` / `npx claude-mem repair` with a visible installer spinner; the Setup hook itself only reads the `.install-version` marker.
See [Plugin Hooks](/architecture/hooks) for detailed hook documentation.
### 2. Worker Service
Express.js HTTP server on port 37777 (configurable) with:
- 10 search HTTP API endpoints (v5.4.0+)
- 8 viewer UI HTTP/SSE endpoints
- Async observation processing via Claude Agent SDK
Express.js HTTP server on a per-user port (default `37700 + (uid % 100)`, override via `CLAUDE_MEM_WORKER_PORT`) with:
- Search HTTP API endpoints
- Viewer UI HTTP/SSE endpoints
- Async observation processing via the Claude Agent SDK (or Gemini / OpenRouter)
- Real-time updates via Server-Sent Events
- Auto-managed by Bun
@@ -230,7 +190,7 @@ Skill-based search with progressive disclosure providing 10 search operations:
See [Search Architecture](/architecture/search-architecture) for technical details and examples.
### 5. Viewer UI
React + TypeScript web interface at http://localhost:37777 featuring:
React + TypeScript web interface served by the worker on its configured port (default `http://127.0.0.1:<worker-port>`) featuring:
- Real-time memory stream via Server-Sent Events
- Infinite scroll pagination with automatic deduplication
- Project filtering and settings persistence
+17 -15
View File
@@ -12,10 +12,10 @@ The worker service is a long-running HTTP API built with Express.js and managed
- **Technology**: Express.js HTTP server
- **Runtime**: Bun (auto-installed if missing)
- **Process Manager**: Native Bun process management via ProcessManager
- **Port**: Fixed port 37777 (configurable via `CLAUDE_MEM_WORKER_PORT`)
- **Port**: Per-user default `37700 + (uid % 100)` (override with `CLAUDE_MEM_WORKER_PORT`). The active port is stored in `~/.claude-mem/settings.json` and reported by `GET /api/health`.
- **Location**: `src/services/worker-service.ts`
- **Built Output**: `plugin/scripts/worker-service.cjs`
- **Model**: Configurable via `CLAUDE_MEM_MODEL` environment variable (default: sonnet)
- **Model**: Configurable via `CLAUDE_MEM_MODEL` (default: `claude-haiku-4-5-20251001`)
## REST API Endpoints
@@ -51,10 +51,12 @@ GET /health
{
"status": "ok",
"uptime": 12345,
"port": 37777
"port": 37742
}
```
The `port` value is the actual worker port for the current user — per-user default `37700 + (uid % 100)`, or whatever `CLAUDE_MEM_WORKER_PORT` is set to. The example above is illustrative; your value will differ.
#### 3. Server-Sent Events Stream
```
GET /stream
@@ -612,7 +614,7 @@ The worker service auto-starts when the SessionStart hook fires. Manual start is
### Bun Requirement
Bun is required to run the worker service. If Bun is not installed, the smart-install script will automatically install it on first run:
Bun is required to run the worker service. If Bun is not installed, `npx claude-mem install` (and `npx claude-mem repair`) installs it globally during setup, with a visible clack spinner:
- **Windows**: `powershell -c "irm bun.sh/install.ps1 | iex"`
- **macOS/Linux**: `curl -fsSL https://bun.sh/install | bash`
@@ -640,26 +642,26 @@ The worker service routes observations to the Claude Agent SDK for AI-powered pr
### Model Configuration
Set the AI model used for processing via environment variable:
Set the Claude model used for compression via environment variable or `~/.claude-mem/settings.json`:
```bash
export CLAUDE_MEM_MODEL=sonnet
export CLAUDE_MEM_MODEL=claude-haiku-4-5-20251001
```
Available shorthand models (forward to latest version):
- `haiku` - Fast, cost-efficient
- `sonnet` - Balanced (default)
- `opus` - Most capable
Allowed values:
- `claude-haiku-4-5-20251001` - default, fast and cheap (best for compression)
- `claude-sonnet-4-6` - balanced quality and cost
- `claude-opus-4-7` - highest quality, most expensive
## Port Allocation
The worker uses a fixed port (37777 by default) for consistent communication:
The worker uses a per-user default port so different OS users on the same machine never collide:
- **Default**: Port 37777
- **Override**: Set `CLAUDE_MEM_WORKER_PORT` environment variable
- **Port File**: `${CLAUDE_PLUGIN_ROOT}/data/worker.port` tracks current port
- **Default**: `37700 + (uid % 100)` (set in `src/shared/SettingsDefaultsManager.ts`)
- **Override**: Set `CLAUDE_MEM_WORKER_PORT` (env or `~/.claude-mem/settings.json`)
- **Discovery**: `GET /api/health` returns the active port; `~/.claude-mem/settings.json` stores the configured value
If port 37777 is in use, the worker will fail to start. Set a custom port via environment variable.
If the chosen port is occupied, the worker fails to start — pin a different port via `CLAUDE_MEM_WORKER_PORT` and restart.
## Data Storage
+2 -2
View File
@@ -13,11 +13,11 @@ Claude-Mem offers a beta channel for users who want to try experimental features
## Version Channel Switching
You can switch between stable and beta versions directly from the web viewer UI at http://localhost:37777.
You can switch between stable and beta versions directly from the web viewer UI (the worker prints its URL on startup; default `http://127.0.0.1:<worker-port>`).
### How to Access
1. Open the Claude-Mem viewer at http://localhost:37777
1. Open the Claude-Mem viewer (the worker prints its URL on startup)
2. Click the **Settings** gear icon in the top-right
3. Find the **Version Channel** section
4. Click **Try Beta (Endless Mode)** to switch to beta, or **Switch to Stable** to return
+55 -80
View File
@@ -13,12 +13,14 @@ Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created
| Setting | Default | Description |
|-------------------------------|---------------------------------|---------------------------------------|
| `CLAUDE_MEM_MODEL` | `sonnet` | AI model for processing observations (when using Claude) |
| `CLAUDE_MEM_MODEL` | `claude-haiku-4-5-20251001` | Claude model used to compress observations (when using the Claude provider) |
| `CLAUDE_MEM_PROVIDER` | `claude` | AI provider: `claude`, `gemini`, or `openrouter` |
| `CLAUDE_MEM_CLAUDE_AUTH_METHOD` | `subscription` | Claude provider auth mode: `subscription`, `api-key`, or `gateway` |
| `CLAUDE_MEM_MODE` | `code` | Active mode profile (e.g., `code--es`, `email-investigation`) |
| `CLAUDE_MEM_CONTEXT_OBSERVATIONS` | `50` | Number of observations to inject |
| `CLAUDE_MEM_WORKER_PORT` | `37777` | Worker service port |
| `CLAUDE_MEM_WORKER_PORT` | `37700 + (uid % 100)` | Worker service port (per-user default; override for fixed port) |
| `CLAUDE_MEM_WORKER_HOST` | `127.0.0.1` | Worker service host address |
| `CLAUDE_MEM_DATA_DIR` | `~/.claude-mem` | Data root — every other path (database, chroma, logs, settings.json, worker.pid) derives from this |
| `CLAUDE_MEM_SKIP_TOOLS` | `ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion` | Comma-separated tools to exclude from observations |
### Gemini Provider Settings
@@ -43,6 +45,18 @@ See [Gemini Provider](usage/gemini-provider) for detailed configuration and free
See [OpenRouter Provider](usage/openrouter-provider) for detailed configuration, free model list, and usage guide.
### Claude Gateway Settings
Gateway credentials live in `~/.claude-mem/.env`, not `settings.json`.
| Env var | Default | Description |
|---------|---------|-------------|
| `ANTHROPIC_BASE_URL` | none | LiteLLM or Anthropic-compatible gateway URL for the Claude Agent SDK path |
| `ANTHROPIC_AUTH_TOKEN` | none | Optional LiteLLM master key or virtual key |
| `ANTHROPIC_API_KEY` | none | Direct Anthropic API key; normally omit this in LiteLLM gateway mode |
Use [LiteLLM Gateway](configuration/litellm-gateway) when you want `CLAUDE_MEM_PROVIDER=claude` to route through LiteLLM while preserving the Claude Agent SDK worker path.
### System Configuration
| Setting | Default | Description |
@@ -54,23 +68,19 @@ See [OpenRouter Provider](usage/openrouter-provider) for detailed configuration,
## Model Configuration
Configure which AI model processes your observations.
Configure which Claude model compresses your observations (only applies when `CLAUDE_MEM_PROVIDER=claude`).
### Available Models
Shorthand model names automatically forward to the latest version:
| Value | Notes |
|-------|-------|
| `claude-haiku-4-5-20251001` | Default — fast and cheap, ideal for compression |
| `claude-sonnet-4-6` | Balanced quality and cost |
| `claude-opus-4-7` | Highest quality, most expensive |
- `haiku` - Fast, cost-efficient
- `sonnet` - Balanced (default)
- `opus` - Most capable
### Picking via the Installer
### Using the Interactive Script
```bash
./claude-mem-settings.sh
```
This script manages settings in `~/.claude-mem/settings.json`.
`npx claude-mem install` prompts for the Claude model (when the Claude provider is selected) and persists the choice to `~/.claude-mem/settings.json`.
### Manual Configuration
@@ -78,7 +88,7 @@ Edit `~/.claude-mem/settings.json`:
```json
{
"CLAUDE_MEM_MODEL": "sonnet"
"CLAUDE_MEM_MODEL": "claude-haiku-4-5-20251001"
}
```
@@ -119,8 +129,8 @@ The data directory location depends on the environment:
```
~/.claude-mem/
├── claude-mem.db # SQLite database
├── .install-version # Cached version for smart installer
├── worker.port # Current worker port file
├── .install-version # Version marker written by `npx claude-mem install`/`repair`
├── settings.json # Worker port + provider/model settings
└── logs/
├── worker-out.log # Worker stdout logs
└── worker-error.log # Worker stderr logs
@@ -136,7 +146,7 @@ ${CLAUDE_PLUGIN_ROOT}/
├── hooks/
│ └── hooks.json # Hook configuration
├── scripts/ # Built executables
│ ├── smart-install.js # Smart installer script
│ ├── version-check.js # Sub-100ms Setup-hook version marker check
│ ├── context-hook.js # Context injection hook
│ ├── new-hook.js # Session creation hook
│ ├── save-hook.js # Observation capture hook
@@ -151,44 +161,16 @@ ${CLAUDE_PLUGIN_ROOT}/
### Hooks Configuration
Hooks are configured in `plugin/hooks/hooks.json`:
Hooks are registered in `plugin/hooks/hooks.json`. The current shape uses a single dispatcher (`worker-service.cjs hook claude-code <event>`) launched through `bun-runner.js`, plus a fast Setup-phase `version-check.js`. The events wired up are:
```json
{
"description": "Claude-mem memory system hooks",
"hooks": {
"SessionStart": [{
"hooks": [{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
"timeout": 120
}]
}],
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js",
"timeout": 120
}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js",
"timeout": 120
}]
}],
"Stop": [{
"hooks": [{
"type": "command",
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js",
"timeout": 120
}]
}]
}
}
```
- `Setup` → `version-check.js` (sub-100ms `.install-version` check)
- `SessionStart` → start the worker, then `hook claude-code context` (context injection)
- `UserPromptSubmit` → `hook claude-code session-init`
- `PreToolUse` (matcher `Read`) → `hook claude-code file-context`
- `PostToolUse` (matcher `*`) → `hook claude-code observation`
- `Stop` → `hook claude-code summarize`
The exact `hooks.json` entries are written by the installer; do not hand-edit them in the marketplace copy unless you know what you're doing.
### Search Configuration
@@ -198,7 +180,7 @@ Claude-Mem provides MCP search tools for querying your project history.
Search operations are provided via:
- **MCP Server**: 3 tools (search, timeline, get_observations) with progressive disclosure
- **HTTP API**: 10 endpoints on worker service port 37777
- **HTTP API**: 10 endpoints on the worker service port (per-user, default `37700 + (uid % 100)`; see `~/.claude-mem/settings.json`)
- **Auto-Invocation**: Claude recognizes natural language queries about past work
## Version Channel
@@ -207,7 +189,7 @@ Claude-Mem supports switching between stable and beta versions via the web viewe
### Accessing Version Channel
1. Open the viewer at http://localhost:37777
1. Open the viewer at the worker URL (default `http://127.0.0.1:<worker-port>`; the active port is the value of `CLAUDE_MEM_WORKER_PORT` in `~/.claude-mem/settings.json`)
2. Click the Settings gear icon
3. Find the **Version Channel** section
@@ -243,7 +225,7 @@ Claude-Mem injects past observations into each new session, giving Claude awaren
### Context Settings Modal
Access the settings modal from the web viewer at http://localhost:37777:
Access the settings modal from the web viewer (the worker prints its URL on startup; default is `http://127.0.0.1:<worker-port>`):
1. Click the **gear icon** in the header
2. Adjust settings in the right panel
@@ -315,7 +297,7 @@ Token economics help you understand the value of cached observations vs. re-read
| Setting | Default | Description |
|---------|---------|-------------|
| **Model** | sonnet | AI model for generating observations |
| **Worker Port** | 37777 | Port for background worker service |
| **Worker Port** | `37700 + (uid % 100)` | Port for background worker service (override with `CLAUDE_MEM_WORKER_PORT`) |
| **MCP search server** | true | Enable Model Context Protocol search tools |
| **Include last summary** | false | Add previous session's summary to context |
| **Include last message** | false | Add previous session's final message |
@@ -340,7 +322,7 @@ Settings are stored in `~/.claude-mem/settings.json`:
}
```
**Note**: The Context Settings Modal (at http://localhost:37777) is the recommended way to configure these settings, as it provides live preview of changes.
**Note**: The Context Settings Modal (in the web viewer) is the recommended way to configure these settings, as it provides live preview of changes.
## Customization
@@ -411,22 +393,16 @@ Changes take effect on the next tool execution (no worker restart needed).
### Hook Timeouts
Modify timeouts in `plugin/hooks/hooks.json`:
Hook timeouts are written into `plugin/hooks/hooks.json` by the installer. The current defaults match the shape of the workload at each lifecycle stage:
```json
{
"timeout": 120 // Default: 120 seconds
}
```
Recommended values:
- SessionStart: 120s (needs time for smart install check and context retrieval)
- Setup (`version-check.js`): 300s ceiling but normally < 100ms — only reads `.install-version`
- SessionStart (worker-start + context): 60s
- UserPromptSubmit: 60s
- PostToolUse: 120s (can process many observations)
- Stop: 60s
- SessionEnd: 60s
- PreToolUse (file-context, Read matcher): 60s
- PostToolUse (observation): 120s
- Stop (summary): 120s
**Note**: With smart install caching (v5.0.3+), SessionStart is typically very fast (10ms) unless dependencies need installation.
The Setup hook never installs anything — runtime install (Bun, uv, `bun install`) happens in `npx claude-mem install` / `npx claude-mem repair` outside the session lifecycle.
### Worker Memory Limit
@@ -472,16 +448,15 @@ npm run worker:logs
### Invalid Model Name
If you specify an invalid model name, the worker will fall back to `sonnet` and log a warning.
If you specify an invalid Claude model name, the worker logs a warning and uses the default. Valid Claude models for `CLAUDE_MEM_MODEL`:
Valid shorthand models (forward to latest version):
- haiku
- sonnet
- opus
- `claude-haiku-4-5-20251001` (default)
- `claude-sonnet-4-6`
- `claude-opus-4-7`
### Port Already in Use
If port 37777 is already in use:
The default worker port is `37700 + (uid % 100)`, so different OS users on the same machine get different ports automatically. If you still hit a collision (e.g. running multiple profiles as the same UID), set a fixed port:
1. Set custom port:
```bash
@@ -495,7 +470,7 @@ If port 37777 is already in use:
3. Verify new port:
```bash
cat ~/.claude-mem/worker.port
curl -s http://127.0.0.1:$CLAUDE_MEM_WORKER_PORT/api/health | jq .port
```
## Next Steps
@@ -0,0 +1,113 @@
---
title: "Custom Anthropic-Compatible Backends"
description: "Point claude-mem at bridged or self-hosted Anthropic-compatible API endpoints with ANTHROPIC_BASE_URL"
---
# Custom Anthropic-Compatible Backends
When you use the `claude` provider, claude-mem talks to the Anthropic API through the Claude Agent SDK. By default, the SDK targets the official Anthropic endpoint, but it honors the standard `ANTHROPIC_BASE_URL` environment variable. That means you can route claude-mem at any Anthropic-protocol-compatible backend — for example a corporate gateway, a regional bridge, or a third-party provider that exposes an Anthropic-shaped API — without changing any claude-mem source code.
<Note>
This page documents how to **persist a custom base URL** so claude-mem's worker uses it consistently. For OpenAI-compatible upstream providers, use a gateway such as LiteLLM and follow the [LiteLLM Gateway](litellm-gateway) guide.
</Note>
## When to Use This
Use `ANTHROPIC_BASE_URL` if you need claude-mem's observation worker to talk to:
- A **corporate Anthropic gateway** (proxy in front of `api.anthropic.com`)
- A **regional Anthropic deployment** (e.g. AWS Bedrock or GCP Vertex via an Anthropic-compatible shim)
- A **third-party provider** that bridges its API to the Anthropic protocol
If your provider only speaks the OpenAI chat-completions protocol, put a gateway such as LiteLLM in front of it and point claude-mem's Claude Agent SDK path at that gateway. See [LiteLLM Gateway](litellm-gateway) for the full routing model.
## How the Plumbing Works
The flow is intentionally simple:
1. **You write the credential** to `~/.claude-mem/.env`.
2. **`EnvManager.loadClaudeMemEnv()`** reads that file (`src/shared/EnvManager.ts:67`).
3. **`buildIsolatedEnv()`** copies `ANTHROPIC_BASE_URL` into the worker's spawn environment alongside explicit gateway or API credentials (`src/shared/EnvManager.ts:164`).
4. **`ClaudeProvider.startSession()`** spawns the Claude Agent SDK with that isolated env (`src/services/worker/ClaudeProvider.ts:115`). The SDK reads `ANTHROPIC_BASE_URL` natively — claude-mem does not parse or rewrite it.
Because the variable is isolated to the worker process, your interactive Claude Code sessions are unaffected; only the background memory agent uses the override.
## Configuration
### Step 1: Edit `~/.claude-mem/.env`
The credentials file is a plain `KEY=VALUE` env file at `~/.claude-mem/.env` (mode `0600`). Add or update the `ANTHROPIC_BASE_URL` line:
```bash
# ~/.claude-mem/.env
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_BASE_URL=https://your-gateway.example.com/v1
```
If the file does not yet exist, create it. The directory permissions are enforced to `0700` and the file to `0600` automatically on the next worker write.
### Step 2: Pick a Compatible Model
`CLAUDE_MEM_MODEL` (in `~/.claude-mem/settings.json`) is passed straight through to the SDK. The model name **must be one your bridge accepts** — claude-mem does not translate names.
```json
{
"CLAUDE_MEM_MODEL": "claude-haiku-4-5-20251001"
}
```
If your bridge expects a non-Anthropic model name (for example, a Bedrock inference profile), set that string here instead.
### Step 3: Restart the Worker
Credentials are loaded when the worker spawns the SDK, so a restart is required after you edit `.env`:
```bash
npm run worker:restart
```
## Worked Example: Corporate Gateway
Suppose your team runs `https://anthropic-proxy.internal.example.com` in front of `api.anthropic.com` for audit and rate-limit purposes. The proxy accepts the same protocol and the same model names.
`~/.claude-mem/.env`:
```bash
ANTHROPIC_API_KEY=sk-corp-...
ANTHROPIC_BASE_URL=https://anthropic-proxy.internal.example.com
```
`~/.claude-mem/settings.json`:
```json
{
"CLAUDE_MEM_PROVIDER": "claude",
"CLAUDE_MEM_MODEL": "claude-haiku-4-5-20251001"
}
```
Restart, and the next observation will be routed through your gateway.
## Verifying
After restarting, watch the worker logs for the next observation flush:
```bash
npm run worker:logs
```
A successful request through your gateway shows the standard `SDK Starting SDK query` line followed by `Response received`. If the gateway rejects the request, the SDK error surfaces verbatim in `worker-error.log` — there is no silent fallback to the public Anthropic endpoint.
## Limitations and Gotchas
- **No model-name translation.** If your bridge expects `glm-4.7` and `CLAUDE_MEM_MODEL` is `claude-haiku-4-5-20251001`, the request will fail. Pin `CLAUDE_MEM_MODEL` to a name your bridge recognizes.
- **Gateway auth usually uses `ANTHROPIC_AUTH_TOKEN`.** For LiteLLM gateway mode, store the gateway key or virtual key as `ANTHROPIC_AUTH_TOKEN`. Use `ANTHROPIC_API_KEY` for direct Anthropic API-key mode or gateways that explicitly expect it.
- **`ANTHROPIC_BASE_URL` from your shell is not inherited.** `ANTHROPIC_API_KEY` is in the BLOCKED_ENV_VARS list (`src/shared/EnvManager.ts:10`) to prevent accidental billing on a shell-leaked key — `ANTHROPIC_BASE_URL` is not blocked, but it must still be set in `~/.claude-mem/.env` for the worker to pick it up reliably across restarts. Do not rely on shell exports.
- **No auto-detection.** If you have already configured `ANTHROPIC_BASE_URL`, `ANTHROPIC_DEFAULT_HAIKU_MODEL`, etc. for Claude Code itself, claude-mem will **not** read those today. Mirror the relevant values into `~/.claude-mem/.env` and `~/.claude-mem/settings.json`.
## Related
- [Configuration](../configuration) — All claude-mem settings
- [LiteLLM Gateway](litellm-gateway): Route the Claude Agent SDK path through LiteLLM
- [OpenRouter Provider](../usage/openrouter-provider) — OpenAI-compatible bridge for non-Anthropic protocols
- [Gemini Provider](../usage/gemini-provider) — Native Gemini API alternative
@@ -0,0 +1,307 @@
---
title: "LiteLLM Gateway"
description: "Route claude-mem's Claude Agent SDK worker through LiteLLM while keeping one agentic execution path"
---
# LiteLLM Gateway
claude-mem can route its background memory agent through a LiteLLM proxy. This lets teams keep claude-mem's Claude Agent SDK workflow while using LiteLLM for model routing, centralized credentials, usage tracking, budgets, audit logs, and provider failover.
The important detail: claude-mem does **not** call LiteLLM with the OpenAI client directly. claude-mem still uses the Claude Agent SDK, and the SDK sends Anthropic-format requests to LiteLLM. LiteLLM then translates those requests to the upstream model provider you configured.
```text
Claude Code session
-> claude-mem hooks
-> claude-mem worker
-> Claude Agent SDK subprocess
-> ANTHROPIC_BASE_URL=http://localhost:4000
-> LiteLLM proxy
-> OpenAI / Azure / Vertex / Bedrock / OpenRouter / local model
```
This keeps the memory agent on one implementation path. The Claude provider, knowledge agents, session resume behavior, XML observation prompts, and queue retry logic all continue to use the same SDK code path whether the upstream model is Anthropic or routed through LiteLLM.
## When to Use This
Use LiteLLM gateway mode when you want:
- A single organization-level LLM gateway for claude-mem traffic
- Provider routing without changing claude-mem source code
- Centralized API keys instead of storing provider keys in each developer's claude-mem settings
- LiteLLM budgets, rate limits, logging, fallback routing, or virtual keys
- A non-Anthropic upstream model while preserving the Claude Agent SDK execution path used by claude-mem
Use the native [OpenRouter Provider](../usage/openrouter-provider) or [Gemini Provider](../usage/gemini-provider) instead if you want claude-mem's REST providers directly and do not need the Claude Agent SDK path.
## Architecture
### One Agent Path
The LiteLLM integration is intentionally small. There is no custom LiteLLM provider, no Python handler, and no OpenAI-compatible server embedded in claude-mem.
At runtime:
1. The installer or user writes gateway settings to `~/.claude-mem/.env`.
2. `~/.claude-mem/settings.json` keeps `CLAUDE_MEM_PROVIDER` set to `claude`.
3. The worker starts the Claude Agent SDK with an isolated environment.
4. The SDK reads `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN`.
5. LiteLLM receives the SDK's Anthropic-format request.
6. LiteLLM maps the request to the upstream provider and model configured in LiteLLM.
7. The SDK response is parsed by the normal claude-mem observation pipeline.
The code paths involved are:
| Layer | Responsibility |
| --- | --- |
| `src/npx-cli/commands/install.ts` | Prompts for "LiteLLM or custom gateway", stores the gateway URL/token, and allows custom gateway model names |
| `src/shared/EnvManager.ts` | Stores credentials in `~/.claude-mem/.env`, blocks shell-leaked auth vars, and injects only explicit claude-mem credentials |
| `src/services/worker/ClaudeProvider.ts` | Starts the Claude Agent SDK for observation extraction with the isolated environment |
| `src/services/worker/knowledge/KnowledgeAgent.ts` | Uses the same isolated SDK path for knowledge corpus Q&A |
### Why `CLAUDE_MEM_PROVIDER` Stays `claude`
LiteLLM is a gateway for the Claude Agent SDK path, not a fourth claude-mem provider.
```json
{
"CLAUDE_MEM_PROVIDER": "claude",
"CLAUDE_MEM_CLAUDE_AUTH_METHOD": "gateway",
"CLAUDE_MEM_MODEL": "claude-haiku-4-5-20251001"
}
```
Keeping the provider as `claude` matters because the worker should continue to use `ClaudeProvider`, not the native Gemini or OpenRouter REST providers. The gateway URL changes where the SDK sends model traffic; it does not change how claude-mem manages memory sessions.
## Configure LiteLLM
LiteLLM must expose an Anthropic-compatible endpoint for Claude Code / Claude Agent SDK traffic. Anthropic's gateway guidance recommends the unified LiteLLM endpoint as the normal setup:
```bash
export ANTHROPIC_BASE_URL=http://localhost:4000
```
For claude-mem, that value goes in `~/.claude-mem/.env`, not your shell, so the background worker uses it consistently across restarts.
### Minimal LiteLLM Example
Create a LiteLLM config that defines the model name claude-mem will request:
```yaml
# litellm-config.yaml
model_list:
- model_name: claude-haiku-4-5-20251001
litellm_params:
model: openai/gpt-4o-mini
api_key: os.environ/OPENAI_API_KEY
litellm_settings:
master_key: sk-litellm-local
```
Start LiteLLM:
```bash
OPENAI_API_KEY=sk-your-openai-key \
litellm --config litellm-config.yaml --host 127.0.0.1 --port 4000
```
In this example, claude-mem asks the SDK for `claude-haiku-4-5-20251001`, LiteLLM accepts that model alias, and LiteLLM forwards the request to `openai/gpt-4o-mini`.
<Note>
The alias in `model_name` must match `CLAUDE_MEM_MODEL`, or `CLAUDE_MEM_MODEL` must be changed to match your LiteLLM alias. claude-mem does not translate model names.
</Note>
## Configure claude-mem
### Option 1: Installer
Run the installer:
```bash
npx claude-mem install
```
Choose:
1. `Claude Agent SDK`
2. `API key or gateway`
3. `LiteLLM or custom gateway`
4. Your LiteLLM URL, for example `http://127.0.0.1:4000`
5. Your LiteLLM key/token if the proxy requires one
6. The model alias LiteLLM should receive
The installer stores provider settings in `~/.claude-mem/settings.json` and gateway credentials in `~/.claude-mem/.env`.
### Option 2: Manual Files
Edit `~/.claude-mem/settings.json`:
```json
{
"CLAUDE_MEM_PROVIDER": "claude",
"CLAUDE_MEM_CLAUDE_AUTH_METHOD": "gateway",
"CLAUDE_MEM_MODEL": "claude-haiku-4-5-20251001"
}
```
Edit `~/.claude-mem/.env`:
```bash
# ~/.claude-mem/.env
ANTHROPIC_BASE_URL=http://127.0.0.1:4000
ANTHROPIC_AUTH_TOKEN=sk-litellm-local
```
If your LiteLLM proxy does not require authentication, omit `ANTHROPIC_AUTH_TOKEN`.
Restart the worker after manual edits:
```bash
npm run worker:restart
```
## Environment Isolation
claude-mem deliberately does not trust whatever Anthropic credentials happen to be exported in your shell or project `.env` file.
The worker blocks inherited `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and stale `CLAUDE_CODE_OAUTH_TOKEN` values. It then re-injects only the credentials stored in `~/.claude-mem/.env`.
This avoids two common failure modes:
- A project-level `ANTHROPIC_API_KEY` silently bypasses LiteLLM and bills the public Anthropic API.
- An expired Claude Code OAuth token overrides a configured gateway token and causes confusing auth failures.
If `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, or `ANTHROPIC_API_KEY` is present in `~/.claude-mem/.env`, the worker treats that as explicit gateway/API configuration and skips Claude OAuth lookup. This prevents a configured gateway from falling back to `api.anthropic.com`.
## Model Names
`CLAUDE_MEM_MODEL` is passed through to the Claude Agent SDK. In gateway mode, claude-mem allows any non-empty model string because the valid model list is owned by LiteLLM.
Recommended pattern:
```yaml
model_list:
- model_name: claude-haiku-4-5-20251001
litellm_params:
model: openai/gpt-4o-mini
api_key: os.environ/OPENAI_API_KEY
```
Then keep:
```json
{
"CLAUDE_MEM_MODEL": "claude-haiku-4-5-20251001"
}
```
Alternatively, use a descriptive custom alias:
```yaml
model_list:
- model_name: memory-compressor
litellm_params:
model: azure/gpt-4o-mini-memory
api_base: os.environ/AZURE_API_BASE
api_key: os.environ/AZURE_API_KEY
api_version: "2024-10-21"
```
```json
{
"CLAUDE_MEM_MODEL": "memory-compressor"
}
```
## Request Flow
When a Claude Code session produces transcript events, claude-mem's worker queues them for observation extraction. In gateway mode the extraction flow is:
1. The worker loads pending messages for a memory session.
2. `ClaudeProvider` builds the observation prompt and selected model.
3. `buildIsolatedEnvWithFreshOAuth()` loads `~/.claude-mem/.env`.
4. The SDK subprocess starts with `ANTHROPIC_BASE_URL` pointing at LiteLLM.
5. LiteLLM receives the Anthropic-format request.
6. LiteLLM routes to the configured upstream model.
7. The SDK streams the assistant response back to the worker.
8. claude-mem parses observations, stores them in SQLite, and syncs searchable embeddings.
The knowledge-agent APIs use the same gateway environment, so corpus priming and corpus Q&A route through LiteLLM too.
## What LiteLLM Does and Does Not Replace
LiteLLM replaces:
- Upstream model selection
- Provider credentials
- Gateway-level budgets and rate limits
- Gateway-level logging and auditing
- Optional routing/fallback policies inside LiteLLM
LiteLLM does not replace:
- claude-mem's worker process
- The Claude Agent SDK subprocess
- claude-mem's observation XML format
- SQLite storage
- Chroma/vector sync
- Hook installation
- Session resume handling inside claude-mem
## Verification
Check claude-mem's worker logs:
```bash
npm run worker:logs
```
You should see SDK startup logs that report gateway auth, followed by normal observation processing.
Check LiteLLM's logs for a corresponding request to the configured model alias. If LiteLLM never receives traffic, confirm:
- `CLAUDE_MEM_PROVIDER` is `claude`
- `CLAUDE_MEM_CLAUDE_AUTH_METHOD` is `gateway`
- `ANTHROPIC_BASE_URL` is in `~/.claude-mem/.env`
- The worker was restarted after manual edits
- The LiteLLM URL does not include an extra `/v1` suffix for the unified Anthropic endpoint
## Troubleshooting
### LiteLLM returns "model not found"
The model name sent by claude-mem does not match a LiteLLM `model_name`. Make `CLAUDE_MEM_MODEL` and the LiteLLM alias match exactly.
### claude-mem still uses Anthropic directly
Check `~/.claude-mem/.env`. Gateway settings must be stored there. Shell exports are not the reliable configuration source for the worker.
Also make sure `ANTHROPIC_BASE_URL` is present. A token alone authenticates a gateway, but the base URL is what redirects traffic away from the default Anthropic endpoint.
### Authentication fails
If LiteLLM uses a master key or virtual key, store it as `ANTHROPIC_AUTH_TOKEN` in `~/.claude-mem/.env`. The Claude Agent SDK sends this value as gateway authorization.
If you previously configured a direct Anthropic API key, remove `ANTHROPIC_API_KEY` from `~/.claude-mem/.env` for gateway mode unless your gateway explicitly expects that variable.
### Requests fail after changing files
Restart the worker:
```bash
npm run worker:restart
```
The SDK environment is built when SDK subprocesses are spawned. Restarting guarantees the next memory agent process sees the new gateway values.
### Tool use behaves differently than full Claude Code
claude-mem's memory worker disables file and shell tools for observation extraction. The LiteLLM gateway is only handling the model call used to compress and summarize memory; it is not a replacement for your interactive Claude Code tool loop.
## Related
- [Custom Anthropic-Compatible Backends](custom-anthropic-backends)
- [Configuration](../configuration)
- [Worker Service Architecture](../architecture/worker-service)
- [Anthropic LLM gateway configuration](https://docs.anthropic.com/en/docs/claude-code/llm-gateway)
- [LiteLLM documentation](https://docs.litellm.ai/)
+2 -2
View File
@@ -39,7 +39,7 @@ The build process uses esbuild to compile TypeScript:
**Build Output**:
- Hook executables: `*-hook.js` (ESM format)
- Smart installer: `smart-install.js` (ESM format)
- Setup version-check: `version-check.js` (ESM format, sub-100ms)
- Worker service: `worker-service.cjs` (CJS format)
- MCP server: `mcp-server.cjs` (CJS format)
- Viewer UI: `viewer.html` (self-contained HTML bundle)
@@ -320,7 +320,7 @@ npm test
export function buildObservationPrompt(observation: Observation): string {
return `
<observation>
<!-- Add new XML structure -->
</observation>
`;
}
+3 -1
View File
@@ -80,6 +80,8 @@
"icon": "gear",
"pages": [
"configuration",
"configuration/litellm-gateway",
"configuration/custom-anthropic-backends",
"modes",
"development",
"troubleshooting",
@@ -125,7 +127,7 @@
"header": "Legal",
"items": [
{
"label": "License (AGPL-3.0)",
"label": "License (Apache-2.0)",
"href": "https://github.com/thedotmack/claude-mem/blob/main/LICENSE"
}
]
+26 -37
View File
@@ -54,7 +54,7 @@ Claude Code's hook system provides exactly what we need:
<CardGroup cols={2}>
<Card title="Lifecycle Events" icon="clock">
SessionStart, UserPromptSubmit, PostToolUse, Stop
SessionStart, UserPromptSubmit, PreToolUse (Read), PostToolUse, Stop, SessionEnd
</Card>
<Card title="Non-Blocking" icon="forward">
@@ -74,34 +74,31 @@ Claude Code's hook system provides exactly what we need:
## The Hook Scripts
Claude-Mem uses lifecycle hook scripts across 5 lifecycle events. SessionStart runs 3 hooks in sequence: smart-install, worker-service start, and context-hook.
Claude-Mem uses lifecycle hook scripts across 5 lifecycle events. Runtime setup is handled out-of-band by `npx claude-mem install` (and `npx claude-mem repair`); the Setup hook only runs a sub-100ms `version-check.js` to flag stale installs. SessionStart runs 2 hooks in sequence: worker-service start, then context-hook.
### Pre-Hook: Smart Install (Before SessionStart)
### Setup Hook: Version Check
**Purpose:** Intelligently manage dependencies and start worker service
**Purpose:** Detect stale installs caused by external plugin upgrades and prompt the user to repair.
**Note:** This is NOT a lifecycle hook - it's a pre-hook script executed via command chaining before context-hook runs.
**Note:** Runtime installation (Bun, uv, `bun install` in the plugin cache) is performed by `npx claude-mem install` and `npx claude-mem repair` — the Setup hook itself never installs anything.
**When:** Claude Code starts (startup, clear, or compact)
**When:** Claude Code Setup phase, before every session.
**What it does:**
1. Checks if dependencies need installation (version marker)
2. Only runs `npm install` when necessary:
- First-time installation
- Version changed in package.json
3. Provides Windows-specific error messages
4. Starts Bun worker service
1. Reads the `.install-version` marker written by the npx installer.
2. Compares it against the currently loaded plugin version.
3. On mismatch, writes `run: npx claude-mem repair` to stderr.
4. Always exits 0 (non-blocking, sub-100ms).
**Configuration:**
```json
{
"hooks": {
"SessionStart": [{
"matcher": "startup|clear|compact",
"Setup": [{
"hooks": [{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/../scripts/smart-install.js\" && node ${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js",
"timeout": 300
"command": "node ${CLAUDE_PLUGIN_ROOT}/scripts/version-check.js",
"timeout": 60
}]
}]
}
@@ -109,14 +106,11 @@ Claude-Mem uses lifecycle hook scripts across 5 lifecycle events. SessionStart r
```
**Key Features:**
- ✅ Version caching (`.install-version` file)
- ✅ Fast when already installed (~10ms vs 2-5 seconds)
- ✅ Cross-platform compatible
- ✅ Helpful Windows error messages for build tools
- ✅ Sub-100ms version-marker check (no I/O beyond reading the marker)
- ✅ Always exit 0 — never blocks a session
- ✅ Clear repair instructions on stderr when the plugin was upgraded externally (e.g. `claude plugin update`)
**v5.0.3 Enhancement:** Smart caching eliminates redundant installs
**Source:** `scripts/smart-install.js`
**Source:** `scripts/version-check.js`. The matching installer logic lives in `npx claude-mem install` / `npx claude-mem repair`, which install Bun + uv globally, run `bun install` in the plugin cache, and write the `.install-version` marker — all behind a visible clack spinner.
---
@@ -124,7 +118,7 @@ Claude-Mem uses lifecycle hook scripts across 5 lifecycle events. SessionStart r
**Purpose:** Inject relevant context from previous sessions
**When:** Claude Code starts (runs after smart-install pre-hook)
**When:** Claude Code starts (runs after the worker-start SessionStart entry)
**What it does:**
1. Extracts project name from current working directory
@@ -429,7 +423,7 @@ sequenceDiagram
| Event | Timing | Blocking | Timeout | Output Handling |
|-------|--------|----------|---------|-----------------|
| **SessionStart (smart-install)** | Before session | No | 300s | stderr (log only) |
| **Setup (version-check)** | Before session | No | 60s | stderr hint on stale install (always exit 0) |
| **SessionStart (worker-start)** | Before session | No | 60s | stderr (log only) |
| **SessionStart (context)** | Before session | No | 60s | JSON → additionalContext (silent) |
| **UserPromptSubmit** | Before processing | No | 60s | stdout → context |
@@ -501,7 +495,7 @@ npm run worker:stop
### Worker HTTP API
**Technology:** Express.js REST API on port 37777
**Technology:** Express.js REST API on the worker's per-user port (default `37700 + (uid % 100)`, override via `CLAUDE_MEM_WORKER_PORT`)
**Endpoints:**
@@ -692,23 +686,18 @@ claude --debug
| Hook | Average | p95 | p99 |
|------|---------|-----|-----|
| SessionStart (smart-install, cached) | 10ms | 20ms | 40ms |
| SessionStart (smart-install, first run) | 2500ms | 5000ms | 8000ms |
| Setup (version-check, marker matches) | 8ms | 20ms | 40ms |
| Setup (version-check, marker mismatch — stderr hint, still non-blocking) | 10ms | 25ms | 50ms |
| SessionStart (context) | 45ms | 120ms | 250ms |
| SessionStart (user-message) | 5ms | 10ms | 15ms |
| UserPromptSubmit | 12ms | 25ms | 50ms |
| PostToolUse | 8ms | 15ms | 30ms |
| SessionEnd | 5ms | 10ms | 20ms |
**Why smart-install is sometimes slow:**
- First-time: Full npm install (2-5 seconds)
- Cached: Version check only (~10ms)
- Version change: Full npm install + worker restart
**Optimization (v5.0.3):**
- Version caching with `.install-version` marker
- Only install on version change or missing deps
- Windows-specific error messages with build tool help
**Why the Setup hook stays fast:**
- The Setup hook only reads the `.install-version` marker — no `npm install`, no spawned subprocesses.
- All heavy lifting (Bun + uv install, `bun install` inside the plugin cache) happens in `npx claude-mem install` / `npx claude-mem repair`, which run with a visible clack spinner outside the session lifecycle.
- On marker mismatch the hook prints a one-line `run: npx claude-mem repair` hint to stderr and exits 0; the user opts into the slow path explicitly.
### Database Performance
+13 -16
View File
@@ -16,10 +16,12 @@ npx claude-mem install
```
The interactive installer will:
- Detect your installed IDEs (Claude Code, Cursor, Gemini CLI, Windsurf, etc.)
- Copy plugin files to the correct locations
- Register the plugin with Claude Code
- Install all dependencies (including Bun and uv)
- Run a runtime check (auto-installs Bun and uv if missing)
- Detect your installed IDEs (Claude Code, Cursor, Gemini CLI, Windsurf, OpenCode, Codex CLI) and let you multi-select which ones to wire up
- Offer to install Claude Code if it isn't found
- Prompt for an LLM provider (Claude Code auth, Gemini API key, or OpenRouter API key)
- Prompt for the Claude model used to compress observations (Haiku / Sonnet / Opus) when the Claude provider is selected
- Copy plugin files into the marketplace directory and register the plugin
- Auto-start the worker service
### Option 2: Plugin Marketplace
@@ -39,10 +41,11 @@ Both methods will automatically configure hooks and start the worker service. St
## System Requirements
- **Node.js**: 18.0.0 or higher
- **Claude Code**: Latest version with plugin support
- **Bun**: JavaScript runtime and process manager (auto-installed if missing)
- **SQLite 3**: For persistent storage (bundled)
- **Node.js**: 20.0.0 or higher
- **Bun** ≥ 1.0 (auto-installed by `npx claude-mem install` if missing)
- **uv** (auto-installed if missing — provides Python for Chroma's embedding service)
- **Claude Code** or another supported IDE (Cursor, Gemini CLI, Windsurf, OpenCode, Codex CLI, OpenClaw)
- **SQLite 3**: bundled via `bun:sqlite`
## Advanced Installation
@@ -73,7 +76,7 @@ npm run worker:status
#### 1. Automatic Dependency Installation
Dependencies are installed automatically during plugin installation. The SessionStart hook also ensures dependencies are up-to-date on each session start (this is fast and idempotent). Works cross-platform on Windows, macOS, and Linux.
Dependencies are installed automatically by `npx claude-mem install` and `npx claude-mem repair`. Heavy lifting (Bun + uv install, `bun install` inside the plugin cache) happens behind a visible installer spinner. The Setup hook only performs a sub-100ms `version-check.js` read of the `.install-version` marker — on mismatch it prints `run: npx claude-mem repair` to stderr and exits 0, so it never blocks a session. Works cross-platform on Windows, macOS, and Linux.
#### 2. Verify Plugin Installation
@@ -110,13 +113,7 @@ npm run test:context
## Upgrading
Upgrades are automatic when updating via the plugin marketplace. Key changes in recent versions:
**v7.1.0**: PM2 replaced with native Bun process management. Migration is automatic on first hook trigger.
**v7.0.0+**: 11 configuration settings, dual-tag privacy system.
**v5.4.0+**: Skill-based search replaces MCP tools, saving ~2,250 tokens per session.
Upgrades are automatic when updating via the plugin marketplace. After an external upgrade (for example `claude plugin update`), the Setup hook detects a version-marker mismatch and asks you to run `npx claude-mem repair`, which installs any missing runtime dependencies and refreshes the marker.
See [CHANGELOG](https://github.com/thedotmack/claude-mem/blob/main/CHANGELOG.md) for complete version history.
+11 -29
View File
@@ -34,7 +34,7 @@ Restart Claude Code. Context from previous sessions will automatically appear in
- 🎭 **Mode System** - Switch between workflows (Code, Email Investigation, Chill)
- 🔍 **MCP Search Tools** - Query your project history with natural language
- 🧠 **Knowledge Agents** - Build queryable "brains" from your observation history
- 🌐 **Web Viewer UI** - Real-time memory stream visualization at http://localhost:37777
- 🌐 **Web Viewer UI** - Real-time memory stream visualization served by the local worker
- 🔒 **Privacy Control** - Use `<private>` tags to exclude sensitive content from storage
- ⚙️ **Context Configuration** - Fine-grained control over what context gets injected
- 🤖 **Automatic Operation** - No manual intervention required
@@ -66,40 +66,22 @@ Restart Claude Code. Context from previous sessions will automatically appear in
```
**Core Components:**
1. **4 Lifecycle Hooks** - SessionStart, UserPromptSubmit, PostToolUse, Stop
2. **Smart Install** - Cached dependency checker (pre-hook script)
3. **Worker Service** - HTTP API on port 37777 managed by Bun
4. **SQLite Database** - Stores sessions, observations, summaries with FTS5 search
5. **MCP Search Tools** - Query historical context with natural language
1. **5 Lifecycle Hooks** - SessionStart, UserPromptSubmit, PostToolUse, Summary (Stop), SessionEnd
2. **Worker Service** - Express HTTP API managed by Bun on a per-user port (default `37700 + (uid % 100)`)
3. **SQLite Database** - Stores sessions, observations, summaries with FTS5 search
4. **Chroma Vector Index** - Optional embedding-based semantic search
5. **MCP / Skill Search Tools** - Query historical context with natural language
6. **Web Viewer UI** - Real-time visualization with SSE and infinite scroll
See [Architecture Overview](architecture/overview) for details.
## System Requirements
- **Node.js**: 18.0.0 or higher
- **Claude Code**: Latest version with plugin support
- **Bun**: JavaScript runtime and process manager (auto-installed if missing)
- **SQLite 3**: For persistent storage (bundled)
## What's New
**v9.0.0 - Live Context:**
- **Folder Context Files**: Auto-generated `CLAUDE.md` in project folders with activity timelines
- **Worktree Support**: Unified context from parent repos and git worktrees
- **Configurable Observation Limits**: Control how many observations appear in context
- **Windows Fixes**: Resolved IPC detection and hook execution issues
- **Settings Auto-Creation**: `settings.json` now auto-creates on first run
- **MCP Tools Naming**: Updated from "mem-search skill" to "MCP tools" terminology
**v7.1.0 - Bun Migration:**
- Replaced PM2 with native Bun process management
- Switched from better-sqlite3 to bun:sqlite for faster database access
- Simplified cross-platform support
**v7.0.0 - Context Configuration:**
- 11 settings for fine-grained control over context injection
- Dual-tag privacy system (`<private>` tags)
- **Node.js**: 20.0.0 or higher
- **Bun** ≥ 1.0 (auto-installed by `npx claude-mem install` if missing)
- **uv** (auto-installed if missing — provides Python for Chroma)
- **Claude Code** (or another supported IDE: Cursor, Gemini CLI, Windsurf, OpenCode, Codex CLI, OpenClaw)
- **SQLite 3** — bundled via `bun:sqlite`
## Next Steps
+28 -22
View File
@@ -5,8 +5,7 @@ icon: plug
---
<Note>
**Version:** 7.0.0 (December 2025)
**Target Audience:** Developers building claude-mem integrations (VSCode extensions, IDE plugins, CLI tools)
**Target Audience:** Developers building claude-mem integrations (VSCode extensions, IDE plugins, CLI tools).
</Note>
## Quick Reference
@@ -14,8 +13,11 @@ icon: plug
### Worker Service Basics
```typescript
const WORKER_BASE_URL = 'http://localhost:37777';
const DEFAULT_PORT = 37777; // Override with CLAUDE_MEM_WORKER_PORT
// Resolve the worker port at runtime. The default is per-user (37700 + uid % 100),
// or whatever the user set via CLAUDE_MEM_WORKER_PORT / settings.json. Read it from
// process.env.CLAUDE_MEM_WORKER_PORT, then ~/.claude-mem/settings.json
// (CLAUDE_MEM_WORKER_PORT key), then fall back to the deterministic default.
const WORKER_BASE_URL = `http://127.0.0.1:${workerPort}`;
```
### Most Common Operations
@@ -46,9 +48,10 @@ GET /api/context/recent?project=my-project&limit=3
### Environment Variables
```bash
CLAUDE_MEM_MODEL=claude-sonnet-4-6 # Model for observations/summaries
CLAUDE_MEM_MODEL=claude-haiku-4-5-20251001 # Default Claude model for observations/summaries
CLAUDE_MEM_CONTEXT_OBSERVATIONS=50 # Observations injected at SessionStart
CLAUDE_MEM_WORKER_PORT=37777 # Worker service port
CLAUDE_MEM_WORKER_PORT= # Optional override; default = 37700 + (uid % 100)
CLAUDE_MEM_DATA_DIR= # Optional override for the data directory
CLAUDE_MEM_PYTHON_VERSION=3.13 # Python version for chroma-mcp
```
@@ -68,7 +71,7 @@ npm run worker:status # Check worker status
```plaintext
Platform Hook/Extension
→ HTTP Request to Worker (localhost:37777)
→ HTTP Request to Worker (`${WORKER_BASE_URL}` — per-user, default 37700+uid%100)
→ Route Handler (SessionRoutes/DataRoutes/SearchRoutes/etc.)
→ Domain Service (SessionManager/SearchManager/DatabaseManager)
→ Database (SQLite3 + Chroma vector DB)
@@ -311,7 +314,7 @@ GET /api/stats
"uptime": 12345,
"activeSessions": 2,
"sseClients": 1,
"port": 37777
"port": 37742
},
"database": {
"path": "~/.claude-mem/claude-mem.db",
@@ -796,7 +799,7 @@ interface TimelineItem {
async function onPostToolUse(context: HookContext) {
const { session_id, tool_name, tool_input, tool_result, cwd } = context;
const response = await fetch('http://localhost:37777/api/sessions/observations', {
const response = await fetch(`${WORKER_BASE_URL}/api/sessions/observations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -818,7 +821,7 @@ interface TimelineItem {
async function onSummary(context: HookContext) {
const { session_id, last_user_message, last_assistant_message } = context;
await fetch('http://localhost:37777/api/sessions/summarize', {
await fetch(`${WORKER_BASE_URL}/api/sessions/summarize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -835,7 +838,7 @@ interface TimelineItem {
async function onSessionEnd(context: HookContext) {
const { session_id } = context;
await fetch('http://localhost:37777/api/sessions/complete', {
await fetch(`${WORKER_BASE_URL}/api/sessions/complete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -867,7 +870,7 @@ const searchTool: SearchTool = {
try {
const response = await fetch(
`http://localhost:37777/api/search?query=${encodeURIComponent(query)}&format=index&limit=10`
`${WORKER_BASE_URL}/api/search?query=${encodeURIComponent(query)}&format=index&limit=10`
);
if (!response.ok) {
@@ -905,7 +908,7 @@ const participant = vscode.chat.createChatParticipant('claude-mem', async (reque
stream.markdown(`Searching memory for: ${request.prompt}\n\n`);
const response = await fetch(
`http://localhost:37777/api/search?query=${encodeURIComponent(request.prompt)}&format=index&limit=5`
`${WORKER_BASE_URL}/api/search?query=${encodeURIComponent(request.prompt)}&format=index&limit=5`
);
const results = await response.json();
@@ -931,7 +934,7 @@ async function callWorkerWithFallback<T>(
options?: RequestInit
): Promise<T | null> {
try {
const response = await fetch(`http://localhost:37777${endpoint}`, {
const response = await fetch(`${WORKER_BASE_URL}${endpoint}`, {
...options,
signal: AbortSignal.timeout(5000) // 5s timeout
});
@@ -975,7 +978,7 @@ async function retryWithBackoff<T>(
```typescript
async function isWorkerHealthy(): Promise<boolean> {
try {
const response = await fetch('http://localhost:37777/api/health', {
const response = await fetch(`${WORKER_BASE_URL}/api/health`, {
signal: AbortSignal.timeout(2000)
});
return response.ok;
@@ -1017,7 +1020,7 @@ class WorkerTimeoutError extends Error {
```typescript
function connectToSSE(onEvent: (event: any) => void) {
const eventSource = new EventSource('http://localhost:37777/stream');
const eventSource = new EventSource(`${WORKER_BASE_URL}/stream`);
eventSource.onmessage = (event) => {
try {
@@ -1138,8 +1141,8 @@ esbuild.build({
</Step>
<Step title="Terminal 3: Test API manually">
```bash
curl http://localhost:37777/api/health
curl "http://localhost:37777/api/search?query=test&limit=5"
curl http://127.0.0.1:$WORKER_PORT/api/health
curl "http://127.0.0.1:$WORKER_PORT/api/search?query=test&limit=5"
```
</Step>
<Step title="VSCode: Launch extension host">
@@ -1239,8 +1242,8 @@ describe('Worker Integration', () => {
<AccordionGroup>
<Accordion title="Phase 1: Connection & Health">
- [ ] Worker starts successfully (`npm run worker:status`)
- [ ] Health endpoint responds (`curl http://localhost:37777/api/health`)
- [ ] SSE stream connects (`curl http://localhost:37777/stream`)
- [ ] Health endpoint responds (`curl http://127.0.0.1:$WORKER_PORT/api/health`)
- [ ] SSE stream connects (`curl http://127.0.0.1:$WORKER_PORT/stream`)
</Accordion>
<Accordion title="Phase 2: Session Lifecycle">
@@ -1284,8 +1287,11 @@ describe('Worker Integration', () => {
export class WorkerClient {
private baseUrl: string;
constructor(port: number = 37777) {
this.baseUrl = `http://localhost:${port}`;
// Resolve the active worker port via env / settings.json, falling back
// to the deterministic per-user default. See parseWorkerPort() for an
// example helper; never hardcode a single value.
constructor(port: number = resolveWorkerPort()) {
this.baseUrl = `http://127.0.0.1:${port}`;
}
async isHealthy(): Promise<boolean> {
+12 -14
View File
@@ -21,24 +21,24 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
---
## v5.x Specific Issues
## Common Issues
### Viewer UI Not Loading
**Symptoms**: Cannot access http://localhost:37777, page doesn't load, or shows connection error.
**Symptoms**: Cannot reach the viewer URL, page doesn't load, or browser shows a connection error.
**Solutions**:
1. Check if worker is running on port 37777:
1. Find the worker port. The default is `37700 + (uid % 100)`. The configured port is the value of `CLAUDE_MEM_WORKER_PORT` in `~/.claude-mem/settings.json`; the running worker also reports it on `/api/health`:
```bash
lsof -i :37777
# or
PORT=$(jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json)
lsof -i :$PORT
npm run worker:status
```
2. Verify worker is healthy:
```bash
curl http://localhost:37777/health
curl http://127.0.0.1:$PORT/health
```
3. Check worker logs for errors:
@@ -51,9 +51,8 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
npm run worker:restart
```
5. Check for port conflicts:
5. Pin a fixed port if the auto-assigned one collides:
```bash
# If port 37777 is in use by another service
export CLAUDE_MEM_WORKER_PORT=38000
npm run worker:restart
```
@@ -170,7 +169,6 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
4. Restart Claude Code after manual install
## Worker Service Issues
### Worker Service Not Starting
@@ -230,7 +228,7 @@ The skill includes comprehensive diagnostics, automated repair sequences, and de
4. Verify new port:
```bash
cat ~/.claude-mem/worker.port
curl -s http://127.0.0.1:$CLAUDE_MEM_WORKER_PORT/api/health | jq .port
```
### Worker Keeps Crashing
@@ -954,11 +952,11 @@ npm run worker:status
# View logs
npm run worker:logs
# Check port file
cat ~/.claude-mem/worker.port
# Check configured port (per-user default = 37700 + uid % 100)
jq -r .CLAUDE_MEM_WORKER_PORT ~/.claude-mem/settings.json
# Test worker health
curl http://localhost:37777/health
# Test worker health (substitute PORT for the value above)
curl "http://127.0.0.1:$PORT/health"
```
### Database Inspection
-2
View File
@@ -34,8 +34,6 @@ Each folder's `CLAUDE.md` contains a "Recent Activity" section showing:
<claude-mem-context>
# Recent Activity
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
### Jan 4, 2026
| ID | Time | T | Title | Read |
+6 -13
View File
@@ -15,7 +15,7 @@ Claude-mem supports Google's Gemini API as an alternative to the Claude Agent SD
- **Cost savings**: The free tier covers most individual usage patterns
- **Same quality**: Gemini extracts observations using the same XML format as Claude
- **Seamless fallback**: Automatically falls back to Claude if Gemini is unavailable
- **Errors throw clearly**: 429s, 5xx, and network failures throw — leaving messages pending so they can be retried
- **Hot-swappable**: Switch providers without restarting the worker
## Getting a Free API Key
@@ -103,23 +103,16 @@ You can switch between Claude and Gemini at any time:
}
```
## Fallback Behavior
## Error Behavior
If Gemini is selected but encounters errors, claude-mem automatically falls back to the Claude Agent SDK:
If Gemini is selected and the API errors, claude-mem logs the failure and re-throws so the message stays pending for later retry. There is no Claude SDK fallback — earlier docs claimed automatic Claude fallback, but the wiring was never actually engaged in production (#2087). To switch providers, change `CLAUDE_MEM_PROVIDER` in settings.
**Triggers fallback:**
**Throwing conditions:**
- Rate limiting (HTTP 429)
- Server errors (HTTP 5xx)
- Network issues (connection refused, timeout)
**Does not trigger fallback:**
- Missing API key (logs warning, uses Claude from start)
- Invalid API key (fails with error)
When fallback occurs:
1. A warning is logged
2. Any in-progress messages are reset to pending
3. Claude SDK takes over with the full conversation context
- 4xx errors other than 429
- Missing API key
## Troubleshooting
+1 -1
View File
@@ -76,7 +76,7 @@ This means Claude "remembers" what happened in previous sessions!
### Worker Management
v4.0+ auto-starts the worker on first session. Manual commands below are optional.
The worker auto-starts on the first SessionStart hook, so you usually don't need these commands. They're handy for diagnostics.
```bash
# Start worker service (optional - auto-starts automatically)

Some files were not shown because too many files have changed in this diff Show More