Sprint 0-1: Python indexer, TS plugin scaffolding, and test suite

## What's new

**Python indexer (`python/obsidian_rag/`)** — full pipeline from scan to LanceDB:
- `config.py` — JSON config loader with cross-platform path resolution
- `security.py` — path traversal prevention, HTML stripping, sensitive content detection, dir allow/deny lists
- `chunker.py` — section-split for journal entries (date-named files), sliding-window for unstructured notes
- `embedder.py` — Ollama `/api/embeddings` client with batched requests and timeout/error handling
- `vector_store.py` — LanceDB schema, upsert (merge_insert), delete, search with filters, stats
- `indexer.py` — full/sync/reindex pipeline orchestrator with progress yields
- `cli.py` — `index | sync | reindex | status` CLI commands

**TypeScript plugin (`src/`)** — OpenClaw plugin scaffold:
- `utils/` — config loader, TypeScript types, response envelope factory, LanceDB client
- `services/` — health state machine (HEALTHY/DEGRADED/UNAVAILABLE), vault watcher with debounce/batching, indexer bridge (subprocess spawner)
- `tools/` — 4 tool stubs: search, index, status, memory_store (OpenClaw wiring pending)
- `index.ts` — plugin entry point with health probe + vault watcher startup

**Config** (`obsidian-rag/config.json`, `openclaw.plugin.json`):
- 627 files / 3764 chunks indexed in dev vault

**Tests: 76 passing**
- Python: 64 pytest tests (chunker, security, vector_store, config)
- TypeScript: 12 vitest tests (lancedb client, response envelope)

## Bugs fixed

- LanceDB `tags` column filter: `LIKE '%tag%'` → `list_contains(tags, 'tag')` (List<String> column)
- LanceDB JS `db.list_tables()` returns `ListTablesResponse` object, not plain array
- LanceDB JS result score field: `_score` → `_distance`
- TypeScript regex literal with unescaped `/` in path-resolve regex
- Python: `create_table_if_not_exists` identity check → name comparison

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-10 22:56:50 -04:00
parent 18ad47e100
commit 5c281165c7
40 changed files with 5814 additions and 59 deletions

View File

@@ -0,0 +1,50 @@
/** Unit tests for security guard and response envelope utilities in TS. */
import { describe, it, expect } from "vitest";
import { makeEnvelope, errorEnvelope } from "../../src/utils/response.js";
describe("makeEnvelope", () => {
it("creates a healthy envelope with data", () => {
const envelope = makeEnvelope<string[]>(
"healthy",
["a", "b"],
null,
{ query_time_ms: 42 }
);
expect(envelope.status).toBe("healthy");
expect(envelope.data).toEqual(["a", "b"]);
expect(envelope.error).toBeNull();
expect(envelope.meta.query_time_ms).toBe(42);
});
it("creates a degraded envelope without data", () => {
const envelope = makeEnvelope("degraded", null, null, { chunks_scanned: 0 });
expect(envelope.status).toBe("degraded");
expect(envelope.data).toBeNull();
});
it("defaults meta fields", () => {
const envelope = makeEnvelope("healthy", [], null, {});
expect(envelope.meta.index_version).toBe("0.1.0");
expect(envelope.meta.vault_mtime).toBeDefined();
});
});
describe("errorEnvelope", () => {
it("creates an unavailable error envelope", () => {
const envelope = errorEnvelope(
"INDEX_NOT_FOUND",
"Vector index not found at expected path",
false,
"Run 'obsidian-rag index' to create the index"
);
expect(envelope.status).toBe("unavailable");
expect(envelope.data).toBeNull();
expect(envelope.error).toEqual({
code: "INDEX_NOT_FOUND",
message: "Vector index not found at expected path",
recoverable: false,
suggestion: "Run 'obsidian-rag index' to create the index",
});
});
});