diff --git a/Documentation.md b/Documentation.md index d57eff4..617f7cf 100644 --- a/Documentation.md +++ b/Documentation.md @@ -1,7 +1,7 @@ # GitAgent Documentation > **GitAgent** — A universal git-native multimodal always-learning AI Agent -> Version 1.3.3 | MIT License | [github.com/open-gitagent/gitagent](https://github.com/open-gitagent/gitagent) +> Version 1.5.2 | MIT License | [github.com/open-gitagent/gitagent](https://github.com/open-gitagent/gitagent) --- @@ -83,7 +83,7 @@ The installer offers four options: curl -fsSL https://raw.githubusercontent.com/open-gitagent/gitagent/main/install.sh | bash # Or manually -npm update -g gitagent +npm update -g @open-gitagent/gitagent ``` --- diff --git a/README.md b/README.md index c684c6d..ea8baf9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@
-
+
@@ -56,7 +56,7 @@ This will:
- Walk you through API key setup (Quick or Advanced mode)
- Launch the voice UI in your browser at `http://localhost:3333`
-> **Requirements:** Node.js 18+, npm, git
+> **Requirements:** Node.js 20+, npm, git
### Or install manually:
@@ -752,7 +752,7 @@ Your agent lives in a git repository with structured files:
### Installation & Setup
**What are the requirements?**
-Node.js 18+ (or 20+ recommended), npm, and git. Install globally with `npm install -g @open-gitagent/gitagent`.
+Node.js 20+, npm, and git. Install globally with `npm install -g @open-gitagent/gitagent`.
**How do I set up API keys?**
Run the installer for guided setup:
diff --git a/install.sh b/install.sh
index 1325f44..472e700 100755
--- a/install.sh
+++ b/install.sh
@@ -91,8 +91,8 @@ check_cmd npm
check_cmd git
NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
-if [ "$NODE_VERSION" -lt 18 ]; then
- echo -e " ${RED}✗ Node.js 18+ required (found $(node -v))${NC}"
+if [ "$NODE_VERSION" -lt 20 ]; then
+ echo -e " ${RED}✗ Node.js 20+ required (found $(node -v))${NC}"
exit 1
fi
diff --git a/package-lock.json b/package-lock.json
index 1e81523..dd28020 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3257,6 +3257,7 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
+ "peer": true,
"engines": {
"node": ">=8"
}
diff --git a/src/__tests__/telemetry.test.ts b/src/__tests__/telemetry.test.ts
index 232554d..7aeff1f 100644
--- a/src/__tests__/telemetry.test.ts
+++ b/src/__tests__/telemetry.test.ts
@@ -1,6 +1,134 @@
-import { describe, it } from "node:test";
+/**
+ * Tests for the telemetry module (src/telemetry.ts).
+ *
+ * These tests verify that initTelemetry correctly gates on the OTLP
+ * endpoint environment variable: it MUST be a no-op when the endpoint
+ * is not configured, and it MUST successfully create an SDK instance
+ * when an endpoint (or test provider) is provided.
+ */
+import { describe, it, before, afterEach } from "node:test";
+import assert from "node:assert/strict";
+import { trace } from "@opentelemetry/api";
+import {
+ NodeTracerProvider,
+ InMemorySpanExporter,
+ SimpleSpanProcessor,
+} from "@opentelemetry/sdk-trace-node";
+
+let initTelemetry: typeof import("../telemetry.ts").initTelemetry;
+let shutdownTelemetry: typeof import("../telemetry.ts").shutdownTelemetry;
+let isTelemetryEnabled: typeof import("../telemetry.ts").isTelemetryEnabled;
+
+before(async () => {
+ const mod = await import("../telemetry.ts");
+ initTelemetry = mod.initTelemetry;
+ shutdownTelemetry = mod.shutdownTelemetry;
+ isTelemetryEnabled = mod.isTelemetryEnabled;
+});
+
+afterEach(async () => {
+ // Always clean up telemetry after each test to avoid cross-test
+ // contamination from global state.
+ await shutdownTelemetry();
+ try {
+ trace.disable();
+ } catch {
+ /* ignore */
+ }
+});
describe("telemetry", () => {
- it.todo("initTelemetry is a no-op when OTEL_EXPORTER_OTLP_ENDPOINT is not set");
- it.todo("initTelemetry creates an SDK instance when endpoint is configured");
+ // ── Helpers ──────────────────────────────────────────────────────
+
+ function makeTestProvider() {
+ const exporter = new InMemorySpanExporter();
+ const provider = new NodeTracerProvider({
+ spanProcessors: [new SimpleSpanProcessor(exporter)],
+ });
+ return { exporter, provider };
+ }
+
+ // ── initTelemetry no-op ──────────────────────────────────────────
+
+ it("initTelemetry without OTLP endpoint does not throw and leaves module in a consistent state", async () => {
+ // Ensure the env var is not set for this test
+ const saved = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
+ delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
+
+ try {
+ // Calling initTelemetry with no options does not throw — the
+ // module always wraps its body in try/catch so failures are
+ // logged, not thrown.
+ await assert.doesNotReject(
+ () => initTelemetry({}),
+ "initTelemetry must never throw, even without an endpoint",
+ );
+ } finally {
+ if (saved !== undefined) {
+ process.env.OTEL_EXPORTER_OTLP_ENDPOINT = saved;
+ }
+ }
+ });
+
+ // ── initTelemetry with endpoint ──────────────────────────────────
+
+ it("initTelemetry creates an SDK instance when endpoint is configured", async () => {
+ // Set a (bogus) OTLP endpoint so the init path proceeds past the
+ // no-op guard. Because we do not have a real collector, we also
+ // provide a _testProvider so the test is deterministic.
+ process.env.OTEL_EXPORTER_OTLP_ENDPOINT = "http://localhost:4318";
+ const { exporter, provider } = makeTestProvider();
+
+ try {
+ await initTelemetry({
+ serviceName: "test-svc",
+ _testProvider: provider,
+ });
+
+ assert.equal(
+ isTelemetryEnabled(),
+ true,
+ "telemetry must be enabled after initTelemetry with _testProvider",
+ );
+
+ // Verify the provider was actually registered: create a span
+ // and confirm it flows through the InMemorySpanExporter.
+ const tracer = trace.getTracer("test");
+ const span = tracer.startSpan("test-span");
+ span.end();
+
+ // Force flush by shutting down (which is handled by afterEach)
+ const spans = exporter.getFinishedSpans();
+ assert.equal(spans.length, 1, "span should be exported");
+ assert.equal(spans[0].name, "test-span");
+ } finally {
+ delete process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
+ }
+ });
+
+ // ── Idempotency ──────────────────────────────────────────────────
+
+ it("initTelemetry is idempotent", async () => {
+ const { provider: provider1 } = makeTestProvider();
+ const { provider: provider2 } = makeTestProvider();
+
+ await initTelemetry({ _testProvider: provider1 });
+ assert.equal(isTelemetryEnabled(), true);
+
+ // Second call should be a no-op — _initialized is already true
+ await initTelemetry({ _testProvider: provider2 });
+ assert.equal(isTelemetryEnabled(), true);
+ });
+
+ // ── shutdownTelemetry ────────────────────────────────────────────
+
+ it("shutdownTelemetry resets the initialized state", async () => {
+ const { provider } = makeTestProvider();
+
+ await initTelemetry({ _testProvider: provider });
+ assert.equal(isTelemetryEnabled(), true);
+
+ await shutdownTelemetry();
+ assert.equal(isTelemetryEnabled(), false);
+ });
});
diff --git a/src/tools/__tests__/memory.test.ts b/src/tools/__tests__/memory.test.ts
index 1a2269e..9c7cd5f 100644
--- a/src/tools/__tests__/memory.test.ts
+++ b/src/tools/__tests__/memory.test.ts
@@ -1,7 +1,197 @@
-import { describe, it } from "node:test";
+/**
+ * Tests for the memory tool (src/tools/memory.ts).
+ *
+ * The memory tool provides git-backed persistent memory with load/save
+ * operations. Each save creates a git commit, giving full history of
+ * what the agent has remembered.
+ */
+import { describe, it, before } from "node:test";
+import assert from "node:assert/strict";
+import { mkdtemp, rm, writeFile, mkdir } from "fs/promises";
+import { join } from "path";
+import { tmpdir } from "os";
+import { execSync } from "child_process";
+
+let createMemoryTool: typeof import("../../../dist/tools/memory.js").createMemoryTool;
+
+before(async () => {
+ const mod = await import("../../../dist/tools/memory.js");
+ createMemoryTool = mod.createMemoryTool;
+});
describe("memory tool", () => {
- it.todo("load returns stored memory content");
- it.todo("save writes content and commits to git");
- it.todo("save requires content and message");
+ /** Create a temporary directory with git init and return the path. */
+ async function setupRepo(): Promise