mirror of
https://github.com/thedotmack/claude-mem.git
synced 2026-09-20 04:23:02 +08:00
work/unbundle
26 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e2d309b372 |
fix(hooks): anchor project names to Claude project dir (#4055)
Prefer CLAUDE_PROJECT_DIR when normalizing Claude Code hook payloads so SDK/subagent temp cwds are not stored as the project identity. getProjectName stays authoritative for explicit worker/transcript cwds. Rebase of #3439 onto main. Co-authored-by: Ben Younes <2910651+ousamabenyounes@users.noreply.github.com> |
||
|
|
76926eb5ef |
fix(hooks): skip plugin cache sessions (#4042)
SessionStart hooks running from an installed plugin version directory created a user project named after the plugin version. Treat the claude-mem plugin cache and marketplace runtime as internal paths. Rehost of #3429 with a typed session-init test (the original eval harness tripped typecheck). Closes #3425. Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Alex Newman <thedotmack@users.noreply.github.com> |
||
|
|
bf25c8972d |
fix: trip worker-unavailable fail-loud latch once, then fail-open (#4033)
Rehost of #3489. Trip the worker-unavailable fail-loud latch once, then fail-open so later hooks in the same outage do not keep blocking prompts. Co-authored-by: Tuna Birgun <tunabirgun@gmail.com> |
||
|
|
14447b9ee9 |
fix(hooks): fail open on malformed hook stdin (#4006)
Night-ship rebase of #3717 by @rodboev onto latest main. Treats the stdin reader's malformed-EOF and incomplete-timeout diagnostics as non-blocking so UserPromptSubmit fails open. Supersedes #3717. Refs #3699 / #3605. |
||
|
|
f03d918a47 |
fix(codex): stop an unparseable Bash command from blocking the tool call (#4001)
`extractFromBash` calls shell-quote's `parse()` on every Bash PreToolUse
command to enrich the payload with `filePaths` for read commands. `parse()`
throws on substitutions it cannot resolve, the throw was not caught here, and
it reached the generic hook handler — which logs `Hook error:` and returns
BLOCKING_ERROR. A best-effort convenience field was turning an ordinary shell
command into a blocked tool call.
Reproduced against the version actually installed here, not just the 1.8.4 in
the report:
shell-quote 1.10.0
parse("cat ${}") -> throws Bad substitution: ${}
parse("echo hi && cat ${}") -> throws Bad substitution: ${}
The trigger is an *empty* substitution. `${FOO}`, `${#FOO}`, `${FOO:?err}` and
`${arr[@]}` all parse fine on 1.10.0, which is why the form matters: the guard
is needed on current dependencies, not only on the older one.
It also matters that `plugin/scripts/worker-service.cjs` — the file that ships —
carries a bundled copy containing the same `Bad substitution` throw, so the
behaviour does not depend on what a fresh `npm install` resolves.
The parse is now guarded and yields no paths on failure. Failing to enrich is
invisible; blocking the user's command is not.
Three tests, two of which fail without the guard (7 pass / 2 fail):
- an unparseable substitution yields [] instead of throwing
- the same when it rides alongside a genuinely readable file, so the failure
is attributable to tokenisation rather than to a bad path
- a command that parses still extracts its paths — the guard must not
swallow the feature it protects
`bun test tests/cli tests/hooks` — 116 passed.
The committed bundle is left alone: rebuilding it here diverges from the
committed artifact for unrelated reasons, so it should be regenerated by the
normal release build rather than by this PR.
Co-authored-by: Nguyen Thanh Dat <ntdat812@gmail.com>
|
||
|
|
4da9ffc68c |
fix: rebuild plugin bundles to match manifest 13.24.0 (#3857)
* fix: rebuild plugin bundles so committed artifacts match manifest 13.24.0 The 13.24.0 release commit ( |
||
|
|
28c84255fe |
feat(telemetry): report observed model, source, and billing tier to PostHog (#3836)
PostHog only knew the observer model (the model claude-mem uses to write observations). It now also carries, per observed session: - observed_model: the model the user's IDE session is running, read from the transcript's last assistant entry on every Stop hook - observed_billing: closed enum (max | pro | team | enterprise | subscription | api_key | bedrock | vertex | foundry | unknown) detected in the hook process from Claude Code's env and ~/.claude.json oauthAccount.organizationType - ide and provider are now actually carried on observer_turn_rollup (the docs already claimed this; the rollup was dropping them) The Stop hook sends both fields to /api/sessions/summarize; the worker persists them on sdk_sessions (observed_model, observed_billing; schema version 50) and mirrors them onto the in-memory ActiveSession so every per-turn session_compressed record carries them into the rollup (last-seen semantics, 'unknown' when never learned). Privacy: only oauthAccount.organizationType, oauthAccount presence, and customApiKeyResponses.approved are read from .claude.json, projected immediately after parse; parse errors log only the error class name. Both new keys are added to the scrub whitelist and documented in telemetry.mdx. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019NoBPqpo7YaR3HKipExjAT |
||
|
|
fe9180c297 |
fix(codex): make startup context injection fast and bounded (#3789)
* fix(codex): simplify startup context injection * fix(codex): bound cold worker startup * fix(codex): cover supported cold starts |
||
|
|
a286e26c76 | fix(codex): avoid duplicate SessionStart context payload | ||
|
|
26fe40a6c1 |
feat: remove Gemini CLI host integration (Phase A)
Google deprecated Gemini CLI's free/individual tier in favor of Antigravity CLI (announced May 19, 2026, cutoff June 18, 2026). Removes claude-mem's Gemini CLI adapter, installer, IDE-detection entry, and docs. The separate Gemini LLM/observation provider (CLAUDE_MEM_GEMINI_API_KEY, GeminiProvider.ts, GeminiObservationProvider.ts) is untouched — that's a different, still-supported Google product. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YMT5iCF1kfikq7fetR6NF |
||
|
|
2a0a407340 |
chore: remove over-engineering findings from repo-wide ponytail audit (wave 1)
Cuts verified dead code, duplication, and speculative abstractions across 14 disjoint areas of the tree: sqlite layer, worker HTTP routes, search pipeline, server, chroma sync, viewer UI, npx-cli, MCP server, shared/utils, telemetry/infra, integrations, scripts, tests, and stale plans/evals docs. Also unifies the Chroma search pipeline onto SearchOrchestrator/strategies and ports dual-project (merged_into_project) scoping plus dateRange filtering into ChromaSearchStrategy, which corpus builds need but had silently lost. Full audit trail: 65-agent verification workflow wf_160a7862-1a6, 14-agent execution wf_c7e48c0f-164, 6-agent fixup wf_865f86a4-c6b. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
4af7daf29c | test: stabilize session init after server rename | ||
|
|
8d80ca4b15 | fix(codex): load startup context through MCP | ||
|
|
348d9ee406 | feat(recovery): scope memories by platform source | ||
|
|
35fbf917e0 |
fix(settings): keep file defaults from inheriting stale env overrides (#2853)
* Keep settings-file defaults from inheriting stale env state * Keep welcome-hint settings overrides request-local * Keep Bun test state from masking settings fallbacks * Keep sticky test mocks from bleeding into later routes * Keep worker recycle tests isolated from module cache |
||
|
|
671de5e3e2 | Prevent observer file edits from self-recording (#2519) | ||
|
|
cf450cec00 |
fix(build): enforce shipped dependency-closure boundary (plan-10, closes #2783) (#2800)
* plan-10 Phase 1: ship deterministic plugin runtime dependency closure Approach A — commit & ship plugin/bun.lock so the plugin's runtime node_modules install is deterministic, fixing the recurring `Cannot find module 'zod/v3'` (#2730). - align generated plugin zod range to root (^4.4.3) in build-hooks.js - new scripts/gen-plugin-lockfile.cjs generates plugin/bun.lock as a build artifact after build-hooks.js writes plugin/package.json - track & ship plugin/bun.lock (.gitignore negation, .npmignore, files allowlist) - install with `bun install --frozen-lockfile --ignore-scripts` at runtime Refs #2783, #2730 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * plan-10 Phase 2: fail loud at install time on a broken dependency closure Strengthen verifyCriticalModules to assert each dependency is actually importable via require.resolve (not merely a directory), and assert the worker-required zod subpaths resolve: zod/v3, zod/v4, zod/v4-mini. A partial/stale install now fails `npx claude-mem install` immediately instead of surfacing later as a Stop-hook `Cannot find module 'zod/v3'`. Bin-only packages (e.g. tree-sitter-cli, which has no bare-name entry point) fall back to resolving <dep>/package.json so a healthy install isn't falsely rejected. Adds tests/cli/verify-critical-modules.test.ts covering a missing zod/v3 subpath (throws), a complete zod (passes), and a bin-only dep (passes). Refs #2783, #2730 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * plan-10 Phase 3: clean-room install + import smoke test (#2730 backstop) Add scripts/smoke-clean-room.cjs and a `smoke:clean-room` npm script. Against fresh temp dirs (never the repo's node_modules) it: - copies plugin/, runs `bun install --frozen-lockfile --ignore-scripts`, asserts zod, zod/v3, zod/v4, zod/v4-mini resolve, and boots the bundled worker asserting no `Cannot find module` — the direct #2730 regression guard; - `npm pack`s, installs the tarball into a second temp dir, and load-tests the published bin entrypoint, warning loudly on any declared main/exports target missing from the tarball (latent #2537 gap). Exits non-zero naming the missing module on any failure; cleans up all temp dirs and the tarball in a finally. Refs #2783, #2730 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * plan-10 Phase 4: gate CI and publish on the clean-room dependency closure - ci.yml: new `clean-room-deps` job (between build and the docker e2e job) runs a frozen-lockfile drift check on the committed plugin lockfile, then `npm run build` + `npm run smoke:clean-room`. The drift step catches a contributor who changed plugin deps without regenerating plugin/bun.lock. - npm-publish.yml: add setup-bun and run `npm run smoke:clean-room` between build and `npm publish`, so a broken runtime closure cannot be published on a tag push (ci.yml does not run on tags). Secrets block untouched. Refs #2783, #2730 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * plan-10: doc recluster note + Phase 0 execution slice for #2730 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * plans: backlog recluster (2026-06-04) — cross-cluster execution order + plan-13 doc Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * plan-10: gen-plugin-lockfile degrades gracefully when bun is absent The Windows build CI job has no bun on PATH; regenerating the lockfile there threw and failed the build. The committed plugin/bun.lock is already the deterministic closure, so skip regeneration (non-fatal) when bun is missing and a lockfile exists; fail loud only when neither is available. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
54a3e6c46e |
test: fix CI-only failures from mock.module leakage and sqlite3 .recover
These surfaced once the lockfile fix let CI actually run the suite (it had
been dying at setup-node before install). All are test-environment issues;
no production code changed.
- logger.dataIn leak (9 summarize-tag-stripping fails): timeline-formatting,
claude-md-utils, and runtime-selector mocked logger via the process-global
mock.module but never restored it (mock.restore() does NOT undo mock.module).
A partial logger mock without dataIn leaked into later files under CI's file
order. Snapshot the real module and re-register it in afterAll, matching the
pattern already used by the chroma-mcp-manager tests.
- summarize-subagent-skip (4 fails, exposed by the runtime-selector restore):
it mocked SettingsDefaultsManager but not hook-settings, and loadFromFileOnce()
module-caches — so it depended on a leaked hook-settings mock. Mock
hook-settings directly and restore in afterAll so it is self-contained.
- schema-repair (3 fails): repairMalformedDatabase shells out to
`sqlite3 .recover`, which needs the dbpage vtab. The ubuntu CI runner's
sqlite3 lacks it ("no such table: sqlite_dbpage"). Probe the capability and
skip the repair tests when absent, mirroring the existing hasPython() guard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
6821d85245 |
refactor(hooks): plan-01 hook IO discipline — typed stdout/stderr/exit channels
- src/shared/hook-io.ts: single module routing intent (DIAGNOSTIC/MODEL_CONTEXT/ USER_HINT/BLOCKING_FEEDBACK/EXIT_SIGNAL) -> correct channel; only place that calls console.log/process.stderr.write/process.exit for the hook path. - Handlers are pure (return HookResult); hookCommand orchestrates hook-io. - Fix #2292: recordWorkerUnreachable now surfaces via emitBlockingError instead of being swallowed by the stderr no-op; exit-code strategy preserved verbatim. - Quiet-on-success: buffered library stderr dropped on graceful exit (#2694). - lint:hook-io CI check forbids direct stream writes in handlers/** and adapters/**. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a8cf671648 |
fix(tests+pipeline): repair test suite (46->7 fails) + plan-09 schema self-healing
Test-suite repair:
- Fix cross-suite mock.module contamination: polluting files (worker-api-endpoints,
hook-execution-e2e, file-context, chroma-mcp-manager-*, summarize-*) now snapshot
and restore real modules in afterAll, making the suite order-independent (17 fails).
- Align stale tests to current contracts (parser {valid,observations[],summary} API,
worker-spawner WorkerStartResult, welcome-card v3 key, haiku default model, .mcp.json
removal, claude-md mock).
Product fixes:
- plan-09: SchemaRepair.ts + Database.openWithSchemaRepair() — self-heal malformed
on-disk schema (orphaned index / dropped column) via sqlite3 .recover before PRAGMA,
preserving data. Migrations re-establish canonical schema. (#2433 groundwork)
- Welcome-hint flag now reads env before cached settings (env always wins).
- Replace console.* with logger.* in background services (plugin-state, project-filter,
worktree, smart-file-read/parser, CorpusRoutes).
Remaining 7 failures are openclaw Observation I/O tests, addressed in plan-08.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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 |
||
|
|
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
|
||
|
|
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> |