From 93a57bf7e858b0d48232ed7e310216043cf6c68e Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 15 Apr 2026 15:35:15 -0700 Subject: [PATCH 1/7] chore: add release infrastructure for v0.1-alpha MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LICENSE (MIT) - CLAUDE.md (repo rules for AI assistants) - CONTRIBUTING.md (setup, workflow, code style) - CODE_OF_CONDUCT.md (Contributor Covenant) - CHANGELOG.md (initial v0.1.0-alpha entry) - .github/workflows/ci.yml (typecheck, fallow, tests with Postgres service) - .github/workflows/release.yml (tag-triggered release with Docker verification) - .github/ISSUE_TEMPLATE/ (bug report + feature request YAML forms) - .github/PULL_REQUEST_TEMPLATE.md - .github/CODEOWNERS - Fix Dockerfile (pnpm → npm) - Add docker-compose.smoke.yml overlay for hermetic CI smoke tests - Update package.json with metadata (repo, homepage, bugs, keywords, license, engines) - Fix vitest config (scoped to src/, updated comment) - Regenerate clean package-lock.json - Update README with features, badges, scope statement, contributing link Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 49 ++ .github/ISSUE_TEMPLATE/feature_request.yml | 33 + .github/PULL_REQUEST_TEMPLATE.md | 18 + .github/workflows/ci.yml | 49 ++ .github/workflows/release.yml | 59 ++ CHANGELOG.md | 24 + CLAUDE.md | 87 +++ CODE_OF_CONDUCT.md | 38 ++ CONTRIBUTING.md | 103 +++ Dockerfile | 7 +- LICENSE | 21 + README.md | 28 +- docker-compose.smoke.yml | 15 + package-lock.json | 742 +-------------------- package.json | 34 +- vitest.config.ts | 3 +- 17 files changed, 569 insertions(+), 742 deletions(-) create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 docker-compose.smoke.yml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..860be9f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ethanj diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..96f77e5 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,49 @@ +name: Bug Report +description: Report a bug in Atomicmemory-core +labels: [bug] +body: + - type: textarea + id: description + attributes: + label: Description + description: A clear description of the bug. + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Minimal steps to reproduce the behavior. + placeholder: | + 1. Start the server with `docker compose up` + 2. POST to /memories/ingest with ... + 3. See error ... + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What you expected to happen. + + - type: dropdown + id: embedding-provider + attributes: + label: Embedding provider + options: + - openai + - openai-compatible + - ollama + - transformers + + - type: input + id: node-version + attributes: + label: Node.js version + placeholder: "22.x" + + - type: input + id: postgres-version + attributes: + label: PostgreSQL version + placeholder: "17 with pgvector" diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..1aa2b74 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,33 @@ +name: Feature Request +description: Suggest a new feature for Atomicmemory-core +labels: [enhancement] +body: + - type: textarea + id: problem + attributes: + label: Problem + description: What problem does this feature solve? + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Proposed solution + description: How should this work? + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: What other approaches did you consider? + + - type: dropdown + id: scope + attributes: + label: Scope + description: Does this belong in the core runtime or the research repo? + options: + - Core runtime (API, ingest, search, storage) + - Research (benchmarks, eval, design exploration) + - Not sure diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..f90f2a4 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +## What changed + + + +## Checklist + +- [ ] `npx tsc --noEmit` — type check passes +- [ ] `npm test` — all tests pass +- [ ] `fallow` — reports no issues +- [ ] Changes are to runtime behavior (not research/eval) + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactoring (no behavior change) +- [ ] Documentation +- [ ] CI / infrastructure diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c22fa7b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,49 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + services: + postgres: + image: pgvector/pgvector:pg17 + env: + POSTGRES_USER: atomicmem + POSTGRES_PASSWORD: atomicmem + POSTGRES_DB: atomicmem_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U atomicmem" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + + env: + DATABASE_URL: postgresql://atomicmem:atomicmem@localhost:5432/atomicmem_test + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Type check + run: npx tsc --noEmit + + - name: Code health (fallow) + run: npx fallow + + - name: Run tests + run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..67e89bf --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + + services: + postgres: + image: pgvector/pgvector:pg17 + env: + POSTGRES_USER: atomicmem + POSTGRES_PASSWORD: atomicmem + POSTGRES_DB: atomicmem_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U atomicmem" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + + env: + DATABASE_URL: postgresql://atomicmem:atomicmem@localhost:5432/atomicmem_test + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Type check + run: npx tsc --noEmit + + - name: Code health (fallow) + run: npx fallow + + - name: Run tests + run: npm test + + - name: Verify Docker build + run: docker build -t atomicmemory-core:${{ github.ref_name }} . + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + generate_release_notes: true diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5f962e3 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). + +## [Unreleased] + +## [0.1.0-alpha] - 2026-04-15 + +### Added +- Initial extraction from atomicmemory-research prototype +- Express API server with memory ingest, search, and consolidation endpoints +- Postgres + pgvector storage backend +- Pluggable embedding providers: openai, openai-compatible, ollama, transformers (WASM) +- AUDN mutation engine (Add, Update, Delete, No-op) with fail-closed semantics +- Contradiction-safe claim versioning +- Hybrid retrieval (vector + BM25/FTS) +- Tiered context packaging +- Entity graph with spreading activation +- Docker and Railway deployment support +- 869 tests across 79 test files +- CI with GitHub Actions (typecheck, fallow, tests) +- Contributor docs (CONTRIBUTING.md, issue/PR templates) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..a0f806f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,87 @@ +# Atomicmemory-core + +Open-source memory engine for AI applications. Docker-deployable backend with +semantic retrieval, AUDN mutation, and contradiction-safe claim versioning. + +## Architecture + +- **Runtime**: Express server (TypeScript, ESM) +- **Storage**: Postgres + pgvector +- **Embeddings**: Pluggable (openai, openai-compatible, ollama, transformers/WASM) +- **Structure**: Single-package repo. Routes → Services → Repository → Postgres. + +### Key modules +- `src/routes/` — Express route handlers +- `src/services/` — Business logic (ingest, search, AUDN, packaging) +- `src/db/` — Repository layer, schema, migrations +- `src/services/memory-service.ts` — Thin facade delegating to focused sub-modules +- `src/services/memory-ingest.ts` — Ingest pipeline +- `src/services/memory-search.ts` — Search/retrieval pipeline +- `src/services/memory-audn.ts` — AUDN mutation decisions +- `src/services/memory-crud.ts` — List/get/delete/consolidate +- `src/services/memory-storage.ts` — Canonical fact storage and projections + +### Boundary rule +This repo is the **releaseable runtime engine**. Eval harnesses, benchmarks, +competitive analysis, and design explorations belong in `atomicmemory-research`. +If it changes shipped backend behavior, it belongs here. If it only changes +benchmark outputs or scoring methodology, it belongs in research. + +## Development Guidelines + +### Code Style & Standards +- Files must be smaller than 400 lines excluding comments. Refactor when exceeded. +- Functions must be smaller than 40 lines excluding comments and catch/finally blocks. +- Test files must be smaller than 400 lines. Tests must be smaller than 40 lines. +- Use TypeScript with proper types for all variables, parameters, and return values. +- Use interfaces for object shapes. Avoid `any`. Use generics when appropriate. +- Use optional chaining (`?.`) and nullish coalescing (`??`). +- No fallback modes — if something fails, fail closed, don't run degraded. +- No silent error catching — all errors must be logged or propagated. +- No direct access to env vars (use `src/config.ts`). +- No hardcoded values — use named constants. +- No timing-based solutions in code or tests. All solutions must be deterministic. + +### Clean Code Rules +- Meaningful names that reveal purpose. +- One function, one responsibility. +- Avoid magic numbers — use named constants. +- Keep code DRY. Duplicate code means duplicate bugs. +- Avoid deep nesting — flatten control flow. +- Comment why, not what. +- Limit function arguments — group related data into objects. + +### Comments and Documentation +- Include a JSDoc comment at the top of each file. +- Write clear comments for complex logic. +- Document public APIs and functions. +- Keep comments up-to-date with code changes. + +### Pre-Commit Checks + +Before committing any work: + +1. `npx tsc --noEmit` — type-check passes +2. `npm test` — all tests pass (requires Postgres via .env.test) +3. `fallow` — zero issues (dead code, duplication, complexity). Use `fallow fix --dry-run` to preview, `fallow fix --yes` to apply. Fix remaining issues manually. + +### Running Tests +- Full suite: `npm test` (requires DATABASE_URL in .env.test pointing to Postgres with pgvector) +- Single test: `dotenv -e .env.test -- npx vitest run "src/**/__tests__/" --reporter verbose` +- Deployment tests: `npm run test:deployment` +- Docker smoke: `npm run test:docker-smoke` + +### Git Workflow +- Never commit directly to main. Always create a branch. +- Do not commit until changes are approved by the user. +- When committing, create a temporary `commit-message.txt` and use `-F`, then delete. +- When creating a PR, create a temporary `pr-description.md`, use `gh pr create --body-file`, then delete. +- Never use `git reset --hard`. + +## General Rules + +1. Read the codebase before making changes. Never speculate about code you haven't opened. +2. Check in with the user before making major changes. +3. Make every change as simple as possible — minimal impact, maximum clarity. +4. Check for existing implementations before writing new code. +5. Mutations must fail closed — no silent fallback-to-ADD on UPDATE/DELETE/SUPERSEDE errors. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..b078496 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,38 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the project maintainers. All complaints will be reviewed and +investigated promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..c84675d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,103 @@ +# Contributing to Atomicmemory-core + +Thanks for your interest in contributing! This guide covers setup, workflow, and quality expectations. + +## Prerequisites + +- Node.js 22+ +- PostgreSQL 17 with [pgvector](https://github.com/pgvector/pgvector) extension +- Docker (for smoke tests and local deployment) + +## Local Setup + +```bash +git clone https://github.com/atomicmemory/Atomicmemory-core.git +cd Atomicmemory-core +npm install + +# Configure test database +cp .env.test.example .env.test +# Edit .env.test with your Postgres connection string + +# Or use Docker for Postgres: +docker compose up postgres -d + +# Run tests +npm test +``` + +## Pre-PR Checklist + +Run these before opening a PR — they match what CI runs: + +```bash +npx tsc --noEmit # Type check +npm test # All tests pass +fallow # Code health (zero issues required) +``` + +## Branch and Commit Conventions + +- Branch from `main` +- Use descriptive branch names: `feat/add-search-filter`, `fix/cors-config`, `docs/api-reference` +- Write clear commit messages describing what changed and why + +## Code Style + +Enforced by `fallow` and TypeScript strict mode: + +- **Files**: < 400 lines (excluding comments) +- **Functions**: < 40 lines (excluding comments, catch/finally) +- **No `any` types** — use proper interfaces and generics +- **No silent error catching** — log or propagate every error +- **No fallback modes** — if something fails, fail closed +- **DRY** — fallow flags duplicated code + +See `CLAUDE.md` for the full style guide. + +## What Belongs Here vs Research + +**Atomicmemory-core** (this repo): +- Runtime behavior (API routes, services, storage) +- Schema and migrations +- Deployment config (Docker, Railway) +- Product-facing docs + +**atomicmemory-research** (separate repo): +- Benchmarks and eval harnesses +- Competitive analysis +- Design explorations +- Experiment results + +**Rule of thumb**: If it changes what the deployed server does, it belongs here. If it only changes benchmark scores or methodology, it belongs in research. + +## Adding a New Endpoint + +1. Add the route handler in `src/routes/` +2. Add business logic in `src/services/` (keep routes thin) +3. Add repository methods in `src/db/` if new queries are needed +4. Add tests in the corresponding `__tests__/` directory +5. Update `docs/api-reference.md` +6. Run the full pre-PR checklist + +## Adding a New Service + +1. Create the service file in `src/services/` +2. Keep it focused — one responsibility per file +3. If it needs config, add the env var to `src/config.ts` and `.env.example` +4. Add unit tests with mocked dependencies +5. Wire it into `memory-service.ts` if it's part of the ingest/search pipeline + +## Review Process + +- All PRs require CI to pass +- Maintainer review required before merge +- Keep PRs focused — one concern per PR + +## Reporting Issues + +Use the [issue templates](https://github.com/atomicmemory/Atomicmemory-core/issues/new/choose) for bug reports and feature requests. + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE). diff --git a/Dockerfile b/Dockerfile index 798aaa2..be6db5d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,14 +2,11 @@ FROM node:22-slim WORKDIR /app -# Install pnpm -RUN corepack enable && corepack prepare pnpm@latest --activate - # Copy dependency manifests first for layer caching -COPY package.json pnpm-lock.yaml ./ +COPY package.json package-lock.json ./ # Install production dependencies -RUN pnpm install --no-frozen-lockfile +RUN npm ci --omit=dev # Copy application source COPY src/ ./src/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..367451c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 AtomicMemory + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index d9b2425..5a9fa32 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,31 @@ # Atomicmemory Core +[![CI](https://github.com/atomicmemory/Atomicmemory-core/actions/workflows/ci.yml/badge.svg)](https://github.com/atomicmemory/Atomicmemory-core/actions/workflows/ci.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + Open-source memory engine for AI applications and agents. Docker-deployable memory backend with durable context, semantic retrieval, and memory mutation (AUDN: Add, Update, Delete, No-op). +> **Alpha** — This is an early release. The API surface is evolving. See the [changelog](CHANGELOG.md) for what's included. + +## Features + +- **Semantic ingest** — extract structured facts from conversations with contradiction detection +- **Hybrid retrieval** — vector similarity + BM25/FTS with RRF fusion +- **AUDN mutation** — Add, Update, Delete, No-op decisions with fail-closed integrity +- **Claim versioning** — temporal lineage tracking with supersession and invalidation +- **Tiered context packaging** — L0/L1/L2 compression for token-efficient retrieval +- **Entity graph** — spreading activation over extracted entities +- **Pluggable embeddings** — openai, openai-compatible, ollama, transformers (local WASM) +- **Docker + Railway** — one-command deployment with Postgres + pgvector + +## What This Is Not + +- Not a benchmark suite — eval harnesses live in [atomicmemory-research](https://github.com/atomicmemory/atomicmemory-research) +- Not an SDK or client library — this is the server/backend +- Not production-hardened yet — alpha quality, evolving API + ## Quick Start ### Docker (recommended) @@ -110,6 +132,10 @@ npm run test:docker-smoke # Docker smoke test npm run migrate:test # Run migrations against test DB ``` +## Contributing + +See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, workflow, and code style expectations. + ## License -TBD +[MIT](LICENSE) diff --git a/docker-compose.smoke.yml b/docker-compose.smoke.yml new file mode 100644 index 0000000..af00e4b --- /dev/null +++ b/docker-compose.smoke.yml @@ -0,0 +1,15 @@ +# Smoke test overlay — used by scripts/docker-smoke-test.sh +# Overrides port and provider config for hermetic CI testing. +# +# Usage: docker compose -f docker-compose.yml -f docker-compose.smoke.yml up --build +services: + app: + ports: + - "${APP_PORT:-3060}:3050" + environment: + - EMBEDDING_PROVIDER=transformers + - EMBEDDING_MODEL=Xenova/all-MiniLM-L6-v2 + - EMBEDDING_DIMENSIONS=384 + - LLM_PROVIDER=openai + - OPENAI_API_KEY=sk-smoke-test-dummy + - PORT=3050 diff --git a/package-lock.json b/package-lock.json index cc877b9..6c99eac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,34 +1,33 @@ { "name": "@atomicmemory/atomicmemory-engine", - "version": "0.0.1", + "version": "0.1.0-alpha", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@atomicmemory/atomicmemory-engine", - "version": "0.0.1", + "version": "0.1.0-alpha", + "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.80.0", "@huggingface/transformers": "^3.8.1", "express": "^5.1.0", - "mem0ai": "^2.3.0", "openai": "^4.80.0", "pg": "^8.18.0", "pgvector": "^0.2.0", "undici": "^7.24.5" }, "devDependencies": { - "@types/better-sqlite3": "^7.6.13", "@types/express": "^5.0.0", "@types/node": "^22.0.0", "@types/pg": "^8.15.0", - "@types/supertest": "^7.2.0", - "better-sqlite3": "^12.8.0", "dotenv-cli": "^8.0.0", - "supertest": "^7.2.2", "tsx": "^4.19.0", "typescript": "^5.7.0", "vitest": "^4.1.3" + }, + "engines": { + "node": ">=22.0.0" } }, "node_modules/@anthropic-ai/sdk": { @@ -1041,9 +1040,9 @@ "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", - "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, @@ -1059,19 +1058,6 @@ "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@oxc-project/types": { "version": "0.124.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", @@ -1082,16 +1068,6 @@ "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" - } - }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1438,16 +1414,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/better-sqlite3": { - "version": "7.6.13", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", - "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", @@ -1480,13 +1446,6 @@ "@types/node": "*" } }, - "node_modules/@types/cookiejar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -1533,13 +1492,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/methods": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", - "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "22.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", @@ -1606,30 +1558,6 @@ "@types/node": "*" } }, - "node_modules/@types/superagent": { - "version": "8.1.9", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", - "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cookiejar": "^2.1.5", - "@types/methods": "^1.1.4", - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/@types/supertest": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.0.tgz", - "integrity": "sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/methods": "^1.1.4", - "@types/superagent": "^8.1.0" - } - }, "node_modules/@vitest/expect": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", @@ -1780,13 +1708,6 @@ "node": ">= 8.0.0" } }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -1803,75 +1724,6 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/axios": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", - "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/better-sqlite3": { - "version": "12.9.0", - "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.9.0.tgz", - "integrity": "sha512-wqUv4Gm3toFpHDQmaKD4QhZm3g1DjUBI0yzS4UBl6lElUmXFYdTQmmEDpAFa5o8FiFiymURypEnfVHzILKaxqQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" - }, - "engines": { - "node": "20.x || 22.x || 23.x || 24.x || 25.x" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -1903,31 +1755,6 @@ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "license": "MIT" }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1997,16 +1824,6 @@ "node": ">= 0.8" } }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/content-disposition": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", @@ -2054,13 +1871,6 @@ "node": ">=6.6.0" } }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2093,32 +1903,6 @@ } } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2186,17 +1970,6 @@ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "license": "MIT" }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", - "dev": true, - "license": "ISC", - "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -2265,16 +2038,6 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2421,16 +2184,6 @@ "node": ">=6" } }, - "node_modules/expand-template": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", - "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", - "dev": true, - "license": "(MIT OR WTFPL)", - "engines": { - "node": ">=6" - } - }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -2484,13 +2237,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "dev": true, - "license": "MIT" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2509,13 +2255,6 @@ } } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "license": "MIT" - }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -2543,26 +2282,6 @@ "integrity": "sha512-MI1qs7Lo4Syw0EOzUl0xjs2lsoeqFku44KpngfIduHBYvzm8h2+7K8YMQh1JtVVVrUvhLpNwqVi4DERegUJhPQ==", "license": "Apache-2.0" }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -2619,24 +2338,6 @@ "node": ">= 12.20" } }, - "node_modules/formidable": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2655,13 +2356,6 @@ "node": ">= 0.8" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true, - "license": "MIT" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2736,13 +2430,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", - "dev": true, - "license": "MIT" - }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -2890,40 +2577,12 @@ "url": "https://opencollective.com/express" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3272,41 +2931,6 @@ "node": ">= 0.8" } }, - "node_modules/mem0ai": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mem0ai/-/mem0ai-2.4.6.tgz", - "integrity": "sha512-8DbLhwD2rd85CNOZgINY3q7q4DqRi/puo1WPszCovX1xSYTO8+Y1ONuSAFA9/Zkfqs0N17obRDaheQSMJlRwhA==", - "license": "Apache-2.0", - "dependencies": { - "axios": "1.13.6", - "openai": "^4.93.0", - "uuid": "9.0.1", - "zod": "^3.24.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@anthropic-ai/sdk": "^0.40.1", - "@azure/identity": "^4.0.0", - "@azure/search-documents": "^12.0.0", - "@cloudflare/workers-types": "^4.20250504.0", - "@google/genai": "^1.2.0", - "@langchain/core": "^1.0.0", - "@mistralai/mistralai": "^1.5.2", - "@qdrant/js-client-rest": "1.13.0", - "@supabase/supabase-js": "^2.49.1", - "@types/jest": "29.5.14", - "@types/pg": "8.11.0", - "better-sqlite3": "^12.6.2", - "cloudflare": "^4.2.0", - "groq-sdk": "0.3.0", - "neo4j-driver": "^5.28.1", - "ollama": "^0.5.14", - "pg": "8.11.3", - "redis": "^4.6.13" - } - }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", @@ -3319,29 +2943,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -3367,19 +2968,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", @@ -3411,13 +2999,6 @@ "node": ">= 18" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3443,13 +3024,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "dev": true, - "license": "MIT" - }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -3459,19 +3033,6 @@ "node": ">= 0.6" } }, - "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -3814,9 +3375,9 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", "dev": true, "funding": [ { @@ -3881,34 +3442,6 @@ "node": ">=0.10.0" } }, - "node_modules/prebuild-install": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", - "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", - "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^2.0.0", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/protobufjs": { "version": "7.5.5", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", @@ -3946,23 +3479,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/qs": { "version": "6.15.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", @@ -4002,37 +3518,6 @@ "node": ">= 0.10" } }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -4110,27 +3595,6 @@ "node": ">= 18" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -4367,53 +3831,6 @@ "dev": true, "license": "ISC" }, - "node_modules/simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/simple-get": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", - "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "decompress-response": "^6.0.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4462,62 +3879,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/superagent": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", - "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "component-emitter": "^1.3.1", - "cookiejar": "^2.1.4", - "debug": "^4.3.7", - "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.5", - "formidable": "^3.5.4", - "methods": "^1.1.2", - "mime": "2.6.0", - "qs": "^6.14.1" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/supertest": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", - "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cookie-signature": "^1.2.2", - "methods": "^1.1.2", - "superagent": "^10.3.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, "node_modules/tar": { "version": "7.5.13", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", @@ -4534,43 +3895,6 @@ "node": ">=18" } }, - "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-fs/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "license": "ISC" - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -4663,19 +3987,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -4740,26 +4051,6 @@ "node": ">= 0.8" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5018,15 +4309,6 @@ "engines": { "node": ">=18" } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } } } } diff --git a/package.json b/package.json index 705fd82..832f482 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,32 @@ { "name": "@atomicmemory/atomicmemory-engine", - "version": "0.0.1", + "version": "0.1.0-alpha", + "description": "Open-source memory engine for AI applications — semantic retrieval, AUDN mutation, and contradiction-safe claim versioning.", "private": true, "type": "module", + "license": "MIT", + "author": "AtomicMemory ", + "repository": { + "type": "git", + "url": "https://github.com/atomicmemory/Atomicmemory-core.git" + }, + "homepage": "https://github.com/atomicmemory/Atomicmemory-core", + "bugs": { + "url": "https://github.com/atomicmemory/Atomicmemory-core/issues" + }, + "keywords": [ + "memory", + "ai", + "llm", + "semantic-search", + "pgvector", + "embeddings", + "context", + "retrieval" + ], + "engines": { + "node": ">=22.0.0" + }, "main": "./src/index.ts", "exports": { ".": "./src/index.ts", @@ -24,18 +48,18 @@ "@anthropic-ai/sdk": "^0.80.0", "@huggingface/transformers": "^3.8.1", "express": "^5.1.0", - "undici": "^7.24.5", "openai": "^4.80.0", "pg": "^8.18.0", - "pgvector": "^0.2.0" + "pgvector": "^0.2.0", + "undici": "^7.24.5" }, "devDependencies": { "@types/express": "^5.0.0", "@types/node": "^22.0.0", "@types/pg": "^8.15.0", - "vitest": "^4.1.3", "dotenv-cli": "^8.0.0", + "tsx": "^4.19.0", "typescript": "^5.7.0", - "tsx": "^4.19.0" + "vitest": "^4.1.3" } } diff --git a/vitest.config.ts b/vitest.config.ts index 54f68d2..9d263bc 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,5 +1,5 @@ /** - * Vitest configuration for the research prototype. + * Vitest configuration for Atomicmemory-core. * Disables file-level parallelism to prevent DB integration tests * from interfering with each other via shared schema DDL. */ @@ -8,6 +8,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + include: ['src/**/*.test.ts'], fileParallelism: false, }, }); From 82fd190b7317bc58854bb1fc7b3362f0d684419b Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 15 Apr 2026 15:41:15 -0700 Subject: [PATCH 2/7] fix: runtime hardening for v0.1-alpha release - Port embedding dimension startup guard (verifies DB matches config before serving) - Make CORS origins ENV-configurable via ALLOWED_ORIGINS - Replace all hardcoded 1536-dim test vectors with config.embeddingDimensions - Switch smoke test to /memories/ingest/quick for hermetic CI (no LLM required) - Clean stale "research prototype" references in docs - Update .env.example with ALLOWED_ORIGINS documentation Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.example | 2 + docs/design/bm25-rrf-channel.md | 4 +- scripts/docker-smoke-test.sh | 8 ++-- src/__tests__/smoke.test.ts | 4 +- src/db/__tests__/links.test.ts | 8 ++-- src/db/__tests__/pgvector-smoke.test.ts | 28 ++++++------- src/db/__tests__/temporal-neighbors.test.ts | 3 +- src/routes/memories.ts | 7 +++- src/server.ts | 39 +++++++++++++++++-- .../__tests__/embedding-cache.test.ts | 2 +- src/services/__tests__/lesson-service.test.ts | 2 +- 11 files changed, 73 insertions(+), 34 deletions(-) diff --git a/.env.example b/.env.example index 163ef70..2010903 100644 --- a/.env.example +++ b/.env.example @@ -4,6 +4,8 @@ OPENAI_API_KEY=sk-... # --- Server --- PORT=3050 +# Comma-separated list of allowed CORS origins (default: localhost:3050,3081) +# ALLOWED_ORIGINS=http://localhost:3050,http://localhost:3081 # --- Embedding --- # EMBEDDING_PROVIDER=openai diff --git a/docs/design/bm25-rrf-channel.md b/docs/design/bm25-rrf-channel.md index 712bc17..fe5038c 100644 --- a/docs/design/bm25-rrf-channel.md +++ b/docs/design/bm25-rrf-channel.md @@ -2,7 +2,7 @@ **Status:** Proposal **Date:** 2026-04-14 -**Scope:** atomicmemory-research prototype retrieval stack +**Scope:** Atomicmemory-core retrieval stack ## Problem @@ -78,7 +78,7 @@ FROM memories WHERE content @@@ 'query text'; | BM25 fidelity | True Okapi BM25 with IDF; best quality | | Code complexity | Minimal — one new query function | | Portability | Requires ParadeDB in Docker image; unavailable on vanilla RDS/Supabase | -| Recommendation | Use for the research prototype; revisit for production deployment | +| Recommendation | Evaluate for core; revisit portability for managed Postgres hosts | ### Path B: Pure-Postgres BM25 approximation diff --git a/scripts/docker-smoke-test.sh b/scripts/docker-smoke-test.sh index 40ccfee..46bcaef 100755 --- a/scripts/docker-smoke-test.sh +++ b/scripts/docker-smoke-test.sh @@ -160,10 +160,10 @@ stats_status=$(curl -sf -o /dev/null -w '%{http_code}' \ assert_ok "POST /memories/stats returns 200 (DB connected)" \ '[ "$stats_status" = "200" ]' -# --- Test 5: Ingest endpoint --- -log "Test: ingest endpoint" +# --- Test 5: Quick ingest endpoint (no LLM required — embedding-only dedup) --- +log "Test: quick ingest endpoint" ingest_response=$(curl -sf -w '\n%{http_code}' \ - -X POST "$BASE/memories/ingest" \ + -X POST "$BASE/memories/ingest/quick" \ -H "Content-Type: application/json" \ -d '{ "user_id": "smoke-test-user", @@ -172,7 +172,7 @@ ingest_response=$(curl -sf -w '\n%{http_code}' \ }') ingest_status=$(echo "$ingest_response" | tail -1) ingest_body=$(echo "$ingest_response" | head -n -1) -assert_ok "POST /memories/ingest returns 200" \ +assert_ok "POST /memories/ingest/quick returns 200" \ '[ "$ingest_status" = "200" ]' assert_ok "Ingest stored at least 1 memory" \ '[ "$(echo "$ingest_body" | jq -r .memoriesStored)" -ge 1 ]' diff --git a/src/__tests__/smoke.test.ts b/src/__tests__/smoke.test.ts index 899ed37..82e4923 100644 --- a/src/__tests__/smoke.test.ts +++ b/src/__tests__/smoke.test.ts @@ -5,11 +5,13 @@ import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest'; +import { config } from '../config.js'; + /** Generate a deterministic embedding seeded by a string. */ function seededEmbedding(text: string): number[] { let seed = 0; for (let i = 0; i < text.length; i++) seed = ((seed << 5) - seed + text.charCodeAt(i)) | 0; - return Array.from({ length: 1536 }, (_, i) => Math.sin(seed * (i + 1)) / 10); + return Array.from({ length: config.embeddingDimensions }, (_, i) => Math.sin(seed * (i + 1)) / 10); } const mocks = vi.hoisted(() => ({ diff --git a/src/db/__tests__/links.test.ts b/src/db/__tests__/links.test.ts index 15a4b08..10b0821 100644 --- a/src/db/__tests__/links.test.ts +++ b/src/db/__tests__/links.test.ts @@ -17,14 +17,16 @@ import { findLinkCandidates, fetchMemoriesByIds, } from '../repository-links.js'; +import { config } from '../../config.js'; const TEST_USER = 'links-test-user-isolated'; +const DIM = config.embeddingDimensions; function makeEmbedding(index: number): number[] { const embeddings = [ - Array.from({ length: 1536 }, (_, i) => (i === 0 ? 1 : 0)), - Array.from({ length: 1536 }, (_, i) => (i === 0 ? 0.9 : i === 1 ? 0.4 : 0)), - Array.from({ length: 1536 }, (_, i) => (i === 1 ? 1 : 0)), + Array.from({ length: DIM }, (_, i) => (i === 0 ? 1 : 0)), + Array.from({ length: DIM }, (_, i) => (i === 0 ? 0.9 : i === 1 ? 0.4 : 0)), + Array.from({ length: DIM }, (_, i) => (i === 1 ? 1 : 0)), ]; return embeddings[index]; } diff --git a/src/db/__tests__/pgvector-smoke.test.ts b/src/db/__tests__/pgvector-smoke.test.ts index a336cc7..ab9d3c9 100644 --- a/src/db/__tests__/pgvector-smoke.test.ts +++ b/src/db/__tests__/pgvector-smoke.test.ts @@ -6,16 +6,10 @@ import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'; import { createMemoryTestContext } from './test-fixtures.js'; import { pool } from '../pool.js'; +import { unitVector } from './test-fixtures.js'; const TEST_USER = 'test-user-1'; -/** Generate a random 1536-dim unit vector. */ -function randomEmbedding(seed: number): number[] { - const vec = Array.from({ length: 1536 }, (_, i) => Math.sin(seed * (i + 1))); - const norm = Math.sqrt(vec.reduce((s, v) => s + v * v, 0)); - return vec.map((v) => v / norm); -} - /** Make a vector close to another by adding small noise. */ function similarTo(base: number[], noise: number): number[] { const vec = base.map((v) => v + (Math.random() - 0.5) * noise); @@ -27,7 +21,7 @@ describe('pgvector smoke test', () => { const { repo } = createMemoryTestContext(pool, { beforeAll, beforeEach, afterAll }); it('stores and retrieves a memory', async () => { - const embedding = randomEmbedding(42); + const embedding = unitVector(42); const id = await repo.storeMemory({ userId: TEST_USER, content: 'User prefers dark mode', @@ -44,9 +38,9 @@ describe('pgvector smoke test', () => { }); it('finds similar vectors with scored search', async () => { - const baseVec = randomEmbedding(1); + const baseVec = unitVector(1); const similarVec = similarTo(baseVec, 0.1); - const differentVec = randomEmbedding(999); + const differentVec = unitVector(999); await repo.storeMemory({ userId: TEST_USER, content: 'similar to query', embedding: similarVec, importance: 0.7, sourceSite: 'test' }); await repo.storeMemory({ userId: TEST_USER, content: 'completely different', embedding: differentVec, importance: 0.5, sourceSite: 'test' }); @@ -58,7 +52,7 @@ describe('pgvector smoke test', () => { }); it('isolates memories by user_id', async () => { - const vec = randomEmbedding(10); + const vec = unitVector(10); await repo.storeMemory({ userId: 'user-a', content: 'from user A', embedding: vec, importance: 0.5, sourceSite: 'test' }); await repo.storeMemory({ userId: 'user-b', content: 'from user B', embedding: similarTo(vec, 0.1), importance: 0.5, sourceSite: 'test' }); @@ -72,7 +66,7 @@ describe('pgvector smoke test', () => { }); it('filters by source_site within user', async () => { - const vec = randomEmbedding(10); + const vec = unitVector(10); await repo.storeMemory({ userId: TEST_USER, content: 'from claude', embedding: vec, importance: 0.5, sourceSite: 'claude.ai' }); await repo.storeMemory({ userId: TEST_USER, content: 'from chatgpt', embedding: similarTo(vec, 0.1), importance: 0.5, sourceSite: 'chatgpt.com' }); @@ -96,7 +90,7 @@ describe('pgvector smoke test', () => { }); it('updates access count on touch', async () => { - const vec = randomEmbedding(77); + const vec = unitVector(77); const id = await repo.storeMemory({ userId: TEST_USER, content: 'test', embedding: vec, importance: 0.5, sourceSite: 'test' }); await repo.touchMemory(id); @@ -107,17 +101,17 @@ describe('pgvector smoke test', () => { }); it('clamps importance to [0, 1]', async () => { - const vec = randomEmbedding(88); + const vec = unitVector(88); const id = await repo.storeMemory({ userId: TEST_USER, content: 'clamped', embedding: vec, importance: 1.5, sourceSite: 'test' }); const memory = await repo.getMemory(id); expect(memory!.importance).toBeLessThanOrEqual(1.0); }); it('counts memories by user', async () => { - const vec = randomEmbedding(50); + const vec = unitVector(50); await repo.storeMemory({ userId: 'counter-user', content: 'a', embedding: vec, importance: 0.5, sourceSite: 'test' }); - await repo.storeMemory({ userId: 'counter-user', content: 'b', embedding: randomEmbedding(51), importance: 0.5, sourceSite: 'test' }); - await repo.storeMemory({ userId: 'other-user', content: 'c', embedding: randomEmbedding(52), importance: 0.5, sourceSite: 'test' }); + await repo.storeMemory({ userId: 'counter-user', content: 'b', embedding: unitVector(51), importance: 0.5, sourceSite: 'test' }); + await repo.storeMemory({ userId: 'other-user', content: 'c', embedding: unitVector(52), importance: 0.5, sourceSite: 'test' }); const count = await repo.countMemories('counter-user'); expect(count).toBe(2); diff --git a/src/db/__tests__/temporal-neighbors.test.ts b/src/db/__tests__/temporal-neighbors.test.ts index bf41607..a5f2a70 100644 --- a/src/db/__tests__/temporal-neighbors.test.ts +++ b/src/db/__tests__/temporal-neighbors.test.ts @@ -8,11 +8,12 @@ import { describe, it, expect, afterAll } from 'vitest'; import { pool } from '../pool.js'; import { storeMemory, deleteAll } from '../repository-write.js'; import { findTemporalNeighbors } from '../repository-read.js'; +import { config } from '../../config.js'; const TEST_USER = 'temporal-test-user'; function makeEmbedding(index: number): number[] { - return Array.from({ length: 1536 }, (_, i) => (i === index ? 1 : 0)); + return Array.from({ length: config.embeddingDimensions }, (_, i) => (i === index ? 1 : 0)); } async function seedWithTimestamps(): Promise<{ ids: string[]; timestamps: Date[] }> { diff --git a/src/routes/memories.ts b/src/routes/memories.ts index 96da138..10bdc7d 100644 --- a/src/routes/memories.ts +++ b/src/routes/memories.ts @@ -12,7 +12,12 @@ import { InputError, handleRouteError } from './route-errors.js'; const MAX_SEARCH_LIMIT = 100; const MAX_CONVERSATION_LENGTH = 100_000; -const ALLOWED_ORIGINS = new Set(['http://localhost:3050', 'http://localhost:3081']); +const ALLOWED_ORIGINS = new Set( + (process.env.ALLOWED_ORIGINS ?? 'http://localhost:3050,http://localhost:3081') + .split(',') + .map((o) => o.trim()) + .filter(Boolean), +); export function createMemoryRouter(service: MemoryService): Router { const router = Router(); diff --git a/src/server.ts b/src/server.ts index aecd363..e6f0c59 100644 --- a/src/server.ts +++ b/src/server.ts @@ -42,9 +42,42 @@ app.get('/health', (_req, res) => { res.json({ status: 'ok' }); }); -app.listen(config.port, () => { - console.log(`AtomicMemory Core running on http://localhost:${config.port}`); -}); +/** + * Verify DB embedding column dimensions match config before accepting traffic. + * Catches the "expected N dimensions, not M" class of errors at startup. + */ +async function checkEmbeddingDimensions(): Promise { + const { rows } = await pool.query<{ typmod: number }>( + `SELECT atttypmod AS typmod + FROM pg_attribute a + JOIN pg_class c ON a.attrelid = c.oid + WHERE c.relname = 'memories' AND a.attname = 'embedding'`, + ); + if (rows.length === 0) { + console.error('[startup] memories.embedding column not found — run npm run migrate first'); + process.exit(1); + } + const dbDims = rows[0].typmod > 0 ? rows[0].typmod : null; + if (dbDims !== null && dbDims !== config.embeddingDimensions) { + console.error( + `[startup] FATAL: DB vector column is ${dbDims} dimensions but EMBEDDING_DIMENSIONS=${config.embeddingDimensions}.\n` + + ` Fix: set EMBEDDING_DIMENSIONS=${dbDims} or run 'npm run migrate' to recreate the schema.`, + ); + process.exit(1); + } + console.log(`[startup] Embedding dimensions OK: config=${config.embeddingDimensions}, DB=${dbDims ?? 'unset'}`); +} + +checkEmbeddingDimensions() + .then(() => { + app.listen(config.port, () => { + console.log(`AtomicMemory Core running on http://localhost:${config.port}`); + }); + }) + .catch((err) => { + console.error('[startup] Embedding dimension check failed:', err); + process.exit(1); + }); process.on('uncaughtException', (err) => { console.error('[FATAL] Uncaught exception:', err); diff --git a/src/services/__tests__/embedding-cache.test.ts b/src/services/__tests__/embedding-cache.test.ts index c7eb2c5..712313d 100644 --- a/src/services/__tests__/embedding-cache.test.ts +++ b/src/services/__tests__/embedding-cache.test.ts @@ -9,7 +9,7 @@ vi.mock('../../config.js', () => ({ config: { embeddingProvider: 'openai', embeddingModel: 'text-embedding-3-small', - embeddingDimensions: 1536, + embeddingDimensions: 1024, embeddingApiUrl: undefined, ollamaBaseUrl: 'http://localhost:11434', openaiApiKey: 'test-key', diff --git a/src/services/__tests__/lesson-service.test.ts b/src/services/__tests__/lesson-service.test.ts index 154b2e2..56ccba0 100644 --- a/src/services/__tests__/lesson-service.test.ts +++ b/src/services/__tests__/lesson-service.test.ts @@ -6,7 +6,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; vi.mock('../embedding.js', () => ({ - embedText: vi.fn().mockResolvedValue(Array(1536).fill(0)), + embedText: vi.fn().mockResolvedValue(Array(1024).fill(0)), })); vi.mock('../audit-events.js', () => ({ From e33f0d75103b1319f1de95a9827487f0f72168e8 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 15 Apr 2026 15:44:29 -0700 Subject: [PATCH 3/7] chore: version 1.0.0 for initial public release Co-Authored-By: Claude Opus 4.6 (1M context) --- CHANGELOG.md | 2 +- README.md | 2 -- package.json | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f962e3..ea09675 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] -## [0.1.0-alpha] - 2026-04-15 +## [1.0.0] - 2026-04-15 ### Added - Initial extraction from atomicmemory-research prototype diff --git a/README.md b/README.md index 5a9fa32..89509bd 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,6 @@ Open-source memory engine for AI applications and agents. Docker-deployable memory backend with durable context, semantic retrieval, and memory mutation (AUDN: Add, Update, Delete, No-op). -> **Alpha** — This is an early release. The API surface is evolving. See the [changelog](CHANGELOG.md) for what's included. - ## Features - **Semantic ingest** — extract structured facts from conversations with contradiction detection diff --git a/package.json b/package.json index 832f482..af9ae88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@atomicmemory/atomicmemory-engine", - "version": "0.1.0-alpha", + "version": "1.0.0", "description": "Open-source memory engine for AI applications — semantic retrieval, AUDN mutation, and contradiction-safe claim versioning.", "private": true, "type": "module", From 0a5f77b086e63a41a1eb70bb2599607ef4def1af Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 15 Apr 2026 15:52:03 -0700 Subject: [PATCH 4/7] fix: resolve fallow dead-code issues in CI (no-cache environment) Remove genuinely unused exports (getSchemaSQL, clearPlans, setupTestSchema re-export, generateLegitimateVariations, createSearchPipelineMockContext). Suppress 2 false positives (factPlans, embeddingPlans) consumed via vi.mock hoisting that fallow cannot trace. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/db/__tests__/test-fixtures.ts | 2 +- src/services/__tests__/deterministic-ingest-harness.ts | 6 ++++-- src/services/__tests__/poisoning-dataset.ts | 2 +- src/services/__tests__/test-fixtures.ts | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/db/__tests__/test-fixtures.ts b/src/db/__tests__/test-fixtures.ts index 9424f1f..d114fe7 100644 --- a/src/db/__tests__/test-fixtures.ts +++ b/src/db/__tests__/test-fixtures.ts @@ -68,7 +68,7 @@ export function createServiceTestContext(pool: pg.Pool, hooks: TestLifecycleHook const __dirname = dirname(fileURLToPath(import.meta.url)); /** Read and prepare schema SQL with configured dimensions. */ -export function getSchemaSQL(): string { +function getSchemaSQL(): string { const raw = readFileSync(resolve(__dirname, '../schema.sql'), 'utf-8'); return raw.replace(/\{\{EMBEDDING_DIMENSIONS\}\}/g, String(config.embeddingDimensions)); } diff --git a/src/services/__tests__/deterministic-ingest-harness.ts b/src/services/__tests__/deterministic-ingest-harness.ts index e633249..84afd83 100644 --- a/src/services/__tests__/deterministic-ingest-harness.ts +++ b/src/services/__tests__/deterministic-ingest-harness.ts @@ -12,10 +12,12 @@ import type { AUDNDecision } from '../extraction.js'; import type { FactInput } from '../memory-service-types.js'; import type pg from 'pg'; -export { unitVector, offsetVector, setupTestSchema } from '../../db/__tests__/test-fixtures.js'; +export { unitVector, offsetVector } from '../../db/__tests__/test-fixtures.js'; +// fallow-ignore-next-line unused-export export const factPlans = new Map(); export const decisionPlans = new Map(); +// fallow-ignore-next-line unused-export export const embeddingPlans = new Map(); /** Deterministic cachedResolveAUDN implementation — shared by mock factory and wire fn. */ @@ -29,7 +31,7 @@ async function resolveAudnFromPlan(factText: string, candidates: Array<{ id: str } /** Clear all plan maps — call in beforeEach. */ -export function clearPlans(): void { +function clearPlans(): void { factPlans.clear(); decisionPlans.clear(); embeddingPlans.clear(); diff --git a/src/services/__tests__/poisoning-dataset.ts b/src/services/__tests__/poisoning-dataset.ts index 822fec0..1e3bae1 100644 --- a/src/services/__tests__/poisoning-dataset.ts +++ b/src/services/__tests__/poisoning-dataset.ts @@ -283,7 +283,7 @@ export const LEGITIMATE_ENTRIES: PoisoningEntry[] = [ * of the hand-crafted entries. Each variation changes specific details * while keeping the same structure. */ -export function generateLegitimateVariations(): PoisoningEntry[] { +function generateLegitimateVariations(): PoisoningEntry[] { const languages = ['Python', 'Rust', 'Go', 'Java', 'C#', 'Ruby', 'Swift', 'Kotlin', 'Scala', 'Elixir']; const editors = ['Neovim', 'IntelliJ', 'Sublime Text', 'Emacs', 'WebStorm', 'Zed', 'Helix']; const frameworks = ['Next.js', 'Svelte', 'Angular', 'Solid', 'Remix', 'Astro', 'Nuxt']; diff --git a/src/services/__tests__/test-fixtures.ts b/src/services/__tests__/test-fixtures.ts index 1ccc2d6..1e30956 100644 --- a/src/services/__tests__/test-fixtures.ts +++ b/src/services/__tests__/test-fixtures.ts @@ -146,7 +146,7 @@ export function createDecisionFactory( * stale retrieval). Returns the fake repo, claims, trace, service, and a * beforeEach callback that resets all mocks. */ -export function createSearchPipelineMockContext(mocks: { +function createSearchPipelineMockContext(mocks: { mockRunSearchPipelineWithTrace: import('vitest').Mock; mockTouchMemory: import('vitest').Mock; mockGetMemory: import('vitest').Mock; From 7b501ee6a08533d6b3e65d8975d40f733b192ac9 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 15 Apr 2026 15:53:08 -0700 Subject: [PATCH 5/7] chore: use fallow --no-cache in CI and pre-commit docs Prevents incremental cache from masking dead-code issues that surface in CI's clean environment. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 2 +- CLAUDE.md | 2 +- CONTRIBUTING.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c22fa7b..9ea20ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: run: npx tsc --noEmit - name: Code health (fallow) - run: npx fallow + run: npx fallow --no-cache - name: Run tests run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67e89bf..eb5ce75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,7 +45,7 @@ jobs: run: npx tsc --noEmit - name: Code health (fallow) - run: npx fallow + run: npx fallow --no-cache - name: Run tests run: npm test diff --git a/CLAUDE.md b/CLAUDE.md index a0f806f..77681ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,7 +63,7 @@ Before committing any work: 1. `npx tsc --noEmit` — type-check passes 2. `npm test` — all tests pass (requires Postgres via .env.test) -3. `fallow` — zero issues (dead code, duplication, complexity). Use `fallow fix --dry-run` to preview, `fallow fix --yes` to apply. Fix remaining issues manually. +3. `fallow --no-cache` — zero issues (dead code, duplication, complexity). Always use `--no-cache` before committing to match CI behavior. Use `fallow fix --dry-run` to preview, `fallow fix --yes` to apply. Fix remaining issues manually. ### Running Tests - Full suite: `npm test` (requires DATABASE_URL in .env.test pointing to Postgres with pgvector) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c84675d..27af0b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ Run these before opening a PR — they match what CI runs: ```bash npx tsc --noEmit # Type check npm test # All tests pass -fallow # Code health (zero issues required) +fallow --no-cache # Code health (zero issues required) ``` ## Branch and Commit Conventions From 9dd24c4d21409fef7182f90da5f8b56675187fb6 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 15 Apr 2026 15:55:19 -0700 Subject: [PATCH 6/7] fix: add required env vars to CI workflow Tests require OPENAI_API_KEY, EMBEDDING_DIMENSIONS, and PORT via dotenv -e .env.test, but .env.test is gitignored. Add placeholder values directly to the CI env block. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ea20ae..351c186 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,9 @@ jobs: env: DATABASE_URL: postgresql://atomicmem:atomicmem@localhost:5432/atomicmem_test + OPENAI_API_KEY: test-placeholder + EMBEDDING_DIMENSIONS: 1024 + PORT: 3051 steps: - uses: actions/checkout@v4 From 5192c8d69fbc477b8ad2d03eebdf450ebc03c7c7 Mon Sep 17 00:00:00 2001 From: Ethan Date: Wed, 15 Apr 2026 15:57:11 -0700 Subject: [PATCH 7/7] docs: add .env.test.example for contributor setup Co-Authored-By: Claude Opus 4.6 (1M context) --- .env.test.example | 8 ++++++++ CONTRIBUTING.md | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 .env.test.example diff --git a/.env.test.example b/.env.test.example new file mode 100644 index 0000000..dc275fd --- /dev/null +++ b/.env.test.example @@ -0,0 +1,8 @@ +# Test environment — copy to .env.test and adjust as needed. +# Requires a Postgres instance with pgvector extension. +# Use `docker compose up postgres -d` to start one locally. + +DATABASE_URL=postgresql://supermem:supermem@localhost:5433/supermem +OPENAI_API_KEY=test-placeholder +EMBEDDING_DIMENSIONS=1024 +PORT=3051 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 27af0b3..7545f8e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,11 +15,11 @@ git clone https://github.com/atomicmemory/Atomicmemory-core.git cd Atomicmemory-core npm install -# Configure test database +# Configure test environment cp .env.test.example .env.test -# Edit .env.test with your Postgres connection string +# Edit .env.test if your Postgres is on a different host/port -# Or use Docker for Postgres: +# Start Postgres with pgvector (if you don't have one running): docker compose up postgres -d # Run tests