From 769994c7987412d84a726df49a55133e6993c602 Mon Sep 17 00:00:00 2001 From: gharibyan Date: Thu, 11 Jun 2026 15:54:06 +0400 Subject: [PATCH] Bundle agent-memory as single npm package --- .github/workflows/publish.yml | 49 +++------------ .github/workflows/test.yml | 15 ----- AGENTS.md | 16 ++--- README.md | 23 ++++--- package.json | 2 +- packages/agent-memory/package.json | 10 ++- packages/agent-memory/tsconfig.json | 8 +++ packages/core/README.md | 4 +- packages/core/package.json | 1 + packages/local/README.md | 9 ++- packages/local/package.json | 1 + packages/openai/README.md | 9 ++- packages/openai/package.json | 1 + packages/postgres/README.md | 7 +-- packages/postgres/package.json | 1 + packages/sqlite/README.md | 7 +-- packages/sqlite/package.json | 1 + pnpm-lock.yaml | 24 +++----- scripts/bundle-public-package.mjs | 88 +++++++++++++++++++++++++++ skills/agent-memory/SKILL.md | 16 ++--- test/adapter-packages.test.mjs | 22 ++++--- test/automation.test.mjs | 39 ++++++------ test/package-boundary.test.mjs | 20 +++--- test/project-guidance.test.mjs | 18 +++--- test/repository-hygiene.test.mjs | 2 +- test/single-package-publish.test.mjs | 91 ++++++++++++++++++++++++++++ test/typescript-structure.test.mjs | 22 +++---- 27 files changed, 323 insertions(+), 183 deletions(-) create mode 100644 scripts/bundle-public-package.mjs create mode 100644 test/single-package-publish.test.mjs diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f6f5107..f303e97 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -30,6 +30,15 @@ jobs: cache: pnpm registry-url: https://registry.npmjs.org + - name: Validate npm publish token + run: | + if [ -z "$NODE_AUTH_TOKEN" ]; then + echo "NPM_TOKEN secret is required for npm publishing" + exit 1 + fi + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Install dependencies run: pnpm install --frozen-lockfile @@ -45,49 +54,9 @@ jobs: - name: Test run: pnpm test - - name: Verify core package contents - run: pnpm --filter @agent-memory/core pack --dry-run - - - name: Verify local adapter package contents - run: pnpm --filter @agent-memory/local pack --dry-run - - - name: Verify SQLite adapter package contents - run: pnpm --filter @agent-memory/sqlite pack --dry-run - - - name: Verify Postgres adapter package contents - run: pnpm --filter @agent-memory/postgres pack --dry-run - - - name: Verify OpenAI adapter package contents - run: pnpm --filter @agent-memory/openai pack --dry-run - - name: Verify public package contents run: pnpm --filter agent-memory pack --dry-run - - name: Publish core - run: pnpm --filter @agent-memory/core publish --access public --no-git-checks - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Publish local adapter - run: pnpm --filter @agent-memory/local publish --access public --no-git-checks - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Publish SQLite adapter - run: pnpm --filter @agent-memory/sqlite publish --access public --no-git-checks - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Publish Postgres adapter - run: pnpm --filter @agent-memory/postgres publish --access public --no-git-checks - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Publish OpenAI adapter - run: pnpm --filter @agent-memory/openai publish --access public --no-git-checks - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - name: Publish public package run: pnpm --filter agent-memory publish --access public --no-git-checks env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ad85b1c..16eefbc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,20 +41,5 @@ jobs: - name: Test run: pnpm test - - name: Verify core package contents - run: pnpm --filter @agent-memory/core pack --dry-run - - - name: Verify local adapter package contents - run: pnpm --filter @agent-memory/local pack --dry-run - - - name: Verify SQLite adapter package contents - run: pnpm --filter @agent-memory/sqlite pack --dry-run - - - name: Verify Postgres adapter package contents - run: pnpm --filter @agent-memory/postgres pack --dry-run - - - name: Verify OpenAI adapter package contents - run: pnpm --filter @agent-memory/openai pack --dry-run - - name: Verify public package contents run: pnpm --filter agent-memory pack --dry-run diff --git a/AGENTS.md b/AGENTS.md index 349d4ae..0122892 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,12 +4,12 @@ This repo is a TypeScript-first pnpm workspace for the `agent-memory` SDK. -- `packages/core` (`@agent-memory/core`) is runtime-neutral. Do not add Node `fs`, provider SDKs, or database drivers here. -- `packages/local` (`@agent-memory/local`) owns local `.memory/memory.json` persistence. -- `packages/sqlite` (`@agent-memory/sqlite`) owns real SQLite `.memory/memory.sqlite` persistence. -- `packages/postgres` (`@agent-memory/postgres`) owns Postgres persistence, automatic migrations, and pgvector search. -- `packages/openai` (`@agent-memory/openai`) owns OpenAI model calls through the official `openai` SDK, plus compatible custom endpoint support. -- `packages/agent-memory` is the public convenience package. It should keep `createAgent({ model })` easy and automatic. +- `packages/core` is the private runtime-neutral workspace package. Do not add Node `fs`, provider SDKs, or database drivers here. +- `packages/local` is the private workspace package for local `.memory/memory.json` persistence. +- `packages/sqlite` is the private workspace package for real SQLite `.memory/memory.sqlite` persistence. +- `packages/postgres` is the private workspace package for Postgres persistence, automatic migrations, and pgvector search. +- `packages/openai` is the private workspace package for OpenAI model calls through the official `openai` SDK, plus compatible custom endpoint support. +- `packages/agent-memory` is the only public npm package. It should keep `createAgent({ model })` easy and automatic and bundle private workspace package output into `dist/internal`. - `apps/playground` is private and must never ship in npm packages. ## Development Rules @@ -17,7 +17,7 @@ This repo is a TypeScript-first pnpm workspace for the `agent-memory` SDK. - Keep source in TypeScript under `src`; generated output belongs in `dist`. - Add tests before changing SDK behavior. - Keep package build scripts cleaning `dist` before `tsc` so stale artifacts do not publish. -- Default memory should be automatic in `agent-memory`, but direct `@agent-memory/core` usage should stay adapter-neutral. +- Default memory should be automatic in `agent-memory`, but core internals should stay adapter-neutral. - Major first-party model provider adapters should use official provider SDKs. Compatibility wrappers are for custom OpenAI-compatible endpoints. - Keep `sqliteMemory()` backed by a real SQLite database file, not JSON. - Keep `postgresMemory()` responsible for running its own versioned migrations before the first database operation by default. @@ -33,4 +33,4 @@ pnpm lint pnpm pack:check ``` -Package dry-runs must not include `apps/playground`, `.memory`, `.ai-memory`, generated tarballs, screenshots, logs, or local databases. +Package dry-runs must only target `agent-memory` and must not include `apps/playground`, `.memory`, `.ai-memory`, generated tarballs, screenshots, logs, or local databases. diff --git a/README.md b/README.md index e4fc57b..f1f7f4e 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ The Postgres adapter runs migrations automatically before the first memory opera ## Model Providers -First-party provider integrations should use official SDKs. The OpenAI adapter depends on the official `openai` TypeScript SDK: +First-party provider integrations are exported from `agent-memory` and use provider SDKs or documented provider client paths internally. The OpenAI adapter depends on the official `openai` TypeScript SDK: ```ts import { openai } from "agent-memory" @@ -121,14 +121,19 @@ const model = openAICompatible({ }) ``` -## Packages +## Package -- `agent-memory`: public convenience package with automatic local memory defaults. -- `@agent-memory/core`: runtime-neutral engine, contracts, compiler, retrieval, and in-memory store. -- `@agent-memory/local`: local JSON persistence adapter. -- `@agent-memory/sqlite`: real SQLite persistence adapter. -- `@agent-memory/postgres`: Postgres persistence adapter with pgvector migrations. -- `@agent-memory/openai`: OpenAI official SDK adapter, plus OpenAI-compatible custom endpoint support. +`agent-memory` is the only public npm package. It bundles the runtime, storage adapters, and model provider adapters behind one install and one import surface. + +The repository still keeps implementation boundaries under `packages/*`: + +- `packages/core`: runtime-neutral engine, contracts, compiler, retrieval, and in-memory store. +- `packages/local`: local JSON persistence adapter. +- `packages/sqlite`: real SQLite persistence adapter. +- `packages/postgres`: Postgres persistence adapter with pgvector migrations. +- `packages/openai`: OpenAI official SDK adapter, plus OpenAI-compatible custom endpoint support. + +Those workspace packages are private build units. They are compiled into `agent-memory/dist/internal/*` during the public package build and are not published separately. ## Playground @@ -147,7 +152,7 @@ pnpm lint pnpm pack:check ``` -Package dry-runs must not include `apps/playground`, `.memory`, local databases, logs, screenshots, or generated tarballs. +Package dry-runs must only publish the `agent-memory` artifact and must not include `apps/playground`, `.memory`, local databases, logs, screenshots, or generated tarballs. ## License diff --git a/package.json b/package.json index 331a7ed..d290d78 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "lint": "eslint .", "lint:fix": "eslint . --fix", "test": "pnpm build && node --test packages/agent-memory/test/*.test.mjs test/*.test.mjs", - "pack:check": "pnpm --filter @agent-memory/core pack --dry-run && pnpm --filter @agent-memory/local pack --dry-run && pnpm --filter @agent-memory/sqlite pack --dry-run && pnpm --filter @agent-memory/postgres pack --dry-run && pnpm --filter @agent-memory/openai pack --dry-run && pnpm --filter agent-memory pack --dry-run", + "pack:check": "pnpm --filter agent-memory pack --dry-run", "version:from-tag": "node scripts/sync-package-version-from-tag.mjs" }, "devDependencies": { diff --git a/packages/agent-memory/package.json b/packages/agent-memory/package.json index d40af73..9641afd 100644 --- a/packages/agent-memory/package.json +++ b/packages/agent-memory/package.json @@ -27,15 +27,13 @@ "package.json" ], "scripts": { - "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json", + "build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.json && node ../../scripts/bundle-public-package.mjs", "test": "pnpm build && node --test test/*.test.mjs" }, "dependencies": { - "@agent-memory/core": "workspace:*", - "@agent-memory/local": "workspace:*", - "@agent-memory/openai": "workspace:*", - "@agent-memory/postgres": "workspace:*", - "@agent-memory/sqlite": "workspace:*" + "openai": "^6.10.0", + "pg": "^8.16.3", + "sql.js": "^1.13.0" }, "engines": { "node": ">=20.12" diff --git a/packages/agent-memory/tsconfig.json b/packages/agent-memory/tsconfig.json index 40963af..5e4a2db 100644 --- a/packages/agent-memory/tsconfig.json +++ b/packages/agent-memory/tsconfig.json @@ -1,8 +1,16 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "baseUrl": "../..", "rootDir": "src", "outDir": "dist", + "paths": { + "@agent-memory/core": ["packages/core/dist/index.d.ts"], + "@agent-memory/local": ["packages/local/dist/index.d.ts"], + "@agent-memory/openai": ["packages/openai/dist/index.d.ts"], + "@agent-memory/postgres": ["packages/postgres/dist/index.d.ts"], + "@agent-memory/sqlite": ["packages/sqlite/dist/index.d.ts"] + }, "tsBuildInfoFile": "dist/.tsbuildinfo" }, "include": ["src/**/*.ts"] diff --git a/packages/core/README.md b/packages/core/README.md index 32331b8..8ee7783 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,5 +1,5 @@ -# @agent-memory/core +# packages/core -Core provider-neutral runtime for `agent-memory`. +Private provider-neutral runtime implementation for `agent-memory`. Most users should install and import from `agent-memory`. diff --git a/packages/core/package.json b/packages/core/package.json index 3d23a32..c440638 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,7 @@ { "name": "@agent-memory/core", "version": "0.0.0", + "private": true, "description": "Core provider-neutral agent memory runtime.", "type": "module", "author": "Gharibyan", diff --git a/packages/local/README.md b/packages/local/README.md index 7be47ba..e0dabdc 100644 --- a/packages/local/README.md +++ b/packages/local/README.md @@ -1,10 +1,9 @@ -# @agent-memory/local +# packages/local -Local persistent memory adapter for `agent-memory`. +Private local persistent memory implementation for `agent-memory`. ```ts -import { createAgent } from "@agent-memory/core" -import { localMemory } from "@agent-memory/local" +import { createAgent, localMemory } from "agent-memory" const agent = createAgent({ model, @@ -12,4 +11,4 @@ const agent = createAgent({ }) ``` -By default, local memory persists to `.memory/memory.json` in the current working directory. This adapter is meant for local development, prototypes, and single-node apps. Use `@agent-memory/sqlite` when you need a real SQLite database file. +By default, local memory persists to `.memory/memory.json` in the current working directory. This adapter is meant for local development, prototypes, and single-node apps. Use `sqliteMemory()` from `agent-memory` when you need a real SQLite database file. diff --git a/packages/local/package.json b/packages/local/package.json index 822adb6..bd08531 100644 --- a/packages/local/package.json +++ b/packages/local/package.json @@ -1,6 +1,7 @@ { "name": "@agent-memory/local", "version": "0.0.0", + "private": true, "description": "Local persistent memory adapter for agent-memory.", "type": "module", "author": "Gharibyan", diff --git a/packages/openai/README.md b/packages/openai/README.md index 921a185..e9fff2a 100644 --- a/packages/openai/README.md +++ b/packages/openai/README.md @@ -1,10 +1,9 @@ -# @agent-memory/openai +# packages/openai -OpenAI model provider adapter for `agent-memory`, backed by the official `openai` TypeScript SDK. +Private OpenAI model provider implementation for `agent-memory`, backed by the official `openai` TypeScript SDK. ```ts -import { createAgent } from "agent-memory" -import { openai } from "@agent-memory/openai" +import { createAgent, openai } from "agent-memory" const agent = createAgent({ model: openai("gpt-5") @@ -14,7 +13,7 @@ const agent = createAgent({ For custom models or providers that expose an OpenAI-compatible chat completions API, use `openAICompatible()`: ```ts -import { openAICompatible } from "@agent-memory/openai" +import { openAICompatible } from "agent-memory" const model = openAICompatible({ model: "deepseek-chat", diff --git a/packages/openai/package.json b/packages/openai/package.json index f89b740..039a3b6 100644 --- a/packages/openai/package.json +++ b/packages/openai/package.json @@ -1,6 +1,7 @@ { "name": "@agent-memory/openai", "version": "0.0.0", + "private": true, "description": "OpenAI official SDK model provider adapter for agent-memory.", "type": "module", "author": "Gharibyan", diff --git a/packages/postgres/README.md b/packages/postgres/README.md index 3d5a980..40f12ca 100644 --- a/packages/postgres/README.md +++ b/packages/postgres/README.md @@ -1,10 +1,9 @@ -# @agent-memory/postgres +# packages/postgres -Postgres persistence adapter for `agent-memory` with automatic migrations and pgvector retrieval. +Private Postgres persistence implementation for `agent-memory` with automatic migrations and pgvector retrieval. ```ts -import { createAgent } from "agent-memory" -import { postgresMemory } from "@agent-memory/postgres" +import { createAgent, postgresMemory } from "agent-memory" const agent = createAgent({ model, diff --git a/packages/postgres/package.json b/packages/postgres/package.json index 2812aeb..5176869 100644 --- a/packages/postgres/package.json +++ b/packages/postgres/package.json @@ -1,6 +1,7 @@ { "name": "@agent-memory/postgres", "version": "0.0.0", + "private": true, "description": "Postgres and pgvector persistent memory adapter for agent-memory.", "type": "module", "author": "Gharibyan", diff --git a/packages/sqlite/README.md b/packages/sqlite/README.md index 0d97ef2..9d79814 100644 --- a/packages/sqlite/README.md +++ b/packages/sqlite/README.md @@ -1,10 +1,9 @@ -# @agent-memory/sqlite +# packages/sqlite -SQLite persistent memory adapter for `agent-memory`. +Private SQLite persistent memory implementation for `agent-memory`. ```ts -import { createAgent, openai } from "agent-memory" -import { sqliteMemory } from "@agent-memory/sqlite" +import { createAgent, openai, sqliteMemory } from "agent-memory" const agent = createAgent({ model: openai("gpt-5"), diff --git a/packages/sqlite/package.json b/packages/sqlite/package.json index f585001..a4988a8 100644 --- a/packages/sqlite/package.json +++ b/packages/sqlite/package.json @@ -1,6 +1,7 @@ { "name": "@agent-memory/sqlite", "version": "0.0.0", + "private": true, "description": "SQLite persistent memory adapter for agent-memory.", "type": "module", "author": "Gharibyan", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 716e611..a6c3d69 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,21 +32,15 @@ importers: packages/agent-memory: dependencies: - '@agent-memory/core': - specifier: workspace:* - version: link:../core - '@agent-memory/local': - specifier: workspace:* - version: link:../local - '@agent-memory/openai': - specifier: workspace:* - version: link:../openai - '@agent-memory/postgres': - specifier: workspace:* - version: link:../postgres - '@agent-memory/sqlite': - specifier: workspace:* - version: link:../sqlite + openai: + specifier: ^6.10.0 + version: 6.42.0 + pg: + specifier: ^8.16.3 + version: 8.21.0 + sql.js: + specifier: ^1.13.0 + version: 1.14.1 packages/core: {} diff --git a/scripts/bundle-public-package.mjs b/scripts/bundle-public-package.mjs new file mode 100644 index 0000000..dcd013a --- /dev/null +++ b/scripts/bundle-public-package.mjs @@ -0,0 +1,88 @@ +import { access, cp, readdir, readFile, writeFile } from "node:fs/promises" +import { dirname, relative, resolve, sep } from "node:path" +import { fileURLToPath, pathToFileURL } from "node:url" + +const firstPartyPackages = [ + { dir: "core", name: "@agent-memory/core" }, + { dir: "local", name: "@agent-memory/local" }, + { dir: "openai", name: "@agent-memory/openai" }, + { dir: "postgres", name: "@agent-memory/postgres" }, + { dir: "sqlite", name: "@agent-memory/sqlite" } +] + +const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..") +const publicDistDir = resolve(rootDir, "packages", "agent-memory", "dist") +const internalDistDir = resolve(publicDistDir, "internal") + +export async function bundlePublicPackage() { + await assertBuiltPublicPackage() + + for (const packageInfo of firstPartyPackages) { + await copyInternalPackage(packageInfo.dir) + } + + await rewriteFirstPartySpecifiers(publicDistDir) +} + +async function assertBuiltPublicPackage() { + const entrypoint = resolve(publicDistDir, "index.js") + + try { + await access(entrypoint) + } catch { + throw new Error("Expected packages/agent-memory/dist/index.js. Run the public package TypeScript build first.") + } +} + +async function copyInternalPackage(dir) { + const sourceDir = resolve(rootDir, "packages", dir, "dist") + const destinationDir = resolve(internalDistDir, dir) + + try { + await access(resolve(sourceDir, "index.js")) + await access(resolve(sourceDir, "index.d.ts")) + } catch { + throw new Error(`Expected packages/${dir}/dist to exist before bundling agent-memory. Run pnpm build from the repo root.`) + } + + await cp(sourceDir, destinationDir, { + recursive: true, + filter: (source) => !source.endsWith(".tsbuildinfo") + }) +} + +async function rewriteFirstPartySpecifiers(dir) { + const files = await listFiles(dir) + const rewritableFiles = files.filter((file) => file.endsWith(".js") || file.endsWith(".d.ts")) + + for (const file of rewritableFiles) { + let content = await readFile(file, "utf8") + + for (const packageInfo of firstPartyPackages) { + const internalEntrypoint = resolve(internalDistDir, packageInfo.dir, "index.js") + const replacement = toModuleSpecifier(relative(dirname(file), internalEntrypoint)) + + content = content.split(packageInfo.name).join(replacement) + } + + await writeFile(file, content) + } +} + +async function listFiles(dir) { + const entries = await readdir(dir, { recursive: true, withFileTypes: true }) + + return entries + .filter((entry) => entry.isFile()) + .map((entry) => resolve(entry.parentPath, entry.name)) +} + +function toModuleSpecifier(relativePath) { + const normalized = relativePath.split(sep).join("/") + + return normalized.startsWith(".") ? normalized : `./${normalized}` +} + +if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { + await bundlePublicPackage() +} diff --git a/skills/agent-memory/SKILL.md b/skills/agent-memory/SKILL.md index 5ce4725..bdcea80 100644 --- a/skills/agent-memory/SKILL.md +++ b/skills/agent-memory/SKILL.md @@ -20,12 +20,12 @@ Use this when: ## Package Boundaries -- `@agent-memory/core`: runtime-neutral engine, contracts, compiler, retrieval, in-memory store. No Node `fs`, provider SDKs, or database drivers. -- `@agent-memory/local`: local `.memory/memory.json` persistence. -- `@agent-memory/sqlite`: real SQLite `.memory/memory.sqlite` persistence. -- `@agent-memory/postgres`: Postgres persistence with automatic migrations and pgvector retrieval. -- `@agent-memory/openai`: OpenAI chat completions through the official `openai` SDK, including custom `baseURL` providers for compatible endpoints. -- `agent-memory`: public convenience package. `createAgent({ model })` should work with automatic local memory. +- `packages/core`: private runtime-neutral engine, contracts, compiler, retrieval, in-memory store. No Node `fs`, provider SDKs, or database drivers. +- `packages/local`: private local `.memory/memory.json` persistence. +- `packages/sqlite`: private real SQLite `.memory/memory.sqlite` persistence. +- `packages/postgres`: private Postgres persistence with automatic migrations and pgvector retrieval. +- `packages/openai`: private OpenAI chat completions through the official `openai` SDK, including custom `baseURL` providers for compatible endpoints. +- `agent-memory`: the only public npm package. `createAgent({ model })` should work with automatic local memory and should bundle private workspace package output into `dist/internal`. ## Basic Usage @@ -54,9 +54,9 @@ const result = await agent.generate({ ## Extending -For a provider adapter, create a package like `@agent-memory/openai` and return a `ModelProvider`. Major first-party provider packages should depend on the provider's official SDK; OpenAI-compatible wrappers are for custom model endpoints. +For a provider adapter, add or extend a private workspace package under `packages/*` and return a `ModelProvider`. Major first-party provider packages should depend on the provider's official SDK when one exists; OpenAI-compatible wrappers are for custom model endpoints and documented compatible providers. -For a storage adapter, create a package like `@agent-memory/local` and implement `MemoryStore`. Database adapters should own their migration lifecycle instead of making application code run setup manually. +For a storage adapter, add or extend a private workspace package under `packages/*` and implement `MemoryStore`. Database adapters should own their migration lifecycle instead of making application code run setup manually. Keep core dependency-free and runtime-neutral. diff --git a/test/adapter-packages.test.mjs b/test/adapter-packages.test.mjs index fe2cd83..6fb450d 100644 --- a/test/adapter-packages.test.mjs +++ b/test/adapter-packages.test.mjs @@ -12,12 +12,13 @@ async function readJson(path) { return JSON.parse(await read(path)) } -test("openai-compatible provider lives in its own publishable adapter package", async () => { +test("openai-compatible provider lives in its own internal adapter package", async () => { const packageJson = await readJson("packages/openai/package.json") const source = await read("packages/openai/src/index.ts") const coreIndex = await read("packages/core/src/index.ts") assert.equal(packageJson.name, "@agent-memory/openai") + assert.equal(packageJson.private, true) assert.equal(packageJson.main, "./dist/index.js") assert.equal(packageJson.types, "./dist/index.d.ts") assert.deepEqual(packageJson.files, ["dist", "README.md", "package.json"]) @@ -32,12 +33,13 @@ test("openai-compatible provider lives in its own publishable adapter package", assert.doesNotMatch(coreIndex, /openai/) }) -test("local persistence lives in its own publishable storage adapter package", async () => { +test("local persistence lives in its own internal storage adapter package", async () => { const packageJson = await readJson("packages/local/package.json") const source = await read("packages/local/src/index.ts") const coreIndex = await read("packages/core/src/index.ts") assert.equal(packageJson.name, "@agent-memory/local") + assert.equal(packageJson.private, true) assert.equal(packageJson.main, "./dist/index.js") assert.equal(packageJson.types, "./dist/index.d.ts") assert.deepEqual(packageJson.files, ["dist", "README.md", "package.json"]) @@ -48,12 +50,13 @@ test("local persistence lives in its own publishable storage adapter package", a assert.doesNotMatch(coreIndex, /sqliteMemory/) }) -test("sqlite persistence lives in its own publishable storage adapter package", async () => { +test("sqlite persistence lives in its own internal storage adapter package", async () => { const packageJson = await readJson("packages/sqlite/package.json") const source = await read("packages/sqlite/src/index.ts") const coreIndex = await read("packages/core/src/index.ts") assert.equal(packageJson.name, "@agent-memory/sqlite") + assert.equal(packageJson.private, true) assert.equal(packageJson.main, "./dist/index.js") assert.equal(packageJson.types, "./dist/index.d.ts") assert.deepEqual(packageJson.files, ["dist", "README.md", "package.json"]) @@ -65,12 +68,13 @@ test("sqlite persistence lives in its own publishable storage adapter package", assert.doesNotMatch(coreIndex, /sqliteMemory/) }) -test("postgres persistence lives in its own publishable storage adapter package", async () => { +test("postgres persistence lives in its own internal storage adapter package", async () => { const packageJson = await readJson("packages/postgres/package.json") const source = await read("packages/postgres/src/index.ts") const coreIndex = await read("packages/core/src/index.ts") assert.equal(packageJson.name, "@agent-memory/postgres") + assert.equal(packageJson.private, true) assert.equal(packageJson.main, "./dist/index.js") assert.equal(packageJson.types, "./dist/index.d.ts") assert.deepEqual(packageJson.files, ["dist", "README.md", "package.json"]) @@ -83,14 +87,14 @@ test("postgres persistence lives in its own publishable storage adapter package" assert.doesNotMatch(coreIndex, /postgresMemory/) }) -test("public package re-exports the openai adapter as a convenience import", async () => { +test("public package re-exports internal adapters as convenience imports", async () => { const packageJson = await readJson("packages/agent-memory/package.json") const source = await read("packages/agent-memory/src/index.ts") - assert.equal(packageJson.dependencies["@agent-memory/openai"], "workspace:*") - assert.equal(packageJson.dependencies["@agent-memory/local"], "workspace:*") - assert.equal(packageJson.dependencies["@agent-memory/sqlite"], "workspace:*") - assert.equal(packageJson.dependencies["@agent-memory/postgres"], "workspace:*") + assert.equal(Object.keys(packageJson.dependencies).some((name) => name.startsWith("@agent-memory/")), false) + assert.match(packageJson.dependencies.openai, /^\^/) + assert.match(packageJson.dependencies.pg, /^\^/) + assert.match(packageJson.dependencies["sql.js"], /^\^/) assert.match(source, /export \* from "@agent-memory\/core"/) assert.match(source, /export \{ openAICompatible, openai \} from "@agent-memory\/openai"/) assert.match(source, /export \{ localMemory \} from "@agent-memory\/local"/) diff --git a/test/automation.test.mjs b/test/automation.test.mjs index 701ea0f..e3cc653 100644 --- a/test/automation.test.mjs +++ b/test/automation.test.mjs @@ -24,12 +24,8 @@ test("github ci runs lint, tests, and package boundary check", async () => { assert.match(workflow, /pnpm build/) assert.match(workflow, /pnpm test/) assert.match(workflow, /pnpm lint/) - assert.match(workflow, /pnpm --filter @agent-memory\/core pack --dry-run/) - assert.match(workflow, /pnpm --filter @agent-memory\/local pack --dry-run/) - assert.match(workflow, /pnpm --filter @agent-memory\/sqlite pack --dry-run/) - assert.match(workflow, /pnpm --filter @agent-memory\/postgres pack --dry-run/) - assert.match(workflow, /pnpm --filter @agent-memory\/openai pack --dry-run/) assert.match(workflow, /pnpm --filter agent-memory pack --dry-run/) + assert.doesNotMatch(workflow, /pnpm --filter @agent-memory\/.* pack --dry-run/) }) test("github publish workflow is tag gated and syncs package version from tag", async () => { @@ -39,14 +35,13 @@ test("github publish workflow is tag gated and syncs package version from tag", assert.match(workflow, /'v\*'/) assert.match(workflow, /NODE_AUTH_TOKEN/) assert.match(workflow, /NPM_TOKEN/) + assert.match(workflow, /Validate npm publish token/) + assert.match(workflow, /NPM_TOKEN secret is required for npm publishing/) assert.match(workflow, /scripts\/sync-package-version-from-tag\.mjs/) assert.match(workflow, /pnpm build/) - assert.match(workflow, /pnpm --filter @agent-memory\/core publish --access public --no-git-checks/) - assert.match(workflow, /pnpm --filter @agent-memory\/local publish --access public --no-git-checks/) - assert.match(workflow, /pnpm --filter @agent-memory\/sqlite publish --access public --no-git-checks/) - assert.match(workflow, /pnpm --filter @agent-memory\/postgres publish --access public --no-git-checks/) - assert.match(workflow, /pnpm --filter @agent-memory\/openai publish --access public --no-git-checks/) + assert.match(workflow, /pnpm --filter agent-memory pack --dry-run/) assert.match(workflow, /pnpm --filter agent-memory publish --access public --no-git-checks/) + assert.doesNotMatch(workflow, /pnpm --filter @agent-memory\/.* publish --access public --no-git-checks/) }) test("root package exposes lint and version sync scripts", async () => { @@ -90,22 +85,27 @@ test("version sync script updates the publishable package from a v-prefixed tag" }, null, 2)) await writeFile(join(corePackageDir, "package.json"), JSON.stringify({ name: "@agent-memory/core", + private: true, version: "0.0.0" }, null, 2)) await writeFile(join(localPackageDir, "package.json"), JSON.stringify({ name: "@agent-memory/local", + private: true, version: "0.0.0" }, null, 2)) await writeFile(join(openaiPackageDir, "package.json"), JSON.stringify({ name: "@agent-memory/openai", + private: true, version: "0.0.0" }, null, 2)) await writeFile(join(postgresPackageDir, "package.json"), JSON.stringify({ name: "@agent-memory/postgres", + private: true, version: "0.0.0" }, null, 2)) await writeFile(join(sqlitePackageDir, "package.json"), JSON.stringify({ name: "@agent-memory/sqlite", + private: true, version: "0.0.0" }, null, 2)) @@ -122,19 +122,14 @@ test("version sync script updates the publishable package from a v-prefixed tag" assert.equal(result.version, "1.2.3") assert.deepEqual(result.updatedPackagePaths.sort(), [ - "packages/agent-memory/package.json", - "packages/core/package.json", - "packages/local/package.json", - "packages/openai/package.json", - "packages/postgres/package.json", - "packages/sqlite/package.json" - ].sort()) + "packages/agent-memory/package.json" + ]) assert.equal(packageJson.version, "1.2.3") - assert.equal(corePackageJson.version, "1.2.3") - assert.equal(localPackageJson.version, "1.2.3") - assert.equal(openaiPackageJson.version, "1.2.3") - assert.equal(postgresPackageJson.version, "1.2.3") - assert.equal(sqlitePackageJson.version, "1.2.3") + assert.equal(corePackageJson.version, "0.0.0") + assert.equal(localPackageJson.version, "0.0.0") + assert.equal(openaiPackageJson.version, "0.0.0") + assert.equal(postgresPackageJson.version, "0.0.0") + assert.equal(sqlitePackageJson.version, "0.0.0") } finally { await rm(tempDir, { recursive: true, force: true }) } diff --git a/test/package-boundary.test.mjs b/test/package-boundary.test.mjs index cbe3be3..99b80f6 100644 --- a/test/package-boundary.test.mjs +++ b/test/package-boundary.test.mjs @@ -40,11 +40,11 @@ test("published agent-memory package uses a restrictive files allowlist", async assert.equal(packageJson.files.includes(".ai-memory"), false) }) -test("published core package uses a restrictive files allowlist", async () => { +test("internal core package uses a restrictive files allowlist", async () => { const packageJson = await readJson("packages/core/package.json") const distFiles = await listFiles("packages/core/dist") - assert.equal(packageJson.private, undefined) + assert.equal(packageJson.private, true) assert.deepEqual(packageJson.files, [ "dist", "README.md", @@ -56,10 +56,10 @@ test("published core package uses a restrictive files allowlist", async () => { assert.equal(distFiles.some((file) => file.includes("local-store")), false) }) -test("published local adapter package uses a restrictive files allowlist", async () => { +test("internal local adapter package uses a restrictive files allowlist", async () => { const packageJson = await readJson("packages/local/package.json") - assert.equal(packageJson.private, undefined) + assert.equal(packageJson.private, true) assert.deepEqual(packageJson.files, [ "dist", "README.md", @@ -70,10 +70,10 @@ test("published local adapter package uses a restrictive files allowlist", async assert.equal(packageJson.files.includes(".ai-memory"), false) }) -test("published sqlite adapter package uses a restrictive files allowlist", async () => { +test("internal sqlite adapter package uses a restrictive files allowlist", async () => { const packageJson = await readJson("packages/sqlite/package.json") - assert.equal(packageJson.private, undefined) + assert.equal(packageJson.private, true) assert.deepEqual(packageJson.files, [ "dist", "README.md", @@ -84,10 +84,10 @@ test("published sqlite adapter package uses a restrictive files allowlist", asyn assert.equal(packageJson.files.includes(".ai-memory"), false) }) -test("published postgres adapter package uses a restrictive files allowlist", async () => { +test("internal postgres adapter package uses a restrictive files allowlist", async () => { const packageJson = await readJson("packages/postgres/package.json") - assert.equal(packageJson.private, undefined) + assert.equal(packageJson.private, true) assert.deepEqual(packageJson.files, [ "dist", "README.md", @@ -98,10 +98,10 @@ test("published postgres adapter package uses a restrictive files allowlist", as assert.equal(packageJson.files.includes(".ai-memory"), false) }) -test("published openai adapter package uses a restrictive files allowlist", async () => { +test("internal openai adapter package uses a restrictive files allowlist", async () => { const packageJson = await readJson("packages/openai/package.json") - assert.equal(packageJson.private, undefined) + assert.equal(packageJson.private, true) assert.deepEqual(packageJson.files, [ "dist", "README.md", diff --git a/test/project-guidance.test.mjs b/test/project-guidance.test.mjs index c0f77d7..d5e5d59 100644 --- a/test/project-guidance.test.mjs +++ b/test/project-guidance.test.mjs @@ -11,10 +11,11 @@ async function read(path) { test("agent guidance file describes package boundaries and verification", async () => { const content = await read("AGENTS.md") - assert.match(content, /@agent-memory\/core/) - assert.match(content, /@agent-memory\/local/) - assert.match(content, /@agent-memory\/sqlite/) - assert.match(content, /@agent-memory\/openai/) + assert.match(content, /packages\/core/) + assert.match(content, /packages\/local/) + assert.match(content, /packages\/sqlite/) + assert.match(content, /packages\/openai/) + assert.match(content, /only public npm package/) assert.match(content, /official `openai` SDK/) assert.match(content, /official provider SDKs/) assert.match(content, /pnpm test/) @@ -30,10 +31,11 @@ test("agent-memory skill captures SDK usage and extension patterns", async () => assert.match(content, /createAgent/) assert.match(content, /operationId/) assert.match(content, /\.memory\/memory\.json/) - assert.match(content, /@agent-memory\/core/) - assert.match(content, /@agent-memory\/local/) - assert.match(content, /@agent-memory\/sqlite/) - assert.match(content, /@agent-memory\/openai/) + assert.match(content, /packages\/core/) + assert.match(content, /packages\/local/) + assert.match(content, /packages\/sqlite/) + assert.match(content, /packages\/openai/) + assert.match(content, /only public npm package/) assert.match(content, /official `openai` SDK/) assert.match(content, /official SDK/) }) diff --git a/test/repository-hygiene.test.mjs b/test/repository-hygiene.test.mjs index 0084482..d60e79c 100644 --- a/test/repository-hygiene.test.mjs +++ b/test/repository-hygiene.test.mjs @@ -35,7 +35,7 @@ test("root README and MIT license are present", async () => { assert.match(license, /Gharibyan/) }) -test("publishable packages point to the gharibyan GitHub repository", async () => { +test("workspace packages point to the gharibyan GitHub repository", async () => { for (const path of [ "package.json", "packages/agent-memory/package.json", diff --git a/test/single-package-publish.test.mjs b/test/single-package-publish.test.mjs new file mode 100644 index 0000000..9fb5b5b --- /dev/null +++ b/test/single-package-publish.test.mjs @@ -0,0 +1,91 @@ +import assert from "node:assert/strict" +import { access, readdir, readFile } from "node:fs/promises" +import { join } from "node:path" +import { test } from "node:test" + +const root = new URL("../", import.meta.url) +const internalPackageDirs = [ + "core", + "local", + "openai", + "postgres", + "sqlite" +] + +async function exists(path) { + try { + await access(new URL(path, root)) + return true + } catch { + return false + } +} + +async function read(path) { + return readFile(new URL(path, root), "utf8") +} + +async function readJson(path) { + return JSON.parse(await read(path)) +} + +async function listFiles(dir) { + const entries = await readdir(new URL(dir, root), { recursive: true, withFileTypes: true }) + return entries + .filter((entry) => entry.isFile()) + .map((entry) => join(entry.parentPath, entry.name)) +} + +test("only agent-memory is publishable from the workspace package set", async () => { + const publicPackage = await readJson("packages/agent-memory/package.json") + + assert.equal(publicPackage.private, undefined) + assert.equal( + Object.keys(publicPackage.dependencies ?? {}).some((name) => name.startsWith("@agent-memory/")), + false + ) + + for (const dir of internalPackageDirs) { + const packageJson = await readJson(`packages/${dir}/package.json`) + + assert.equal(packageJson.private, true, `${packageJson.name} should be an internal workspace package`) + } +}) + +test("agent-memory dist contains bundled first-party internals", async () => { + for (const dir of internalPackageDirs) { + assert.equal( + await exists(`packages/agent-memory/dist/internal/${dir}/index.js`), + true, + `expected packages/agent-memory/dist/internal/${dir}/index.js to be packed with agent-memory` + ) + assert.equal( + await exists(`packages/agent-memory/dist/internal/${dir}/index.d.ts`), + true, + `expected packages/agent-memory/dist/internal/${dir}/index.d.ts to be packed with agent-memory` + ) + } + + const distFiles = await listFiles("packages/agent-memory/dist") + const importableFiles = distFiles.filter((file) => file.endsWith(".js") || file.endsWith(".d.ts")) + const contents = await Promise.all(importableFiles.map((file) => read(file.replace(root.pathname, "")))) + + assert.equal( + contents.some((content) => /from ["']@agent-memory\//.test(content)), + false, + "published dist files must not import unpublished @agent-memory/* packages" + ) +}) + +test("pack and publish automation only target the agent-memory npm package", async () => { + const packageJson = await readJson("package.json") + const testWorkflow = await read(".github/workflows/test.yml") + const publishWorkflow = await read(".github/workflows/publish.yml") + + assert.match(packageJson.scripts["pack:check"], /pnpm --filter agent-memory pack --dry-run/) + assert.doesNotMatch(packageJson.scripts["pack:check"], /@agent-memory\//) + assert.match(testWorkflow, /pnpm --filter agent-memory pack --dry-run/) + assert.doesNotMatch(testWorkflow, /@agent-memory\//) + assert.match(publishWorkflow, /pnpm --filter agent-memory publish --access public --no-git-checks/) + assert.doesNotMatch(publishWorkflow, /@agent-memory\/.* publish/) +}) diff --git a/test/typescript-structure.test.mjs b/test/typescript-structure.test.mjs index 505ee5c..4e071bf 100644 --- a/test/typescript-structure.test.mjs +++ b/test/typescript-structure.test.mjs @@ -47,7 +47,7 @@ test("source implementation is TypeScript-first", async () => { assert.ok(publicSource.some((path) => path.endsWith("index.ts"))) }) -test("package structure separates core runtime, adapters, and public package", async () => { +test("package structure separates internal runtime, adapters, and public package", async () => { const corePackage = await readJson("packages/core/package.json") const localPackage = await readJson("packages/local/package.json") const openaiPackage = await readJson("packages/openai/package.json") @@ -57,6 +57,7 @@ test("package structure separates core runtime, adapters, and public package", a const publicIndex = await read("packages/agent-memory/src/index.ts") assert.equal(corePackage.name, "@agent-memory/core") + assert.equal(corePackage.private, true) assert.equal(corePackage.main, "./dist/index.js") assert.equal(corePackage.types, "./dist/index.d.ts") assert.deepEqual(corePackage.files, ["dist", "README.md", "package.json"]) @@ -64,11 +65,10 @@ test("package structure separates core runtime, adapters, and public package", a assert.equal(publicPackage.name, "agent-memory") assert.equal(publicPackage.main, "./dist/index.js") assert.equal(publicPackage.types, "./dist/index.d.ts") - assert.equal(publicPackage.dependencies["@agent-memory/core"], "workspace:*") - assert.equal(publicPackage.dependencies["@agent-memory/local"], "workspace:*") - assert.equal(publicPackage.dependencies["@agent-memory/openai"], "workspace:*") - assert.equal(publicPackage.dependencies["@agent-memory/postgres"], "workspace:*") - assert.equal(publicPackage.dependencies["@agent-memory/sqlite"], "workspace:*") + assert.equal(Object.keys(publicPackage.dependencies).some((name) => name.startsWith("@agent-memory/")), false) + assert.match(publicPackage.dependencies.openai, /^\^/) + assert.match(publicPackage.dependencies.pg, /^\^/) + assert.match(publicPackage.dependencies["sql.js"], /^\^/) assert.match(publicIndex, /export \* from "@agent-memory\/core"/) assert.match(publicIndex, /export \{ localMemory \} from "@agent-memory\/local"/) assert.match(publicIndex, /export \{ openAICompatible, openai \} from "@agent-memory\/openai"/) @@ -76,18 +76,21 @@ test("package structure separates core runtime, adapters, and public package", a assert.match(publicIndex, /export \{ sqliteMemory \} from "@agent-memory\/sqlite"/) assert.equal(localPackage.name, "@agent-memory/local") + assert.equal(localPackage.private, true) assert.equal(localPackage.main, "./dist/index.js") assert.equal(localPackage.types, "./dist/index.d.ts") assert.equal(localPackage.dependencies["@agent-memory/core"], "workspace:*") assert.deepEqual(localPackage.files, ["dist", "README.md", "package.json"]) assert.equal(openaiPackage.name, "@agent-memory/openai") + assert.equal(openaiPackage.private, true) assert.equal(openaiPackage.main, "./dist/index.js") assert.equal(openaiPackage.types, "./dist/index.d.ts") assert.equal(openaiPackage.dependencies["@agent-memory/core"], "workspace:*") assert.deepEqual(openaiPackage.files, ["dist", "README.md", "package.json"]) assert.equal(postgresPackage.name, "@agent-memory/postgres") + assert.equal(postgresPackage.private, true) assert.equal(postgresPackage.main, "./dist/index.js") assert.equal(postgresPackage.types, "./dist/index.d.ts") assert.equal(postgresPackage.dependencies["@agent-memory/core"], "workspace:*") @@ -95,6 +98,7 @@ test("package structure separates core runtime, adapters, and public package", a assert.deepEqual(postgresPackage.files, ["dist", "README.md", "package.json"]) assert.equal(sqlitePackage.name, "@agent-memory/sqlite") + assert.equal(sqlitePackage.private, true) assert.equal(sqlitePackage.main, "./dist/index.js") assert.equal(sqlitePackage.types, "./dist/index.d.ts") assert.equal(sqlitePackage.dependencies["@agent-memory/core"], "workspace:*") @@ -107,11 +111,7 @@ test("root scripts build TypeScript before test and package checks", async () => assert.equal(packageJson.scripts.build, "pnpm --filter @agent-memory/core build && pnpm --filter @agent-memory/local build && pnpm --filter @agent-memory/sqlite build && pnpm --filter @agent-memory/postgres build && pnpm --filter @agent-memory/openai build && pnpm --filter agent-memory build") assert.match(packageJson.scripts.test, /pnpm build/) - assert.match(packageJson.scripts["pack:check"], /@agent-memory\/core/) - assert.match(packageJson.scripts["pack:check"], /@agent-memory\/local/) - assert.match(packageJson.scripts["pack:check"], /@agent-memory\/sqlite/) - assert.match(packageJson.scripts["pack:check"], /@agent-memory\/postgres/) - assert.match(packageJson.scripts["pack:check"], /@agent-memory\/openai/) + assert.equal(packageJson.scripts["pack:check"], "pnpm --filter agent-memory pack --dry-run") assert.match(packageJson.devDependencies.typescript, /^\^/) })