Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2597,8 +2597,9 @@ function buildFTS5Query(query: string): string | null {
* Returns error message if invalid, null if valid.
*/
export function validateSemanticQuery(query: string): string | null {
// Check for negation syntax
if (/-\w/.test(query) || /-"/.test(query)) {
// Check for negation syntax — only at token boundaries (start of string or after whitespace).
// Hyphenated words like "real-time" or "write-ahead" must not trigger this.
if (/(^|\s)-[\w"]/.test(query)) {
return 'Negation (-term) is not supported in vec/hyde queries. Use lex for exclusions.';
}
return null;
Expand Down
58 changes: 57 additions & 1 deletion test/structured-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,17 +361,73 @@ describe("lex query syntax", () => {
expect(validateSemanticQuery("what is the CAP theorem")).toBeNull();
});

test("rejects negation syntax", () => {
test("rejects negation at start of query", () => {
expect(validateSemanticQuery("-redis connection pooling")).toContain("Negation");
});

test("rejects negation after space", () => {
expect(validateSemanticQuery("performance -sports")).toContain("Negation");
});

test("rejects negated quoted phrase", () => {
expect(validateSemanticQuery('-"exact phrase"')).toContain("Negation");
});

test("rejects multiple negations", () => {
expect(validateSemanticQuery("error handling -java -python")).toContain("Negation");
});

test("rejects negation after leading whitespace", () => {
expect(validateSemanticQuery(" -term at start")).toContain("Negation");
});

test("rejects negation after tab", () => {
expect(validateSemanticQuery("foo\t-bar")).toContain("Negation");
});

test("accepts hyphenated compound words", () => {
expect(validateSemanticQuery("long-lived server shared across clients")).toBeNull();
expect(validateSemanticQuery("real-time voice processing pipeline")).toBeNull();
expect(validateSemanticQuery("how does the rate-limiter handle burst traffic")).toBeNull();
expect(validateSemanticQuery("self-hosted deployment options")).toBeNull();
expect(validateSemanticQuery("multi-client session architecture")).toBeNull();
expect(validateSemanticQuery("cross-platform compatibility")).toBeNull();
expect(validateSemanticQuery("non-blocking I/O model")).toBeNull();
expect(validateSemanticQuery("in-memory caching strategy")).toBeNull();
expect(validateSemanticQuery("write-ahead log for crash recovery")).toBeNull();
expect(validateSemanticQuery("copy-on-write semantics")).toBeNull();
});

test("accepts multiple hyphens in a phrase", () => {
expect(validateSemanticQuery("state-of-the-art embedding models")).toBeNull();
expect(validateSemanticQuery("end-to-end testing")).toBeNull();
expect(validateSemanticQuery("man-in-the-middle attack prevention")).toBeNull();
});

test("accepts multiple hyphenated words in one query", () => {
expect(validateSemanticQuery("built-in vs add-on features")).toBeNull();
});

test("accepts short hyphenated terms", () => {
expect(validateSemanticQuery("A-B testing for ML models")).toBeNull();
expect(validateSemanticQuery("e-commerce platform")).toBeNull();
});

test("accepts bare hyphen without word character", () => {
expect(validateSemanticQuery("-")).toBeNull();
});

test("accepts hyde-style hypothetical answers", () => {
expect(validateSemanticQuery(
"The CAP theorem states that a distributed system cannot simultaneously provide consistency, availability, and partition tolerance."
)).toBeNull();
});

test("accepts hyde with hyphenated words", () => {
expect(validateSemanticQuery(
"HTTP transport runs a single long-lived daemon shared across all clients, avoiding per-session model re-loading."
)).toBeNull();
});
});

describe("validateLexQuery", () => {
Expand Down