UNPKG

cc-inspect-mcp

Version:

MCP server for inspecting and searching Claude Code conversation history with boolean search support

161 lines (160 loc) 5.43 kB
export class BooleanQueryParser { pos = 0; input = ''; parse(query) { this.input = query; this.pos = 0; return this.parseExpression(); } parseExpression() { let left = this.parseAndExpression(); while (this.consumeKeyword('OR')) { const right = this.parseAndExpression(); left = { type: 'OR', left, right }; } return left; } parseAndExpression() { let left = this.parseNotExpression(); while (this.pos < this.input.length && !this.peekKeyword('OR') && !this.peekToken(')')) { // Check for explicit AND or implicit AND (space) const hasAnd = this.consumeKeyword('AND'); // Skip whitespace this.skipWhitespace(); // If we hit OR, closing paren, or end of input, break if (this.pos >= this.input.length || this.peekKeyword('OR') || this.peekToken(')')) { break; } const right = this.parseNotExpression(); left = { type: 'AND', left, right }; } return left; } parseNotExpression() { if (this.consumeToken('-') || this.consumeKeyword('NOT')) { const child = this.parsePrimaryExpression(); return { type: 'NOT', child }; } return this.parsePrimaryExpression(); } parsePrimaryExpression() { this.skipWhitespace(); // Handle parentheses if (this.consumeToken('(')) { const expr = this.parseExpression(); if (!this.consumeToken(')')) { throw new Error(`Expected closing parenthesis at position ${this.pos}`); } return expr; } // Handle quoted phrases if (this.peekToken('"')) { return this.parsePhrase(); } // Handle regular terms return this.parseTerm(); } parseTerm() { this.skipWhitespace(); let term = ''; while (this.pos < this.input.length) { const char = this.input[this.pos]; // Stop at special characters or keywords if (char === ' ' || char === '(' || char === ')' || char === '"' || char === '-') { break; } // Check if we're at a keyword boundary if (this.peekKeyword('AND') || this.peekKeyword('OR') || this.peekKeyword('NOT')) { break; } term += char; this.pos++; } if (term.length === 0) { throw new Error(`Unexpected character at position ${this.pos}`); } return { type: 'TERM', value: term }; } parsePhrase() { this.consumeToken('"'); let phrase = ''; while (this.pos < this.input.length && this.input[this.pos] !== '"') { phrase += this.input[this.pos]; this.pos++; } this.consumeToken('"'); return { type: 'PHRASE', value: phrase }; } skipWhitespace() { while (this.pos < this.input.length && this.input[this.pos] === ' ') { this.pos++; } } peekToken(token) { this.skipWhitespace(); return this.input.substring(this.pos, this.pos + token.length) === token; } consumeToken(token) { this.skipWhitespace(); if (this.input.substring(this.pos, this.pos + token.length) === token) { this.pos += token.length; return true; } return false; } peekKeyword(keyword) { this.skipWhitespace(); const ahead = this.input.substring(this.pos, this.pos + keyword.length + 1); return (ahead === keyword + ' ' || ahead === keyword + '(' || this.input.substring(this.pos) === keyword); } consumeKeyword(keyword) { if (this.peekKeyword(keyword)) { this.pos += keyword.length; return true; } return false; } } export class BooleanQueryEvaluator { evaluate(query, text) { const lowerText = text.toLowerCase(); switch (query.type) { case 'TERM': // Simple substring match for terms return lowerText.includes(query.value.toLowerCase()); case 'PHRASE': return lowerText.includes(query.value.toLowerCase()); case 'AND': return this.evaluate(query.left, text) && this.evaluate(query.right, text); case 'OR': return this.evaluate(query.left, text) || this.evaluate(query.right, text); case 'NOT': return !this.evaluate(query.child, text); default: return false; } } // Extract all positive terms for scoring extractTerms(query, terms = []) { switch (query.type) { case 'TERM': case 'PHRASE': if (query.value) terms.push(query.value); break; case 'AND': case 'OR': if (query.left) this.extractTerms(query.left, terms); if (query.right) this.extractTerms(query.right, terms); break; case 'NOT': // Don't include NOT terms in scoring break; } return terms; } }