diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09fed2b..dde660d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,20 +32,15 @@ jobs: test: runs-on: ubuntu-latest - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - PI_E2E_PROVIDER: openai - PI_E2E_MODEL: gpt-4o-mini steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - run: npm ci - - name: Run e2e tests - if: ${{ env.OPENAI_API_KEY != '' }} - run: npm test - - name: Skip e2e tests (missing OPENAI_API_KEY) - if: ${{ env.OPENAI_API_KEY == '' }} - run: echo "Skipping e2e tests. Add an OPENAI_API_KEY repository secret to enable." + - run: npm run test:unit + - run: npm run test:cli diff --git a/.github/workflows/publish-npm.yml b/.github/workflows/publish-npm.yml index 512cf45..8719f51 100644 --- a/.github/workflows/publish-npm.yml +++ b/.github/workflows/publish-npm.yml @@ -21,5 +21,28 @@ jobs: node-version: 20 cache: npm registry-url: "https://registry.npmjs.org" + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest - run: npm ci + - name: Verify tag matches package.json version + shell: bash + run: | + case "$GITHUB_REF" in + refs/tags/v*) ;; + *) + echo "Publishing requires a v* tag ref; got $GITHUB_REF." >&2 + echo "For manual runs, dispatch the workflow from the release tag." >&2 + exit 1 + ;; + esac + tag="${GITHUB_REF_NAME#v}" + pkg="$(node -p "require('./package.json').version")" + if [ "$tag" != "$pkg" ]; then + echo "Tag $GITHUB_REF_NAME does not match package.json version $pkg" >&2 + exit 1 + fi + - run: npm run lint + - run: npm run build + - run: npm test - run: npm publish diff --git a/README.md b/README.md index 591edd6..196f9c1 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,25 @@ -# agent-memory (agentmemory) +# agent-memory -`agentmemory` is the canonical GitHub repository for the `agent-memory` CLI (`myagentmemory` on npm). +**Persistent memory for AI coding agents.** Give [Claude Code](https://claude.ai/code), [OpenAI Codex](https://github.com/openai/codex), [Cursor](https://cursor.com), and Agent (Cursor CLI) a memory that survives across sessions โ€” long-term facts, daily logs, topic notes, and a scratchpad checklist, stored as plain markdown and searchable with [qmd](https://github.com/tobi/qmd)-powered semantic search. -Persistent memory for coding agents โ€” [Claude Code](https://claude.ai/code), [OpenAI Codex](https://github.com/openai/codex), Cursor, and Agent (Cursor CLI). Local-first markdown memory with [qmd](https://github.com/tobi/qmd)-powered semantic search and automatic context injection. +[![npm version](https://img.shields.io/npm/v/myagentmemory?color=cb3837&logo=npm)](https://www.npmjs.com/package/myagentmemory) +[![npm downloads](https://img.shields.io/npm/dm/myagentmemory?color=cb3837&logo=npm)](https://www.npmjs.com/package/myagentmemory) +[![license](https://img.shields.io/npm/l/myagentmemory)](LICENSE) +[![website](https://img.shields.io/badge/website-jayzeng.github.io%2Fagentmemory-0d9b7a)](https://jayzeng.github.io/agentmemory/) -Project site (GitHub Pages): https://jayzeng.github.io/agentmemory/ +**[๐ŸŒ Website & quickstart โ†’](https://jayzeng.github.io/agentmemory/)** ยท [Install](#installation) ยท [CLI commands](#cli-commands) ยท [How it works](#how-it-works) -Search aliases: -- `agentmemory` (GitHub repo + Homebrew tap) -- `agent-memory` (CLI command/binary) -- `myagentmemory` (npm package name) -- `coding agent memory` / `AI coding memory` +## Why agent-memory? -GitHub metadata assets: -- Social preview image: `.github/assets/social-preview.png` (1280x640) -- Release notes template: `.github/release.yml` (used by GitHub auto-generated release notes) -- Landing page source: `docs/index.html` (deployed by `.github/workflows/deploy-pages.yml`) +Coding agents forget everything between sessions. `agent-memory` gives them a durable, local-first memory so they stop re-learning your stack, your preferences, and past decisions on every run. -Thanks to https://github.com/skyfallsin/pi-mem for inspiration. +- ๐Ÿง  **Memory that persists** โ€” decisions, preferences, and project context carry across sessions instead of starting cold. +- ๐Ÿ“ **Plain markdown, local-first** โ€” every memory is a readable, git-friendly file on disk. No database, no cloud, no lock-in. +- ๐Ÿ” **Semantic search** โ€” optional [qmd](https://github.com/tobi/qmd) integration adds keyword, semantic, and hybrid search across all memory files. +- โšก **Automatic context injection** โ€” relevant past memories surface into each turn, no manual tool calls required. +- ๐Ÿค **One memory, every agent** โ€” the same store is shared across Claude Code, Codex, Cursor, and Agent. -Long-term facts, daily logs, topic/event notes, and a scratchpad checklist stored as plain markdown files. Optional qmd integration adds keyword, semantic, and hybrid search across all memory files, plus automatic selective injection of relevant past memories into every turn. +> **Naming:** `agentmemory` is the GitHub repo (and Homebrew tap), `myagentmemory` is the npm package, and `agent-memory` is the installed CLI binary. Also known as *coding agent memory* or *AI coding memory*. ## Installation @@ -48,11 +48,7 @@ agent-memory install-skills agent-memory uninstall-skills ``` -### Pi users - -If you're on Pi and prefer a native extension, use `pi-memory` (https://github.com/jayzeng/pi-memory) instead of installing this skill. The CLI + skill workflow here is the cross-platform alternative, and works fine on Pi without any extension. - -This installs: +`install-skills` writes a SKILL.md into each agent's config directory: - `~/.claude/skills/agent-memory/SKILL.md` โ€” Claude Code skill - `~/.codex/skills/agent-memory/SKILL.md` โ€” Codex skill - `~/.cursor/skills/agent-memory/SKILL.md` โ€” Cursor skill @@ -62,6 +58,10 @@ This installs: - `%USERPROFILE%\.cursor\skills\agent-memory\SKILL.md` โ€” Cursor skill (Windows) - `%USERPROFILE%\.agents\skills\agent-memory\SKILL.md` โ€” Agent CLI skill (Windows) +### Pi users + +If you're on Pi and prefer a native extension, use `pi-memory` (https://github.com/jayzeng/pi-memory) instead of installing this skill. The CLI + skill workflow here is the cross-platform alternative, and works fine on Pi without any extension. + ### Optional: Enable search with qmd When qmd is installed, the collection is automatically set up via `agent-memory init`. @@ -80,22 +80,21 @@ Without qmd, all core tools (write/read/scratchpad) work normally. Only `memory_ ## Architecture ``` - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ src/core.ts โ”‚ โ† all logic (paths, truncation, scratchpad, - โ”‚ โ”‚ context builder, qmd, tool functions) - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - โ”‚ - โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ–ผ โ–ผ - โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” - โ”‚ src/ โ”‚ โ”‚ skills/ โ”‚ - โ”‚ cli.ts โ”‚ โ”‚ โ”œโ”€ claude-code/SKILL.md - โ”‚ โ”‚ โ”‚ โ”œโ”€ codex/SKILL.md - โ”‚ โ”‚ โ”‚ โ”œโ”€ cursor/SKILL.md - โ”‚ โ”‚ โ”‚ โ””โ”€ agent/SKILL.md - โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - CLI binary instruction files - `agent-memory` that invoke CLI + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ src/core.ts โ”‚ โ† all logic: paths, truncation, scratchpad, + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ context builder, qmd, tool functions + โ”‚ + โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ” + โ–ผ โ–ผ + โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” + โ”‚ src/ โ”‚ โ”‚ skills/ โ”‚ + โ”‚ cli.ts โ”‚ โ”‚ โ”œโ”€ claude-code/SKILL.md โ”‚ + โ”‚ โ”‚ โ”‚ โ”œโ”€ codex/SKILL.md โ”‚ + โ”‚ โ”‚ โ”‚ โ”œโ”€ cursor/SKILL.md โ”‚ + โ”‚ โ”‚ โ”‚ โ””โ”€ agent/SKILL.md โ”‚ + โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + CLI binary instruction files + `agent-memory` that invoke the CLI ``` The memory directory defaults to `~/.agent-memory/`. Override with `AGENT_MEMORY_DIR` env var or `--dir` flag. @@ -130,14 +129,14 @@ If the first search doesn't find what you need, try rephrasing or switching mode ``` ~/.agent-memory/ - MEMORY.md # Curated long-term memory - SCRATCHPAD.md # Checklist of things to fix/remember + MEMORY.md # Curated long-term memory + SCRATCHPAD.md # Checklist of things to fix/remember daily/ - 2026-02-15.md # Daily append-only log + 2026-02-15.md # Daily append-only log 2026-02-14.md ... topics/ - auth.md # Topic/event log linked back to daily entries + auth.md # Topic/event log linked back to daily entries ``` ## Topic notes @@ -165,7 +164,7 @@ Before every agent turn, the following are injected into the system prompt (in p Total injection is capped at 16K chars. When qmd is unavailable, step 3 is skipped and the rest works as before. -For Claude Code, context is injected via the `!`agent-memory context`` syntax in the SKILL.md. For Codex, Cursor, and Agent, the agent runs `agent-memory context` at session start. +For Claude Code, context is injected via the `!`agent-memory context --no-search`` syntax in the SKILL.md. For Codex, Cursor, and Agent, the agent runs `agent-memory context` at session start. ### Selective injection @@ -233,7 +232,7 @@ agent-memory install-skills ```bash # Confirm package name is available -npm view agent-memory +npm view myagentmemory # Bump version (choose patch/minor/major) npm version patch @@ -242,13 +241,23 @@ npm version patch npm publish --access public ``` +### Repository assets (maintainers) + +- **Social preview image:** `.github/assets/social-preview.png` (1280ร—640) +- **Release notes template:** `.github/release.yml` (used by GitHub auto-generated release notes) +- **Landing page source:** `docs/index.html` (deployed by `.github/workflows/deploy-pages.yml`) + +## Acknowledgments + +Inspired by [skyfallsin/pi-mem](https://github.com/skyfallsin/pi-mem). Semantic search is powered by [qmd](https://github.com/tobi/qmd). + ## Changelog -### 0.5.0 +### 0.4.12 - **Removed pi extension**: Removed `index.ts` and all pi-specific code (`@mariozechner/pi-ai`, `@mariozechner/pi-coding-agent`, `@sinclair/typebox` peer dependencies). - **Standalone tool functions**: Extracted `memoryWrite()`, `memoryRead()`, `scratchpadAction()`, `memorySearch()` into `src/core.ts` as standalone functions usable without any framework. -- **Renamed package**: `pi-memory` โ†’ `agent-memory`. +- **Renamed package**: `pi-memory` โ†’ `myagentmemory` (npm); the CLI binary is `agent-memory`. - **Renamed env var**: `PI_MEMORY_QMD_UPDATE` โ†’ `AGENT_MEMORY_QMD_UPDATE` (old name still works as fallback). - **Default memory directory**: Now always `~/.agent-memory/`. - **Removed pi-specific tests**: Deleted `test/e2e.ts`, `test/eval-recall.ts`, `test/unit.ts`. diff --git a/docs/index.html b/docs/index.html index fffa1cc..11099d0 100644 --- a/docs/index.html +++ b/docs/index.html @@ -3,11 +3,54 @@ - agentmemory - Persistent Memory For Coding Agents + agentmemory โ€” Persistent Memory for Coding Agents (Claude Code, Codex, Cursor) + + + + + + + + + + + + + + + + + + + + + +
-

