AkurAI Build
Menu

popagent

public

Latest change 4565eb356975518dfc0202eab29b56dff51e8f71 - add deterministic agent health validation by AkurAI Build

import { afterEach, describe, expect, test } from "bun:test";
import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
  CodeIntelligenceService,
  OpenCodebaseIndexBackend,
  type CodeIndexCall,
  type CodeIndexResult,
  type CodeIndexSession,
} from "./code-intelligence";

const temporaryPaths: string[] = [];

afterEach(async () => {
  await Promise.all(temporaryPaths.splice(0).map((path) => rm(path, { recursive: true, force: true })));
});

function fakeWorkspace(path: string) {
  return {
    resolveRepository: async (id: string) => ({
      workspace: { id, name: id, repositoryPath: ".", createdAt: "", updatedAt: "" },
      path,
    }),
  };
}

function recordingBackend(results: Record<string, string> = {}) {
  const batches: Array<{ repositoryPath: string; calls: CodeIndexCall[] }> = [];
  return {
    batches,
    backend: {
      execute: async (repositoryPath: string, calls: CodeIndexCall[]): Promise<CodeIndexResult[]> => {
        batches.push({ repositoryPath, calls });
        return calls.map(({ name }) => ({ text: results[name] ?? `${name} evidence`, isError: false }));
      },
    },
  };
}

describe("CodeIntelligenceService", () => {
  test("uses the contained workspace and bounded context operation", async () => {
    const { backend, batches } = recordingBackend({ codebase_context: "bounded context" });
    const service = new CodeIntelligenceService(fakeWorkspace("/contained/repository"), backend);

    await expect(service.codeContext("workspace-a", "browser host policy", 8)).resolves.toEqual({
      evidence: "bounded context",
    });
    expect(batches).toEqual([{
      repositoryPath: "/contained/repository",
      calls: [{
        name: "codebase_context",
        arguments: { query: "browser host policy", limit: 8, tokenBudget: 4_000 },
      }],
    }]);
  });

  test("combines definition and direct relationship evidence for a symbol", async () => {
    const { backend, batches } = recordingBackend({
      implementation_lookup: "definition",
      call_graph: "graph",
    });
    const service = new CodeIntelligenceService(fakeWorkspace("/contained/repository"), backend);

    await expect(service.symbolContext("workspace-a", "RepositoryBriefService", "src/repo-brief.ts")).resolves.toEqual({
      definition: "definition",
      callers: "graph",
      callees: "graph",
    });
    expect(batches[0]!.calls).toEqual([
      {
        name: "implementation_lookup",
        arguments: { query: "RepositoryBriefService", limit: 5, directory: "src" },
      },
      {
        name: "call_graph",
        arguments: { name: "RepositoryBriefService", direction: "callers", filePath: "src/repo-brief.ts" },
      },
      {
        name: "call_graph",
        arguments: { name: "RepositoryBriefService", direction: "callees", filePath: "src/repo-brief.ts" },
      },
    ]);
  });

  test("uses current changed paths when impact paths are omitted", async () => {
    const { backend, batches } = recordingBackend({
      codebase_edit_context: "file impact",
      pr_impact: "branch impact",
    });
    const service = new CodeIntelligenceService(
      fakeWorkspace("/contained/repository"),
      backend,
      async () => ["src/api-types.ts", "src/server.ts"],
    );

    await expect(service.changeImpact("workspace-a", [])).resolves.toEqual({
      paths: ["src/api-types.ts", "src/server.ts"],
      branchImpact: "branch impact",
      files: [
        { path: "src/api-types.ts", evidence: "file impact" },
        { path: "src/server.ts", evidence: "file impact" },
      ],
      unresolved: [],
    });
    expect(batches[0]!.calls).toEqual([
      {
        name: "pr_impact",
        arguments: { maxDepth: 5, direction: "both", checkConflicts: false },
      },
      {
        name: "codebase_edit_context",
        arguments: {
          query: "Assess the blast radius and affected tests for changes in src/api-types.ts.",
          filePath: "src/api-types.ts",
          callerLimit: 8,
          calleeLimit: 8,
          tokenBudget: 4_000,
        },
      },
      {
        name: "codebase_edit_context",
        arguments: {
          query: "Assess the blast radius and affected tests for changes in src/server.ts.",
          filePath: "src/server.ts",
          callerLimit: 8,
          calleeLimit: 8,
          tokenBudget: 4_000,
        },
      },
    ]);
  });
});

describe("OpenCodebaseIndexBackend", () => {
  test("refreshes the local index before queries and keeps index state out of source", async () => {
    const repositoryPath = await mkdtemp(join(tmpdir(), "popagent-code-index-"));
    temporaryPaths.push(repositoryPath);
    await mkdir(join(repositoryPath, ".git", "info"), { recursive: true });
    const calls: CodeIndexCall[] = [];
    let closed = false;
    const session: CodeIndexSession = {
      call: async (call) => {
        calls.push(call);
        return { text: `${call.name} result`, isError: false };
      },
      close: async () => {
        closed = true;
      },
    };
    const dataRoot = await mkdtemp(join(tmpdir(), "popagent-code-index-data-"));
    temporaryPaths.push(dataRoot);
    const backend = new OpenCodebaseIndexBackend({
      dataRoot,
      embedding: {
        baseUrl: "http://127.0.0.1:8081/v1",
        model: "text-embedding-3-small",
        dimensions: 1_024,
      },
      openSession: async () => session,
    });

    await expect(backend.execute(repositoryPath, [{ name: "codebase_context", arguments: { query: "auth" } }]))
      .resolves.toEqual([{ text: "codebase_context result", isError: false }]);
    expect(calls).toEqual([
      { name: "index_codebase", arguments: { force: false, estimateOnly: false, verbose: false } },
      { name: "codebase_context", arguments: { query: "auth" } },
    ]);
    expect(closed).toBe(true);
    expect(await readFile(join(repositoryPath, ".git", "info", "exclude"), "utf8"))
      .toContain("/.codebase-index/");
    expect(await Bun.file(join(repositoryPath, ".gitignore")).exists()).toBe(false);
  });
});