mirror of
https://github.com/rohitg00/agentmemory.git
synced 2026-09-14 20:16:33 +08:00
8b98432853
* chore(release): v0.9.13 — env-example discovery + CJK tokenizer + load harness + deploy templates + Gemini GA bumps + 14 advisories closed Six PRs landed since v0.9.12: - #372 .env.example discovery (this commit) — repo-root template + `init` CLI command + CI sync-checker - #362 CJK BM25 tokenizer (`@node-rs/jieba` + tiny-segmenter + Hangul) - #363 `benchmark/load-100k.ts` harness with p50/p90/p99 + per-release results dir - #361 one-click deploy templates for fly.io / Railway / Render / Coolify (multi-stage Dockerfile, `iiidev/iii` base, `gosu` privilege drop, first-boot HMAC, verified end-to-end on fly.io) - #364 Python ecosystem via `iii-sdk` example (replaces closed PR #360) - #370 Gemini GA bumps (LLM default → gemini-2.5-flash, embedding → gemini-embedding-001 + L2-norm + 768 dims) Plus 14 open Dependabot advisories closed in PR #348 via Next.js → 16.2.6 and PostCSS → 8.5.10 overrides. Bumped: - src/version.ts: VERSION 0.9.12 → 0.9.13 - package.json: 0.9.12 → 0.9.13, files += ".env.example", build script copies .env.example into dist/ - packages/mcp/package.json: 0.9.12 → 0.9.13 (lockstep with main) - plugin/.claude-plugin/plugin.json, plugin/.codex-plugin/plugin.json: 0.9.12 → 0.9.13 - src/types.ts: ExportData.version union extended with "0.9.13" - src/functions/export-import.ts: supportedVersions Set extended - test/export-import.test.ts: expected version updated New surface: - .env.example at repo root — every env var read by src/ documented in one place, grouped by surface (LLM, embedding, auth, search tuning, behaviour flags, CLI runtime, ports, iii engine pin, Claude Code bridge, Obsidian export). Every line commented out by default so the file is a template. - agentmemory init — copies bundled .env.example to ~/.agentmemory/.env if absent, refuses to overwrite, prints a diff command. Wired into CLI dispatch + help block. - scripts/check-env-example.mjs — walks src/ for env-read patterns, fails CI on drift in either direction. Plugged into ci.yml after npm test. Initial bootstrap: 60 keys in sync. Verified: npm test 903/903, npm run build clean, init smoke pass (creates ~/.agentmemory/.env on first run, refuses overwrite on second). * fix(init): atomic copy via COPYFILE_EXCL; address CodeRabbit review Two valid findings from the CodeRabbit pass on PR #383. 1. `runInit` race between existsSync(target) + copyFile(template, target). A parallel `agentmemory init` (or any other process touching ~/.agentmemory/.env between the two calls) would silently overwrite the config the operator just wrote. Switch to a single atomic `copyFile(template, target, fsConstants.COPYFILE_EXCL)` and treat the EEXIST error as the "already configured" signal — same warning + diff hint as before, but the check + copy now happen in one syscall so they cannot race. Other failure paths still surface as process exit 1. 2. Comment on `scripts/check-env-example.mjs::walk` claimed it matched ".ts / .mts / .mjs" but the regex also matched ".js". Rewrote the comment to match the regex (".ts / .mts / .mjs / .js"). Same comment pass: noted that test/ never enters because the walk is rooted at src/, not because of an explicit skip. Skipped findings: - WHAT-style comment on `findEnvExample` — kept a one-liner explaining the package-vs-source priority since both paths are real; reduced the block from 4 lines to 2 instead of removing it entirely. - "Add trailing newline to .env.example" — file already ends with `\n` (verified `tail -c 5` shows `tion\n`). Verified locally: - `npm run build` clean. - `npm test` 903 / 903 pass. - First `agentmemory init` against a clean HOME creates the file. - Second init against the same HOME hits EEXIST and prints the "leaving it untouched" warning + diff hint without overwriting. - `node scripts/check-env-example.mjs` — in sync (60 keys).
102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
|
//
|
|
// Sync-check: every env var read by `src/` MUST be documented in
|
|
// `.env.example`. Runs in CI as a soft guard rail — keeps `.env.example`
|
|
// from drifting behind real config-surface additions.
|
|
//
|
|
// Usage:
|
|
// node scripts/check-env-example.mjs
|
|
//
|
|
// Returns 0 when in sync, 1 with a diff when out of sync.
|
|
|
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const ROOT = new URL("..", import.meta.url).pathname;
|
|
const SRC = join(ROOT, "src");
|
|
const ENV_FILE = join(ROOT, ".env.example");
|
|
|
|
// Env vars read by the runtime but NOT user-facing config — these are
|
|
// either process-injected (HOME, PATH, USERPROFILE), set by the build /
|
|
// wrapper (NODE_*, npm_*), or set by tests (VITEST, *_TEST_*). Skipping
|
|
// them keeps `.env.example` a documented config surface rather than an
|
|
// inventory of every getenv anywhere in the codebase.
|
|
const RUNTIME_ONLY = new Set([
|
|
"HOME",
|
|
"PATH",
|
|
"USERPROFILE",
|
|
"NODE_ENV",
|
|
"AGENTMEMORY_SDK_CHILD",
|
|
]);
|
|
|
|
// Walk src/ for .ts / .mts / .mjs / .js files (excluding `.d.ts` declarations
|
|
// and dotfile dirs / node_modules). test/ lives outside src/ so it never enters.
|
|
function walk(dir) {
|
|
const out = [];
|
|
for (const entry of readdirSync(dir)) {
|
|
const full = join(dir, entry);
|
|
const s = statSync(full);
|
|
if (s.isDirectory()) {
|
|
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
out.push(...walk(full));
|
|
} else if (/\.(ts|mts|mjs|js)$/.test(entry) && !entry.endsWith(".d.ts")) {
|
|
out.push(full);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Multiple patterns:
|
|
// process.env["KEY"] — direct access
|
|
// env["KEY"] — local alias inside detectProvider, etc.
|
|
// getEnvVar("KEY") — helper from src/config.ts
|
|
// env: ProcessEnv → env.KEY — caught as `env["KEY"]` only; if you add
|
|
// a dotted-access path, extend the regex.
|
|
const PATTERNS = [
|
|
// Direct map index: process.env["KEY"], env["KEY"], getMergedEnv()["KEY"].
|
|
// The trailing `]\s*` form covers `…)["KEY"]` and `…env["KEY"]`.
|
|
/\[\s*"([A-Z][A-Z0-9_]+)"\s*\]/g,
|
|
/getEnvVar\(\s*"([A-Z][A-Z0-9_]+)"\s*\)/g,
|
|
];
|
|
const used = new Set();
|
|
for (const file of walk(SRC)) {
|
|
const text = readFileSync(file, "utf8");
|
|
for (const pat of PATTERNS) {
|
|
pat.lastIndex = 0;
|
|
let m;
|
|
while ((m = pat.exec(text)) !== null) {
|
|
const name = m[1];
|
|
if (!RUNTIME_ONLY.has(name)) used.add(name);
|
|
}
|
|
}
|
|
}
|
|
|
|
const envText = readFileSync(ENV_FILE, "utf8");
|
|
const documented = new Set();
|
|
for (const line of envText.split("\n")) {
|
|
const m = line.match(/^#?\s*([A-Z][A-Z0-9_]+)=/);
|
|
if (m) documented.add(m[1]);
|
|
}
|
|
|
|
const missing = [...used].filter((k) => !documented.has(k)).sort();
|
|
const orphan = [...documented].filter((k) => !used.has(k)).sort();
|
|
|
|
if (missing.length === 0 && orphan.length === 0) {
|
|
console.log(`env-example: in sync (${used.size} keys documented)`);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (missing.length > 0) {
|
|
console.error(
|
|
`env-example: MISSING from .env.example — add documentation for these keys:`,
|
|
);
|
|
for (const k of missing) console.error(` - ${k}`);
|
|
}
|
|
if (orphan.length > 0) {
|
|
console.error(
|
|
`env-example: ORPHAN in .env.example — no longer read by src/, remove or move to runtime-only allowlist:`,
|
|
);
|
|
for (const k of orphan) console.error(` - ${k}`);
|
|
}
|
|
process.exit(1);
|