Persistent memory extension for coding agents

+

Persistent memory for coding agents ยท CLI + skills

Stop losing context between sessions.

- agentmemory gives your coding agent durable memory with local markdown files, daily logs, scratchpad - checklists, and qmd-powered semantic search. + agentmemory gives your coding agent a durable, local-first memory โ€” long-term facts, daily logs, topic + notes, and a scratchpad checklist, all stored as plain markdown you can read, edit, and commit. Optional + qmd semantic search surfaces the right memory into every turn, + automatically.

- Open Repository - Quick Install + Get Started + View on GitHub +
+
+ npm version + npm downloads + GitHub stars + MIT license
Claude Code @@ -330,6 +489,122 @@

Stop losing context between sessions.

+
+
+

Why agentmemory?

+

+ Coding agents start every session from zero. agentmemory is the memory layer that makes them + remember โ€” without a database, a cloud account, or vendor lock-in. +

+
+
+
+

๐Ÿง  Memory that persists

+

Decisions, preferences, and project context carry across sessions, so your agent stops re-learning your stack on every run.

+
+
+

๐Ÿ“ Plain markdown, local-first

+

Every memory is a readable file on disk โ€” diff it, edit it, commit it to git. No database, no cloud, no lock-in.

+
+
+

๐Ÿ” Semantic search

