Files
Empryo/tests/ignore-matcher.test.ts
PouiiroandSoulForge 45854f4207 feat(soulmap): trigram search, gitignore, soul_query pipeline, hash CAS, scan gating
Optimize the Soul Map for AI-driven work at scale (10k-50k+ files):

- Trigram substring index (trigram.ts): soul_grep narrows literal
  searches via a WITHOUT ROWID trigram table — 0.8s full scan to
  sub-ms candidate lookup at 50k files. Superset filter; ripgrep
  still does the real match (no regex/case/multiline loss).
- .gitignore + .soulforgeignore honored (ignore.ts, wraps `ignore`
  pkg): git path via ls-files --exclude-standard; non-git walk and
  .soulforgeignore via IgnoreMatcher.
- soul_query pipeline tool: one call composes search/find/filter/
  deps/outline/read/limit, threading a working file-set. Zero file
  I/O until a read stage.
- read hash + ifHash CAS: read emits hash: footer; edit_file/
  multi_edit/read accept ifHash to skip re-reads on iterative edits.
- navigate(references) annotates each call site with its enclosing
  function/class (getEnclosingSymbols).
- mtime+size scan gating (files.size_bytes): catches same-mtime
  content swaps, skips redundant re-index.
- atomic writes (atomicWriteFile): crash-safe tmp+rename on all
  platforms, routed through edit_file/multi_edit/undo.

Tests: trigram, ignore-matcher, soul-query, size-gating, repo-map-refs.
typecheck + lint + full suite green.

Co-Authored-By: SoulForge <[email protected]>
2026-05-31 01:01:37 +02:00

76 lines
3.0 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { buildIgnoreMatcher, parseIgnore } from "../src/core/intelligence/ignore.js";
import { collectFiles } from "../src/core/intelligence/repo-map-utils.js";
describe("parseIgnore", () => {
test("drops comments and blanks, preserves rules incl. negations", () => {
const rules = parseIgnore("# comment\n\n!keep.ts\nbuild/\n");
// negations are valid gitignore syntax — the engine honors them, so we keep them
expect(rules).toEqual(["!keep.ts", "build/"]);
});
});
describe("IgnoreMatcher", () => {
test("dir/ matches a directory at any depth", () => {
const m = buildIgnoreMatcher("build/");
expect(m.ignores("build", true)).toBe(true);
expect(m.ignores("packages/x/build", true)).toBe(true);
// dir-only rule must NOT match a file of the same name
expect(m.ignores("build", false)).toBe(false);
});
test("rooted /foo matches only at repo root", () => {
const m = buildIgnoreMatcher("/dist");
expect(m.ignores("dist", true)).toBe(true);
expect(m.ignores("src/dist", true)).toBe(false);
});
test("*.ext glob matches by extension at any depth", () => {
const m = buildIgnoreMatcher("*.log");
expect(m.ignores("debug.log", false)).toBe(true);
expect(m.ignores("logs/server.log", false)).toBe(true);
expect(m.ignores("server.ts", false)).toBe(false);
});
test("exact basename matches at any depth", () => {
const m = buildIgnoreMatcher("secret.key");
expect(m.ignores("secret.key", false)).toBe(true);
expect(m.ignores("config/secret.key", false)).toBe(true);
expect(m.ignores("secret.key.example", false)).toBe(false);
});
test("path with slash anchors to that path", () => {
const m = buildIgnoreMatcher("src/generated");
expect(m.ignores("src/generated", true)).toBe(true);
expect(m.ignores("src/generated/api.ts", false)).toBe(true);
expect(m.ignores("lib/src/generated", true)).toBe(false);
});
test("** double-star matches at any depth (gitignore spec)", () => {
const m = buildIgnoreMatcher("**/temp");
expect(m.ignores("temp", true)).toBe(true);
expect(m.ignores("a/b/temp", true)).toBe(true);
});
test("negation re-includes a previously ignored path", () => {
const m = buildIgnoreMatcher("*.log\n!keep.log\n");
expect(m.ignores("debug.log", false)).toBe(true);
expect(m.ignores("keep.log", false)).toBe(false);
});
test("empty matcher never ignores", () => {
const m = buildIgnoreMatcher(null, undefined, "");
expect(m.size).toBe(0);
expect(m.ignores("anything", true)).toBe(false);
});
test("does not ignore real source files", () => {
const m = buildIgnoreMatcher("node_modules/\ndist/\n*.log\ncoverage/\n");
expect(m.ignores("src/core/agents/forge.ts", false)).toBe(false);
expect(m.ignores("src/components/App.tsx", false)).toBe(false);
});
});