+

Optional qmd integration adds keyword, semantic, and hybrid search across every memory file you've ever written.

+
+
+

โšก Automatic context injection

+

Relevant past memories surface into each turn on their own โ€” no manual tool calls, no copy-pasting context.

+
+
+

๐Ÿค One memory, every agent

+

The same store is shared across Claude Code, Codex, Cursor, and Agent โ€” switch tools without losing what they know.

+
+
+

โฑ๏ธ Zero-config setup

+

Two commands: init and install-skills. Works the moment you install it, qmd or not.

+
+
+
+ +
+
+

How it's different

+

Most memory tools are cloud services with their own SDK and storage. agentmemory is the opposite: files you own, on the agents you already use.

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 agentmemoryHosted memory SaaSA single CLAUDE.md
StorageLocal markdown filesVendor cloudOne local file
SearchKeyword + semantic (qmd)Vector DBNone
Auto context injectionYesVariesManual
Works across agentsClaude Code ยท Codex ยท Cursor ยท AgentSDK per appPer project
Account requiredNoYesNo
+
+
+ +
+
+

See it in action

+

Memory is just markdown. Here's what your agent reads back at the start of every session.

+
+
+
# ~/.agent-memory/MEMORY.md
+
+## Stack
+Postgres for all backend services. #decision [[database-choice]]
+URL-prefix API versioning (/v1/) to avoid CDN cache issues. #lesson
+
+## Preferences
+Neovim + LazyVim. Conventional commits. Run tests before every push.
+
+# Recall it later, in any agent:
+$ agent-memory search --query "how do we version the API" --mode semantic
+โ†’ URL-prefix API versioning (/v1/) to avoid CDN cache issues.
+
+
+
+ +
+
+

Install in under a minute

+

Grab the CLI, create your memory store, and wire it into every agent you use.

+
+
+

Install from npm

@@ -357,7 +632,7 @@

Initialize memory

Install skill files

-

Install extension prompts for Claude Code, Codex, Cursor, and Agent CLI.

+

Wire memory into Claude Code, Codex, Cursor, and Agent CLI in one command.

agent-memory install-skills @@ -366,7 +641,7 @@

Install skill files

-

Three-minute setup

+

Setup in three steps

  1. Install `agent-memory` from npm or Homebrew.
  2. Run `agent-memory init` to create local memory storage.
  3. diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 0000000..4cd00f2 --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://jayzeng.github.io/agentmemory/sitemap.xml diff --git a/docs/sitemap.xml b/docs/sitemap.xml new file mode 100644 index 0000000..17dbe5d --- /dev/null +++ b/docs/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://jayzeng.github.io/agentmemory/ + weekly + 1.0 + + diff --git a/scripts/postinstall.cjs b/scripts/postinstall.cjs index b346a06..7599e7b 100644 --- a/scripts/postinstall.cjs +++ b/scripts/postinstall.cjs @@ -1,6 +1,9 @@ const { spawnSync } = require("node:child_process"); +const fs = require("node:fs"); const path = require("node:path"); +const packageRoot = path.resolve(__dirname, ".."); + function hasQmd() { const result = spawnSync("qmd", ["status"], { stdio: "ignore", @@ -14,23 +17,39 @@ function memoryDir() { return path.join(home, ".agent-memory"); } +// Distinguish a development checkout of agentmemory from an end-user install. +// When agentmemory is installed as a dependency it lives under node_modules and +// ships without the `.githooks` directory (it's absent from the package.json +// "files" allowlist). We must never touch a consumer's repo or VCS config, so +// the dev-only hook setup below is gated on "are we actually in the source repo?". +function isDevCheckout() { + if (packageRoot.split(path.sep).includes("node_modules")) return false; + return fs.existsSync(path.join(packageRoot, ".githooks")); +} + +// Point git at the repo's tracked hooks (lint + tests on commit). Scoped to the +// agentmemory working tree only โ€” `cwd` + the dev-checkout gate ensure we only +// ever write to this repo's local git config, never a consumer's. function configureGitHooks() { - const result = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { + const insideRepo = spawnSync("git", ["rev-parse", "--is-inside-work-tree"], { + cwd: packageRoot, stdio: "ignore", shell: process.platform === "win32", }); - - if (result.status !== 0) { + if (insideRepo.status !== 0) { return; } spawnSync("git", ["config", "core.hooksPath", ".githooks"], { + cwd: packageRoot, stdio: "ignore", shell: process.platform === "win32", }); } -configureGitHooks(); +if (isDevCheckout()) { + configureGitHooks(); +} if (!hasQmd()) { const dir = memoryDir(); diff --git a/src/cli.ts b/src/cli.ts index 6ad2065..c1b1ce7 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -43,6 +43,7 @@ import { installSkills, nowTimestamp, parseScratchpad, + probeEmbeddings, readFileSafe, runQmdEmbedDetached, runQmdSearch, @@ -665,11 +666,18 @@ async function cmdStatus(flags: Record) { const qmdFound = await detectQmd(); let hasCollection = false; let health = null; + let embeddings: "ready" | "missing" | "unknown" | "n/a" = "n/a"; if (qmdFound) { hasCollection = await checkCollection(); if (hasCollection) { await ensureQmdAvailableForSync(); health = await getQmdHealth(); + // A live semantic probe confirms embeddings are actually usable, but + // it costs a real qmd query (and a possible model load), so it's + // opt-in โ€” the cheap pending-embed count below covers the common case. + if (hasFlag(flags, "probe")) { + embeddings = await probeEmbeddings(); + } } } @@ -695,6 +703,7 @@ async function cmdStatus(flags: Record) { available: qmdFound, collection: hasCollection ? getCollectionName() : null, health, + embeddings, }, embedMode, }, @@ -725,6 +734,15 @@ async function cmdStatus(flags: Record) { `Collection '${getCollectionName()}': ${hasCollection ? "configured" : "not configured โ€” run: agent-memory init"}`, ); console.log(`Embed mode: ${embedMode}`); + if (hasCollection && embeddings !== "n/a") { + const embLabel = + embeddings === "ready" + ? "ready" + : embeddings === "missing" + ? "missing โ€” run: agent-memory sync" + : "unknown (could not verify within probe timeout)"; + console.log(`Embeddings (semantic/deep search): ${embLabel}`); + } if (health) { if (health.totalFiles !== null) console.log(`Files indexed: ${health.totalFiles}`); if (health.vectorsEmbedded !== null) console.log(`Vectors embedded: ${health.vectorsEmbedded}`); @@ -789,7 +807,7 @@ Commands: distil Generate compact MEMORY.md index from daily logs + topics sync Re-index and embed all files (requires qmd) init Initialize memory directory and qmd collection - status Show configuration and status + status Show configuration and status (--probe for a live embeddings check) Global flags: --dir Override memory directory diff --git a/src/core.ts b/src/core.ts index 0219140..42f7535 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1146,12 +1146,13 @@ export function runQmdSearch( mode: "keyword" | "semantic" | "deep", query: string, limit: number, + options: { signal?: AbortSignal } = {}, ): Promise<{ results: QmdSearchResult[]; stderr: string }> { const subcommand = mode === "keyword" ? "search" : mode === "semantic" ? "vsearch" : "query"; const args = [subcommand, "--json", "-c", QMD_COLLECTION_NAME, "-n", String(limit), query]; return new Promise((resolve, reject) => { - execFileFn("qmd", args, { timeout: 60_000 }, (err, stdout, stderr) => { + execFileFn("qmd", args, { timeout: 60_000, signal: options.signal }, (err, stdout, stderr) => { if (err) { reject(new Error(stderr?.trim() || err.message)); return; @@ -1171,6 +1172,39 @@ export function runQmdSearch( }); } +/** + * Best-effort check of whether vector embeddings are actually usable for + * semantic/deep search right now. Runs a tiny semantic probe and looks for + * qmd's "need embeddings" warning. Bounded by a short timeout because the very + * first semantic query can trigger a model download โ€” returns "unknown" rather + * than blocking on it. "ready" means the probe ran without the warning; it does + * not prove the index has content. + */ +export async function probeEmbeddings(): Promise<"ready" | "missing" | "unknown"> { + let timer: ReturnType | undefined; + // Abort the underlying qmd child when the timeout fires so it does not keep + // the event loop open until its own 60s timeout and hang the CLI. + const controller = new AbortController(); + try { + const { stderr } = await Promise.race([ + runQmdSearch("semantic", "memory", 1, { signal: controller.signal }), + new Promise((_, reject) => { + timer = setTimeout(() => { + controller.abort(); + reject(new Error("timeout")); + }, 4_000); + }), + ]); + return /need embeddings/i.test(stderr ?? "") ? "missing" : "ready"; + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + if (/need embeddings/i.test(msg)) return "missing"; + return "unknown"; + } finally { + clearTimeout(timer); + } +} + // --------------------------------------------------------------------------- // Standalone tool functions // --------------------------------------------------------------------------- diff --git a/test/cli.test.ts b/test/cli.test.ts index d5265c0..491eb19 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -330,6 +330,8 @@ describe("CLI subprocess", () => { expect(out.directory).toBe(tmpDir); expect(out.dailyLogs).toBe(0); expect(out.topics).toBe(0); + // Live embeddings probe is opt-in (--probe), so it never runs here. + expect(out.qmd.embeddings).toBe("n/a"); }); test("write and read round-trip", async () => { @@ -559,6 +561,8 @@ describe("CLI subprocess", () => { fs.mkdirSync(path.join(projectDir, "skills", "claude-code"), { recursive: true }); fs.writeFileSync(path.join(projectDir, "skills", "claude-code", "SKILL.md"), "# Claude", "utf-8"); fs.mkdirSync(path.join(homeDir, ".claude"), { recursive: true }); + // Detect via a marker file so the test does not depend on a `claude` binary being on PATH. + fs.writeFileSync(path.join(homeDir, ".claude", "settings.json"), "{}", "utf-8"); const result = Bun.spawnSync( ["bun", "run", path.join(__dirname, "..", "src", "cli.ts"), "install-skills", "--json"], @@ -759,6 +763,9 @@ describe("install scripts", () => { // Install first fs.mkdirSync(path.join(tmpHome, ".claude"), { recursive: true }); fs.mkdirSync(path.join(tmpHome, ".codex"), { recursive: true }); + // Detect via marker files so the test does not depend on `claude`/`codex` binaries on PATH. + fs.writeFileSync(path.join(tmpHome, ".claude", "settings.json"), "{}", "utf-8"); + fs.writeFileSync(path.join(tmpHome, ".codex", "config.toml"), "", "utf-8"); const installResult = Bun.spawnSync(["bash", path.join(repoRoot, "scripts", "install-skills.sh")], { cwd: repoRoot, @@ -789,6 +796,9 @@ describe("install scripts", () => { fs.mkdirSync(path.join(tmpHome, ".codex"), { recursive: true }); fs.mkdirSync(path.join(tmpHome, ".cursor"), { recursive: true }); fs.mkdirSync(path.join(tmpHome, ".agents"), { recursive: true }); + // Detect via marker files so the test does not depend on `claude`/`codex` binaries on PATH. + fs.writeFileSync(path.join(tmpHome, ".claude", "settings.json"), "{}", "utf-8"); + fs.writeFileSync(path.join(tmpHome, ".codex", "config.toml"), "", "utf-8"); const result = Bun.spawnSync(["bash", path.join(repoRoot, "scripts", "install-skills.sh")], { cwd: repoRoot,