@ai-devkit/memory 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/database/connection.d.ts.map +1 -1
- package/dist/database/connection.js +5 -1
- package/dist/database/connection.js.map +1 -1
- package/dist/handlers/search.d.ts.map +1 -1
- package/dist/handlers/search.js +15 -9
- package/dist/handlers/search.js.map +1 -1
- package/dist/services/ranker.d.ts +2 -1
- package/dist/services/ranker.d.ts.map +1 -1
- package/dist/services/ranker.js +2 -2
- package/dist/services/ranker.js.map +1 -1
- package/dist/services/search.d.ts +6 -0
- package/dist/services/search.d.ts.map +1 -1
- package/dist/services/search.js +93 -18
- package/dist/services/search.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/database/connection.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAMtC;;GAEG;AACH,eAAO,MAAM,eAAe,QAA6C,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,OAAO,GAAE,eAAoB;
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/database/connection.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAMtC;;GAEG;AACH,eAAO,MAAM,eAAe,QAA6C,CAAC;AAE1E,MAAM,WAAW,eAAe;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,qBAAa,kBAAkB;IAC3B,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,OAAO,GAAE,eAAoB;IAezC,OAAO,CAAC,SAAS;IASjB,IAAI,QAAQ,IAAI,QAAQ,CAAC,QAAQ,CAEhC;IAED,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,KAAK,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,CAAC,EAAE;IAIlD,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,CAAC,GAAG,SAAS;IAI/D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,QAAQ,CAAC,SAAS;IAGhE,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC;IAI9B,KAAK,IAAI,IAAI;CAKhB;AAKD,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,kBAAkB,CAWzE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAMpC"}
|
|
@@ -17,12 +17,16 @@ export class DatabaseConnection {
|
|
|
17
17
|
});
|
|
18
18
|
this.db = new Database(this.dbPath, {
|
|
19
19
|
readonly: options.readonly ?? false,
|
|
20
|
+
timeout: 5000,
|
|
20
21
|
verbose: options.verbose ? console.log : undefined
|
|
21
22
|
});
|
|
22
23
|
this.configure();
|
|
23
24
|
}
|
|
24
25
|
configure() {
|
|
25
|
-
this.db.pragma('journal_mode
|
|
26
|
+
const journalMode = this.db.pragma('journal_mode', {
|
|
27
|
+
simple: true
|
|
28
|
+
});
|
|
29
|
+
if (journalMode.toLowerCase() !== 'wal') this.db.pragma('journal_mode = WAL');
|
|
26
30
|
this.db.pragma('foreign_keys = ON');
|
|
27
31
|
this.db.pragma('synchronous = NORMAL');
|
|
28
32
|
this.db.pragma('busy_timeout = 5000');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/database/connection.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { mkdirSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { homedir } from 'os';\nimport { initializeSchema } from './schema.js';\n\n/**\n * Default database path: ~/.ai-devkit/memory.db\n */\nexport const DEFAULT_DB_PATH = join(homedir(), '.ai-devkit', 'memory.db');\n\nexport interface DatabaseOptions {\n dbPath?: string;\n verbose?: boolean;\n readonly?: boolean;\n}\n\nexport class DatabaseConnection {\n private db: Database.Database;\n private readonly dbPath: string;\n\n constructor(options: DatabaseOptions = {}) {\n this.dbPath = options.dbPath ?? DEFAULT_DB_PATH;\n\n const dir = dirname(this.dbPath);\n mkdirSync(dir, { recursive: true });\n\n this.db = new Database(this.dbPath, {\n readonly: options.readonly ?? false,\n verbose: options.verbose ? console.log : undefined,\n });\n\n this.configure();\n }\n\n private configure(): void {\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('synchronous = NORMAL');\n this.db.pragma('busy_timeout = 5000');\n this.db.pragma('mmap_size = 268435456');\n }\n\n get instance(): Database.Database {\n return this.db;\n }\n\n get path(): string {\n return this.dbPath;\n }\n\n get isOpen(): boolean {\n return this.db.open;\n }\n\n query<T>(sql: string, params: unknown[] = []): T[] {\n return this.db.prepare(sql).all(...params) as T[];\n }\n\n queryOne<T>(sql: string, params: unknown[] = []): T | undefined {\n return this.db.prepare(sql).get(...params) as T | undefined;\n }\n\n execute(sql: string, params: unknown[] = []): Database.RunResult {\n return this.db.prepare(sql).run(...params);\n }\n transaction<T>(fn: () => T): T {\n return this.db.transaction(fn)();\n }\n\n close(): void {\n if (this.db.open) {\n this.db.close();\n }\n }\n}\n\nlet instance: DatabaseConnection | null = null;\nlet schemaInitialized = false;\n\nexport function getDatabase(options?: DatabaseOptions): DatabaseConnection {\n if (!instance) {\n instance = new DatabaseConnection(options);\n }\n\n if (!schemaInitialized) {\n initializeSchema(instance);\n schemaInitialized = true;\n }\n\n return instance;\n}\n\nexport function closeDatabase(): void {\n if (instance) {\n instance.close();\n instance = null;\n schemaInitialized = false;\n }\n}"],"names":["Database","mkdirSync","dirname","join","homedir","initializeSchema","DEFAULT_DB_PATH","DatabaseConnection","db","dbPath","options","dir","recursive","readonly","verbose","console","log","undefined","configure","pragma","instance","path","isOpen","open","query","sql","params","prepare","all","queryOne","get","execute","run","transaction","fn","close","schemaInitialized","getDatabase","closeDatabase"],"mappings":"AAAA,OAAOA,cAAc,iBAAiB;AACtC,SAASC,SAAS,QAAQ,KAAK;AAC/B,SAASC,OAAO,EAAEC,IAAI,QAAQ,OAAO;AACrC,SAASC,OAAO,QAAQ,KAAK;AAC7B,SAASC,gBAAgB,QAAQ,cAAc;AAE/C;;CAEC,GACD,OAAO,MAAMC,kBAAkBH,KAAKC,WAAW,cAAc,aAAa;AAQ1E,OAAO,MAAMG;IACDC,GAAsB;IACbC,OAAe;IAEhC,YAAYC,UAA2B,CAAC,CAAC,CAAE;QACvC,IAAI,CAACD,MAAM,GAAGC,QAAQD,MAAM,IAAIH;QAEhC,MAAMK,MAAMT,QAAQ,IAAI,CAACO,MAAM;QAC/BR,UAAUU,KAAK;YAAEC,WAAW;QAAK;QAEjC,IAAI,CAACJ,EAAE,GAAG,IAAIR,SAAS,IAAI,CAACS,MAAM,EAAE;YAChCI,UAAUH,QAAQG,QAAQ,IAAI;YAC9BC,
|
|
1
|
+
{"version":3,"sources":["../../src/database/connection.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { mkdirSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { homedir } from 'os';\nimport { initializeSchema } from './schema.js';\n\n/**\n * Default database path: ~/.ai-devkit/memory.db\n */\nexport const DEFAULT_DB_PATH = join(homedir(), '.ai-devkit', 'memory.db');\n\nexport interface DatabaseOptions {\n dbPath?: string;\n verbose?: boolean;\n readonly?: boolean;\n}\n\nexport class DatabaseConnection {\n private db: Database.Database;\n private readonly dbPath: string;\n\n constructor(options: DatabaseOptions = {}) {\n this.dbPath = options.dbPath ?? DEFAULT_DB_PATH;\n\n const dir = dirname(this.dbPath);\n mkdirSync(dir, { recursive: true });\n\n this.db = new Database(this.dbPath, {\n readonly: options.readonly ?? false,\n timeout: 5000,\n verbose: options.verbose ? console.log : undefined,\n });\n\n this.configure();\n }\n\n private configure(): void {\n const journalMode = this.db.pragma('journal_mode', { simple: true }) as string;\n if (journalMode.toLowerCase() !== 'wal') this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('synchronous = NORMAL');\n this.db.pragma('busy_timeout = 5000');\n this.db.pragma('mmap_size = 268435456');\n }\n\n get instance(): Database.Database {\n return this.db;\n }\n\n get path(): string {\n return this.dbPath;\n }\n\n get isOpen(): boolean {\n return this.db.open;\n }\n\n query<T>(sql: string, params: unknown[] = []): T[] {\n return this.db.prepare(sql).all(...params) as T[];\n }\n\n queryOne<T>(sql: string, params: unknown[] = []): T | undefined {\n return this.db.prepare(sql).get(...params) as T | undefined;\n }\n\n execute(sql: string, params: unknown[] = []): Database.RunResult {\n return this.db.prepare(sql).run(...params);\n }\n transaction<T>(fn: () => T): T {\n return this.db.transaction(fn)();\n }\n\n close(): void {\n if (this.db.open) {\n this.db.close();\n }\n }\n}\n\nlet instance: DatabaseConnection | null = null;\nlet schemaInitialized = false;\n\nexport function getDatabase(options?: DatabaseOptions): DatabaseConnection {\n if (!instance) {\n instance = new DatabaseConnection(options);\n }\n\n if (!schemaInitialized) {\n initializeSchema(instance);\n schemaInitialized = true;\n }\n\n return instance;\n}\n\nexport function closeDatabase(): void {\n if (instance) {\n instance.close();\n instance = null;\n schemaInitialized = false;\n }\n}\n"],"names":["Database","mkdirSync","dirname","join","homedir","initializeSchema","DEFAULT_DB_PATH","DatabaseConnection","db","dbPath","options","dir","recursive","readonly","timeout","verbose","console","log","undefined","configure","journalMode","pragma","simple","toLowerCase","instance","path","isOpen","open","query","sql","params","prepare","all","queryOne","get","execute","run","transaction","fn","close","schemaInitialized","getDatabase","closeDatabase"],"mappings":"AAAA,OAAOA,cAAc,iBAAiB;AACtC,SAASC,SAAS,QAAQ,KAAK;AAC/B,SAASC,OAAO,EAAEC,IAAI,QAAQ,OAAO;AACrC,SAASC,OAAO,QAAQ,KAAK;AAC7B,SAASC,gBAAgB,QAAQ,cAAc;AAE/C;;CAEC,GACD,OAAO,MAAMC,kBAAkBH,KAAKC,WAAW,cAAc,aAAa;AAQ1E,OAAO,MAAMG;IACDC,GAAsB;IACbC,OAAe;IAEhC,YAAYC,UAA2B,CAAC,CAAC,CAAE;QACvC,IAAI,CAACD,MAAM,GAAGC,QAAQD,MAAM,IAAIH;QAEhC,MAAMK,MAAMT,QAAQ,IAAI,CAACO,MAAM;QAC/BR,UAAUU,KAAK;YAAEC,WAAW;QAAK;QAEjC,IAAI,CAACJ,EAAE,GAAG,IAAIR,SAAS,IAAI,CAACS,MAAM,EAAE;YAChCI,UAAUH,QAAQG,QAAQ,IAAI;YAC9BC,SAAS;YACTC,SAASL,QAAQK,OAAO,GAAGC,QAAQC,GAAG,GAAGC;QAC7C;QAEA,IAAI,CAACC,SAAS;IAClB;IAEQA,YAAkB;QACtB,MAAMC,cAAc,IAAI,CAACZ,EAAE,CAACa,MAAM,CAAC,gBAAgB;YAAEC,QAAQ;QAAK;QAClE,IAAIF,YAAYG,WAAW,OAAO,OAAO,IAAI,CAACf,EAAE,CAACa,MAAM,CAAC;QACxD,IAAI,CAACb,EAAE,CAACa,MAAM,CAAC;QACf,IAAI,CAACb,EAAE,CAACa,MAAM,CAAC;QACf,IAAI,CAACb,EAAE,CAACa,MAAM,CAAC;QACf,IAAI,CAACb,EAAE,CAACa,MAAM,CAAC;IACnB;IAEA,IAAIG,WAA8B;QAC9B,OAAO,IAAI,CAAChB,EAAE;IAClB;IAEA,IAAIiB,OAAe;QACf,OAAO,IAAI,CAAChB,MAAM;IACtB;IAEA,IAAIiB,SAAkB;QAClB,OAAO,IAAI,CAAClB,EAAE,CAACmB,IAAI;IACvB;IAEAC,MAASC,GAAW,EAAEC,SAAoB,EAAE,EAAO;QAC/C,OAAO,IAAI,CAACtB,EAAE,CAACuB,OAAO,CAACF,KAAKG,GAAG,IAAIF;IACvC;IAEAG,SAAYJ,GAAW,EAAEC,SAAoB,EAAE,EAAiB;QAC5D,OAAO,IAAI,CAACtB,EAAE,CAACuB,OAAO,CAACF,KAAKK,GAAG,IAAIJ;IACvC;IAEAK,QAAQN,GAAW,EAAEC,SAAoB,EAAE,EAAsB;QAC7D,OAAO,IAAI,CAACtB,EAAE,CAACuB,OAAO,CAACF,KAAKO,GAAG,IAAIN;IACvC;IACAO,YAAeC,EAAW,EAAK;QAC3B,OAAO,IAAI,CAAC9B,EAAE,CAAC6B,WAAW,CAACC;IAC/B;IAEAC,QAAc;QACV,IAAI,IAAI,CAAC/B,EAAE,CAACmB,IAAI,EAAE;YACd,IAAI,CAACnB,EAAE,CAAC+B,KAAK;QACjB;IACJ;AACJ;AAEA,IAAIf,WAAsC;AAC1C,IAAIgB,oBAAoB;AAExB,OAAO,SAASC,YAAY/B,OAAyB;IACjD,IAAI,CAACc,UAAU;QACXA,WAAW,IAAIjB,mBAAmBG;IACtC;IAEA,IAAI,CAAC8B,mBAAmB;QACpBnC,iBAAiBmB;QACjBgB,oBAAoB;IACxB;IAEA,OAAOhB;AACX;AAEA,OAAO,SAASkB;IACZ,IAAIlB,UAAU;QACVA,SAASe,KAAK;QACdf,WAAW;QACXgB,oBAAoB;IACxB;AACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/handlers/search.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/handlers/search.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAkBrF,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,qBAAqB,CAmDlF"}
|
package/dist/handlers/search.js
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
1
|
import { getDatabase } from '../database/index.js';
|
|
2
|
-
import { buildFtsQuery, buildSearchQuery, buildSimpleQuery } from '../services/search.js';
|
|
2
|
+
import { buildBroadFtsQuery, buildBroadSearchQuery, buildFtsQuery, buildSearchQuery, buildSimpleQuery, normalizeSearchTokens } from '../services/search.js';
|
|
3
3
|
import { rankResults } from '../services/ranker.js';
|
|
4
4
|
import { ValidationError } from '../utils/errors.js';
|
|
5
5
|
const DEFAULT_LIMIT = 5;
|
|
6
6
|
const MAX_LIMIT = 20;
|
|
7
7
|
const MIN_QUERY_LENGTH = 3;
|
|
8
8
|
const MAX_QUERY_LENGTH = 500;
|
|
9
|
+
const BROAD_CANDIDATE_MULTIPLIER = 4;
|
|
9
10
|
export function searchKnowledge(input) {
|
|
10
11
|
validateSearchInput(input);
|
|
11
12
|
const db = getDatabase();
|
|
12
13
|
const limit = Math.min(Math.max(input.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
|
|
13
14
|
const ftsQuery = buildFtsQuery(input.query);
|
|
15
|
+
const tokens = normalizeSearchTokens(input.query);
|
|
14
16
|
let rows;
|
|
17
|
+
let strategy;
|
|
15
18
|
if (ftsQuery === '') {
|
|
16
|
-
//
|
|
19
|
+
// A query containing no searchable terms returns recent items.
|
|
17
20
|
const { sql, params } = buildSimpleQuery(input.scope, limit);
|
|
18
21
|
rows = db.query(sql, params);
|
|
22
|
+
strategy = 'recent';
|
|
19
23
|
} else {
|
|
20
24
|
// Full-text search with BM25
|
|
21
25
|
const { sql, params } = buildSearchQuery(ftsQuery, input.scope, limit * 2);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
rows = db.query(
|
|
26
|
+
rows = db.query(sql, params);
|
|
27
|
+
strategy = 'strict';
|
|
28
|
+
if (rows.length === 0 && tokens.length >= 2) {
|
|
29
|
+
const broadQuery = buildBroadFtsQuery(input.query);
|
|
30
|
+
const broad = buildBroadSearchQuery(broadQuery, tokens, input.scope, limit * BROAD_CANDIDATE_MULTIPLIER);
|
|
31
|
+
rows = db.query(broad.sql, broad.params);
|
|
32
|
+
strategy = 'broad';
|
|
28
33
|
}
|
|
29
34
|
}
|
|
30
35
|
// Apply ranking with tag and scope boosts
|
|
@@ -37,7 +42,8 @@ export function searchKnowledge(input) {
|
|
|
37
42
|
return {
|
|
38
43
|
results,
|
|
39
44
|
totalMatches: ranked.length,
|
|
40
|
-
query: input.query
|
|
45
|
+
query: input.query,
|
|
46
|
+
strategy
|
|
41
47
|
};
|
|
42
48
|
}
|
|
43
49
|
function validateSearchInput(input) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/handlers/search.ts"],"sourcesContent":["import { getDatabase } from '../database/index.js';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../src/handlers/search.ts"],"sourcesContent":["import { getDatabase } from '../database/index.js';\nimport {\n buildBroadFtsQuery,\n buildBroadSearchQuery,\n buildFtsQuery,\n buildSearchQuery,\n buildSimpleQuery,\n normalizeSearchTokens,\n} from '../services/search.js';\nimport { rankResults } from '../services/ranker.js';\nimport { ValidationError } from '../utils/errors.js';\nimport type { SearchKnowledgeInput, SearchKnowledgeResult } from '../types/index.js';\n\nconst DEFAULT_LIMIT = 5;\nconst MAX_LIMIT = 20;\nconst MIN_QUERY_LENGTH = 3;\nconst MAX_QUERY_LENGTH = 500;\nconst BROAD_CANDIDATE_MULTIPLIER = 4;\n\ninterface RawSearchRow {\n id: string;\n title: string;\n content: string;\n tags: string;\n scope: string;\n bm25_score: number;\n token_coverage?: number;\n}\n\nexport function searchKnowledge(input: SearchKnowledgeInput): SearchKnowledgeResult {\n validateSearchInput(input);\n\n const db = getDatabase();\n const limit = Math.min(Math.max(input.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);\n const ftsQuery = buildFtsQuery(input.query);\n const tokens = normalizeSearchTokens(input.query);\n\n let rows: RawSearchRow[];\n let strategy: SearchKnowledgeResult['strategy'];\n\n if (ftsQuery === '') {\n // A query containing no searchable terms returns recent items.\n const { sql, params } = buildSimpleQuery(input.scope, limit);\n rows = db.query<RawSearchRow>(sql, params);\n strategy = 'recent';\n } else {\n // Full-text search with BM25\n const { sql, params } = buildSearchQuery(ftsQuery, input.scope, limit * 2);\n\n rows = db.query<RawSearchRow>(sql, params);\n strategy = 'strict';\n\n if (rows.length === 0 && tokens.length >= 2) {\n const broadQuery = buildBroadFtsQuery(input.query);\n const broad = buildBroadSearchQuery(\n broadQuery,\n tokens,\n input.scope,\n limit * BROAD_CANDIDATE_MULTIPLIER,\n );\n rows = db.query<RawSearchRow>(broad.sql, broad.params);\n strategy = 'broad';\n }\n }\n\n // Apply ranking with tag and scope boosts\n const ranked = rankResults(rows, {\n contextTags: input.contextTags,\n queryScope: input.scope,\n });\n\n // Limit to requested count\n const results = ranked.slice(0, limit);\n\n return {\n results,\n totalMatches: ranked.length,\n query: input.query,\n strategy,\n };\n}\n\nfunction validateSearchInput(input: SearchKnowledgeInput): void {\n const errors: string[] = [];\n\n if (!input.query || typeof input.query !== 'string') {\n errors.push('Query is required');\n } else {\n const trimmed = input.query.trim();\n if (trimmed.length < MIN_QUERY_LENGTH) {\n errors.push(`Query must be at least ${MIN_QUERY_LENGTH} characters`);\n }\n if (trimmed.length > MAX_QUERY_LENGTH) {\n errors.push(`Query must be at most ${MAX_QUERY_LENGTH} characters`);\n }\n }\n\n if (input.limit !== undefined) {\n if (typeof input.limit !== 'number' || input.limit < 1) {\n errors.push('Limit must be a positive number');\n }\n }\n\n if (errors.length > 0) {\n throw new ValidationError(errors.join('; '), { errors });\n }\n}\n"],"names":["getDatabase","buildBroadFtsQuery","buildBroadSearchQuery","buildFtsQuery","buildSearchQuery","buildSimpleQuery","normalizeSearchTokens","rankResults","ValidationError","DEFAULT_LIMIT","MAX_LIMIT","MIN_QUERY_LENGTH","MAX_QUERY_LENGTH","BROAD_CANDIDATE_MULTIPLIER","searchKnowledge","input","validateSearchInput","db","limit","Math","min","max","ftsQuery","query","tokens","rows","strategy","sql","params","scope","length","broadQuery","broad","ranked","contextTags","queryScope","results","slice","totalMatches","errors","push","trimmed","trim","undefined","join"],"mappings":"AAAA,SAASA,WAAW,QAAQ,uBAAuB;AACnD,SACIC,kBAAkB,EAClBC,qBAAqB,EACrBC,aAAa,EACbC,gBAAgB,EAChBC,gBAAgB,EAChBC,qBAAqB,QAClB,wBAAwB;AAC/B,SAASC,WAAW,QAAQ,wBAAwB;AACpD,SAASC,eAAe,QAAQ,qBAAqB;AAGrD,MAAMC,gBAAgB;AACtB,MAAMC,YAAY;AAClB,MAAMC,mBAAmB;AACzB,MAAMC,mBAAmB;AACzB,MAAMC,6BAA6B;AAYnC,OAAO,SAASC,gBAAgBC,KAA2B;IACvDC,oBAAoBD;IAEpB,MAAME,KAAKjB;IACX,MAAMkB,QAAQC,KAAKC,GAAG,CAACD,KAAKE,GAAG,CAACN,MAAMG,KAAK,IAAIT,eAAe,IAAIC;IAClE,MAAMY,WAAWnB,cAAcY,MAAMQ,KAAK;IAC1C,MAAMC,SAASlB,sBAAsBS,MAAMQ,KAAK;IAEhD,IAAIE;IACJ,IAAIC;IAEJ,IAAIJ,aAAa,IAAI;QACjB,+DAA+D;QAC/D,MAAM,EAAEK,GAAG,EAAEC,MAAM,EAAE,GAAGvB,iBAAiBU,MAAMc,KAAK,EAAEX;QACtDO,OAAOR,GAAGM,KAAK,CAAeI,KAAKC;QACnCF,WAAW;IACf,OAAO;QACH,6BAA6B;QAC7B,MAAM,EAAEC,GAAG,EAAEC,MAAM,EAAE,GAAGxB,iBAAiBkB,UAAUP,MAAMc,KAAK,EAAEX,QAAQ;QAExEO,OAAOR,GAAGM,KAAK,CAAeI,KAAKC;QACnCF,WAAW;QAEX,IAAID,KAAKK,MAAM,KAAK,KAAKN,OAAOM,MAAM,IAAI,GAAG;YACzC,MAAMC,aAAa9B,mBAAmBc,MAAMQ,KAAK;YACjD,MAAMS,QAAQ9B,sBACV6B,YACAP,QACAT,MAAMc,KAAK,EACXX,QAAQL;YAEZY,OAAOR,GAAGM,KAAK,CAAeS,MAAML,GAAG,EAAEK,MAAMJ,MAAM;YACrDF,WAAW;QACf;IACJ;IAEA,0CAA0C;IAC1C,MAAMO,SAAS1B,YAAYkB,MAAM;QAC7BS,aAAanB,MAAMmB,WAAW;QAC9BC,YAAYpB,MAAMc,KAAK;IAC3B;IAEA,2BAA2B;IAC3B,MAAMO,UAAUH,OAAOI,KAAK,CAAC,GAAGnB;IAEhC,OAAO;QACHkB;QACAE,cAAcL,OAAOH,MAAM;QAC3BP,OAAOR,MAAMQ,KAAK;QAClBG;IACJ;AACJ;AAEA,SAASV,oBAAoBD,KAA2B;IACpD,MAAMwB,SAAmB,EAAE;IAE3B,IAAI,CAACxB,MAAMQ,KAAK,IAAI,OAAOR,MAAMQ,KAAK,KAAK,UAAU;QACjDgB,OAAOC,IAAI,CAAC;IAChB,OAAO;QACH,MAAMC,UAAU1B,MAAMQ,KAAK,CAACmB,IAAI;QAChC,IAAID,QAAQX,MAAM,GAAGnB,kBAAkB;YACnC4B,OAAOC,IAAI,CAAC,CAAC,uBAAuB,EAAE7B,iBAAiB,WAAW,CAAC;QACvE;QACA,IAAI8B,QAAQX,MAAM,GAAGlB,kBAAkB;YACnC2B,OAAOC,IAAI,CAAC,CAAC,sBAAsB,EAAE5B,iBAAiB,WAAW,CAAC;QACtE;IACJ;IAEA,IAAIG,MAAMG,KAAK,KAAKyB,WAAW;QAC3B,IAAI,OAAO5B,MAAMG,KAAK,KAAK,YAAYH,MAAMG,KAAK,GAAG,GAAG;YACpDqB,OAAOC,IAAI,CAAC;QAChB;IACJ;IAEA,IAAID,OAAOT,MAAM,GAAG,GAAG;QACnB,MAAM,IAAItB,gBAAgB+B,OAAOK,IAAI,CAAC,OAAO;YAAEL;QAAO;IAC1D;AACJ"}
|
|
@@ -6,6 +6,7 @@ interface RawSearchResult {
|
|
|
6
6
|
tags: string;
|
|
7
7
|
scope: string;
|
|
8
8
|
bm25_score: number;
|
|
9
|
+
token_coverage?: number;
|
|
9
10
|
}
|
|
10
11
|
interface RankingContext {
|
|
11
12
|
contextTags?: string[];
|
|
@@ -14,7 +15,7 @@ interface RankingContext {
|
|
|
14
15
|
/**
|
|
15
16
|
* Apply ranking formula to search results
|
|
16
17
|
*
|
|
17
|
-
* Formula: final_score = bm25_score × tag_boost + scope_boost
|
|
18
|
+
* Formula: final_score = bm25_score × token_coverage × tag_boost + scope_boost
|
|
18
19
|
*/
|
|
19
20
|
export declare function rankResults(results: RawSearchResult[], context: RankingContext): SearchResultItem[];
|
|
20
21
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ranker.d.ts","sourceRoot":"","sources":["../../src/services/ranker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,UAAU,eAAe;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ranker.d.ts","sourceRoot":"","sources":["../../src/services/ranker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,UAAU,eAAe;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,UAAU,cAAc;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAwCD;;;;GAIG;AACH,wBAAgB,WAAW,CACvB,OAAO,EAAE,eAAe,EAAE,EAC1B,OAAO,EAAE,cAAc,GACxB,gBAAgB,EAAE,CAgCpB"}
|
package/dist/services/ranker.js
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
/**
|
|
33
33
|
* Apply ranking formula to search results
|
|
34
34
|
*
|
|
35
|
-
* Formula: final_score = bm25_score × tag_boost + scope_boost
|
|
35
|
+
* Formula: final_score = bm25_score × token_coverage × tag_boost + scope_boost
|
|
36
36
|
*/ export function rankResults(results, context) {
|
|
37
37
|
const ranked = results.map((result)=>{
|
|
38
38
|
let tags;
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
// BM25 returns negative values (closer to 0 = better match)
|
|
47
47
|
// We negate it to make higher values better
|
|
48
48
|
const normalizedBm25 = -result.bm25_score;
|
|
49
|
-
const finalScore = normalizedBm25 * tagBoost + scopeBoost;
|
|
49
|
+
const finalScore = normalizedBm25 * (result.token_coverage ?? 1) * tagBoost + scopeBoost;
|
|
50
50
|
return {
|
|
51
51
|
id: result.id,
|
|
52
52
|
title: result.title,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/ranker.ts"],"sourcesContent":["import type { SearchResultItem } from '../types/index.js';\n\ninterface RawSearchResult {\n id: string;\n title: string;\n content: string;\n tags: string;\n scope: string;\n bm25_score: number;\n}\n\ninterface RankingContext {\n contextTags?: string[];\n queryScope?: string | null;\n}\n\n/**\n * Calculate tag boost based on matching contextTags\n * +10% per matching tag\n */\nfunction calculateTagBoost(itemTags: string[], contextTags: string[]): number {\n if (!contextTags || contextTags.length === 0) {\n return 1.0;\n }\n\n const itemTagsLower = itemTags.map(t => t.toLowerCase());\n const contextTagsLower = contextTags.map(t => t.toLowerCase());\n\n let matchCount = 0;\n for (const tag of contextTagsLower) {\n if (itemTagsLower.includes(tag)) {\n matchCount++;\n }\n }\n\n return 1 + (matchCount * 0.1);\n}\n\n/**\n * Calculate scope boost\n * +0.5 if scope matches query scope\n * +0.2 if global\n * 0 otherwise\n */\nfunction calculateScopeBoost(itemScope: string, queryScope?: string | null): number {\n if (queryScope && itemScope === queryScope) {\n return 0.5;\n }\n if (itemScope === 'global') {\n return 0.2;\n }\n return 0;\n}\n\n/**\n * Apply ranking formula to search results\n * \n * Formula: final_score = bm25_score × tag_boost + scope_boost\n */\nexport function rankResults(\n results: RawSearchResult[],\n context: RankingContext\n): SearchResultItem[] {\n const ranked = results.map(result => {\n let tags: string[];\n try {\n tags = JSON.parse(result.tags) as string[];\n } catch {\n tags = [];\n }\n\n const tagBoost = calculateTagBoost(tags, context.contextTags ?? []);\n const scopeBoost = calculateScopeBoost(result.scope, context.queryScope);\n\n // BM25 returns negative values (closer to 0 = better match)\n // We negate it to make higher values better\n const normalizedBm25 = -result.bm25_score;\n\n const finalScore = (normalizedBm25 * tagBoost) + scopeBoost;\n\n return {\n id: result.id,\n title: result.title,\n content: result.content,\n tags,\n scope: result.scope,\n score: Math.round(finalScore * 1000) / 1000,\n };\n });\n\n // Sort by score descending\n ranked.sort((a, b) => b.score - a.score);\n\n return ranked;\n}\n"],"names":["calculateTagBoost","itemTags","contextTags","length","itemTagsLower","map","t","toLowerCase","contextTagsLower","matchCount","tag","includes","calculateScopeBoost","itemScope","queryScope","rankResults","results","context","ranked","result","tags","JSON","parse","tagBoost","scopeBoost","scope","normalizedBm25","bm25_score","finalScore","id","title","content","score","Math","round","sort","a","b"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/services/ranker.ts"],"sourcesContent":["import type { SearchResultItem } from '../types/index.js';\n\ninterface RawSearchResult {\n id: string;\n title: string;\n content: string;\n tags: string;\n scope: string;\n bm25_score: number;\n token_coverage?: number;\n}\n\ninterface RankingContext {\n contextTags?: string[];\n queryScope?: string | null;\n}\n\n/**\n * Calculate tag boost based on matching contextTags\n * +10% per matching tag\n */\nfunction calculateTagBoost(itemTags: string[], contextTags: string[]): number {\n if (!contextTags || contextTags.length === 0) {\n return 1.0;\n }\n\n const itemTagsLower = itemTags.map(t => t.toLowerCase());\n const contextTagsLower = contextTags.map(t => t.toLowerCase());\n\n let matchCount = 0;\n for (const tag of contextTagsLower) {\n if (itemTagsLower.includes(tag)) {\n matchCount++;\n }\n }\n\n return 1 + (matchCount * 0.1);\n}\n\n/**\n * Calculate scope boost\n * +0.5 if scope matches query scope\n * +0.2 if global\n * 0 otherwise\n */\nfunction calculateScopeBoost(itemScope: string, queryScope?: string | null): number {\n if (queryScope && itemScope === queryScope) {\n return 0.5;\n }\n if (itemScope === 'global') {\n return 0.2;\n }\n return 0;\n}\n\n/**\n * Apply ranking formula to search results\n * \n * Formula: final_score = bm25_score × token_coverage × tag_boost + scope_boost\n */\nexport function rankResults(\n results: RawSearchResult[],\n context: RankingContext\n): SearchResultItem[] {\n const ranked = results.map(result => {\n let tags: string[];\n try {\n tags = JSON.parse(result.tags) as string[];\n } catch {\n tags = [];\n }\n\n const tagBoost = calculateTagBoost(tags, context.contextTags ?? []);\n const scopeBoost = calculateScopeBoost(result.scope, context.queryScope);\n\n // BM25 returns negative values (closer to 0 = better match)\n // We negate it to make higher values better\n const normalizedBm25 = -result.bm25_score;\n\n const finalScore = (normalizedBm25 * (result.token_coverage ?? 1) * tagBoost) + scopeBoost;\n\n return {\n id: result.id,\n title: result.title,\n content: result.content,\n tags,\n scope: result.scope,\n score: Math.round(finalScore * 1000) / 1000,\n };\n });\n\n // Sort by score descending\n ranked.sort((a, b) => b.score - a.score);\n\n return ranked;\n}\n"],"names":["calculateTagBoost","itemTags","contextTags","length","itemTagsLower","map","t","toLowerCase","contextTagsLower","matchCount","tag","includes","calculateScopeBoost","itemScope","queryScope","rankResults","results","context","ranked","result","tags","JSON","parse","tagBoost","scopeBoost","scope","normalizedBm25","bm25_score","finalScore","token_coverage","id","title","content","score","Math","round","sort","a","b"],"mappings":"AAiBA;;;CAGC,GACD,SAASA,kBAAkBC,QAAkB,EAAEC,WAAqB;IAChE,IAAI,CAACA,eAAeA,YAAYC,MAAM,KAAK,GAAG;QAC1C,OAAO;IACX;IAEA,MAAMC,gBAAgBH,SAASI,GAAG,CAACC,CAAAA,IAAKA,EAAEC,WAAW;IACrD,MAAMC,mBAAmBN,YAAYG,GAAG,CAACC,CAAAA,IAAKA,EAAEC,WAAW;IAE3D,IAAIE,aAAa;IACjB,KAAK,MAAMC,OAAOF,iBAAkB;QAChC,IAAIJ,cAAcO,QAAQ,CAACD,MAAM;YAC7BD;QACJ;IACJ;IAEA,OAAO,IAAKA,aAAa;AAC7B;AAEA;;;;;CAKC,GACD,SAASG,oBAAoBC,SAAiB,EAAEC,UAA0B;IACtE,IAAIA,cAAcD,cAAcC,YAAY;QACxC,OAAO;IACX;IACA,IAAID,cAAc,UAAU;QACxB,OAAO;IACX;IACA,OAAO;AACX;AAEA;;;;CAIC,GACD,OAAO,SAASE,YACZC,OAA0B,EAC1BC,OAAuB;IAEvB,MAAMC,SAASF,QAAQX,GAAG,CAACc,CAAAA;QACvB,IAAIC;QACJ,IAAI;YACAA,OAAOC,KAAKC,KAAK,CAACH,OAAOC,IAAI;QACjC,EAAE,OAAM;YACJA,OAAO,EAAE;QACb;QAEA,MAAMG,WAAWvB,kBAAkBoB,MAAMH,QAAQf,WAAW,IAAI,EAAE;QAClE,MAAMsB,aAAaZ,oBAAoBO,OAAOM,KAAK,EAAER,QAAQH,UAAU;QAEvE,4DAA4D;QAC5D,4CAA4C;QAC5C,MAAMY,iBAAiB,CAACP,OAAOQ,UAAU;QAEzC,MAAMC,aAAa,AAACF,iBAAkBP,CAAAA,OAAOU,cAAc,IAAI,CAAA,IAAKN,WAAYC;QAEhF,OAAO;YACHM,IAAIX,OAAOW,EAAE;YACbC,OAAOZ,OAAOY,KAAK;YACnBC,SAASb,OAAOa,OAAO;YACvBZ;YACAK,OAAON,OAAOM,KAAK;YACnBQ,OAAOC,KAAKC,KAAK,CAACP,aAAa,QAAQ;QAC3C;IACJ;IAEA,2BAA2B;IAC3BV,OAAOkB,IAAI,CAAC,CAACC,GAAGC,IAAMA,EAAEL,KAAK,GAAGI,EAAEJ,KAAK;IAEvC,OAAOf;AACX"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* FTS5 Query Builder
|
|
3
3
|
* Converts natural language queries to FTS5 match expressions
|
|
4
4
|
*/
|
|
5
|
+
export declare function normalizeSearchTokens(query: string): string[];
|
|
5
6
|
/**
|
|
6
7
|
* Build FTS5 query from natural language input
|
|
7
8
|
*
|
|
@@ -11,6 +12,7 @@
|
|
|
11
12
|
* - Escape special characters
|
|
12
13
|
*/
|
|
13
14
|
export declare function buildFtsQuery(query: string): string;
|
|
15
|
+
export declare function buildBroadFtsQuery(query: string): string;
|
|
14
16
|
/**
|
|
15
17
|
* Build FTS5 query with column boosting
|
|
16
18
|
* Uses bm25() with weights: title=10, content=5, tags=1
|
|
@@ -19,6 +21,10 @@ export declare function buildSearchQuery(ftsQuery: string, scope?: string | null
|
|
|
19
21
|
sql: string;
|
|
20
22
|
params: unknown[];
|
|
21
23
|
};
|
|
24
|
+
export declare function buildBroadSearchQuery(ftsQuery: string, tokens: string[], scope?: string | null, limit?: number): {
|
|
25
|
+
sql: string;
|
|
26
|
+
params: unknown[];
|
|
27
|
+
};
|
|
22
28
|
/**
|
|
23
29
|
* Build simple search query without FTS (fallback for empty queries)
|
|
24
30
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/services/search.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAY7D;AAMD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC5B,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,SAAI,GACV;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CA6BpC;AAED,wBAAgB,qBAAqB,CACjC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,SAAI,GACV;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAwBpC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,EACrB,KAAK,SAAI,GACV;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAoBpC"}
|
package/dist/services/search.js
CHANGED
|
@@ -1,14 +1,67 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FTS5 Query Builder
|
|
3
3
|
* Converts natural language queries to FTS5 match expressions
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
*/ const STOPWORDS = new Set([
|
|
5
|
+
'a',
|
|
6
|
+
'an',
|
|
7
|
+
'and',
|
|
8
|
+
'are',
|
|
9
|
+
'as',
|
|
10
|
+
'at',
|
|
11
|
+
'be',
|
|
12
|
+
'been',
|
|
13
|
+
'but',
|
|
14
|
+
'by',
|
|
15
|
+
'do',
|
|
16
|
+
'does',
|
|
17
|
+
'for',
|
|
18
|
+
'from',
|
|
19
|
+
'had',
|
|
20
|
+
'has',
|
|
21
|
+
'have',
|
|
22
|
+
'how',
|
|
23
|
+
'i',
|
|
24
|
+
'if',
|
|
25
|
+
'in',
|
|
26
|
+
'is',
|
|
27
|
+
'it',
|
|
28
|
+
'of',
|
|
29
|
+
'on',
|
|
30
|
+
'or',
|
|
31
|
+
'should',
|
|
32
|
+
'that',
|
|
33
|
+
'the',
|
|
34
|
+
'their',
|
|
35
|
+
'this',
|
|
36
|
+
'to',
|
|
37
|
+
'was',
|
|
38
|
+
'what',
|
|
39
|
+
'when',
|
|
40
|
+
'where',
|
|
41
|
+
'which',
|
|
42
|
+
'who',
|
|
43
|
+
'why',
|
|
44
|
+
'will',
|
|
45
|
+
'with',
|
|
46
|
+
'you',
|
|
47
|
+
'your'
|
|
48
|
+
]);
|
|
49
|
+
export function normalizeSearchTokens(query) {
|
|
50
|
+
const matches = query.normalize('NFKC').match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
return matches.flatMap((token)=>{
|
|
53
|
+
const normalized = token.toLowerCase();
|
|
54
|
+
if (normalized.length === 1 || STOPWORDS.has(normalized) || seen.has(normalized)) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
seen.add(normalized);
|
|
58
|
+
return [
|
|
59
|
+
normalized
|
|
60
|
+
];
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function buildPrefixTerms(query) {
|
|
64
|
+
return normalizeSearchTokens(query).map((token)=>`"${token}"*`);
|
|
12
65
|
}
|
|
13
66
|
/**
|
|
14
67
|
* Build FTS5 query from natural language input
|
|
@@ -18,14 +71,10 @@
|
|
|
18
71
|
* - Use prefix matching (*) for partial matches
|
|
19
72
|
* - Escape special characters
|
|
20
73
|
*/ export function buildFtsQuery(query) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
// Use prefix matching for each word
|
|
27
|
-
// This allows "api design" to match "API", "designing", etc.
|
|
28
|
-
return words.map((word)=>`${word}*`).join(' ');
|
|
74
|
+
return buildPrefixTerms(query).join(' ');
|
|
75
|
+
}
|
|
76
|
+
export function buildBroadFtsQuery(query) {
|
|
77
|
+
return buildPrefixTerms(query).join(' OR ');
|
|
29
78
|
}
|
|
30
79
|
/**
|
|
31
80
|
* Build FTS5 query with column boosting
|
|
@@ -41,7 +90,8 @@
|
|
|
41
90
|
k.scope,
|
|
42
91
|
k.created_at,
|
|
43
92
|
k.updated_at,
|
|
44
|
-
bm25(knowledge_fts, 10.0, 5.0, 1.0) as bm25_score
|
|
93
|
+
bm25(knowledge_fts, 10.0, 5.0, 1.0) as bm25_score,
|
|
94
|
+
1.0 as token_coverage
|
|
45
95
|
FROM knowledge k
|
|
46
96
|
JOIN knowledge_fts fts ON k.rowid = fts.rowid
|
|
47
97
|
WHERE knowledge_fts MATCH ?
|
|
@@ -58,6 +108,30 @@
|
|
|
58
108
|
params
|
|
59
109
|
};
|
|
60
110
|
}
|
|
111
|
+
export function buildBroadSearchQuery(ftsQuery, tokens, scope, limit = 5) {
|
|
112
|
+
const tokenMatches = tokens.map(()=>'(CASE WHEN k.rowid IN (SELECT rowid FROM knowledge_fts WHERE knowledge_fts MATCH ?) THEN 1 ELSE 0 END)').join(' + ');
|
|
113
|
+
const params = tokens.map((token)=>`"${token}"*`);
|
|
114
|
+
let sql = `
|
|
115
|
+
SELECT
|
|
116
|
+
k.id, k.title, k.content, k.tags, k.scope, k.created_at, k.updated_at,
|
|
117
|
+
bm25(knowledge_fts, 10.0, 5.0, 1.0) as bm25_score,
|
|
118
|
+
CAST((${tokenMatches}) AS REAL) / ${tokens.length} as token_coverage
|
|
119
|
+
FROM knowledge k
|
|
120
|
+
JOIN knowledge_fts fts ON k.rowid = fts.rowid
|
|
121
|
+
WHERE knowledge_fts MATCH ?
|
|
122
|
+
`;
|
|
123
|
+
params.push(ftsQuery);
|
|
124
|
+
if (scope) {
|
|
125
|
+
sql += ` AND (k.scope = ? OR k.scope = 'global')`;
|
|
126
|
+
params.push(scope);
|
|
127
|
+
}
|
|
128
|
+
sql += ` ORDER BY token_coverage DESC, bm25_score LIMIT ?`;
|
|
129
|
+
params.push(limit);
|
|
130
|
+
return {
|
|
131
|
+
sql,
|
|
132
|
+
params
|
|
133
|
+
};
|
|
134
|
+
}
|
|
61
135
|
/**
|
|
62
136
|
* Build simple search query without FTS (fallback for empty queries)
|
|
63
137
|
*/ export function buildSimpleQuery(scope, limit = 5) {
|
|
@@ -65,7 +139,8 @@
|
|
|
65
139
|
let sql = `
|
|
66
140
|
SELECT
|
|
67
141
|
id, title, content, tags, scope, created_at, updated_at,
|
|
68
|
-
0 as bm25_score
|
|
142
|
+
0 as bm25_score,
|
|
143
|
+
1.0 as token_coverage
|
|
69
144
|
FROM knowledge
|
|
70
145
|
`;
|
|
71
146
|
if (scope) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/search.ts"],"sourcesContent":["/**\n * FTS5 Query Builder\n * Converts natural language queries to FTS5 match expressions\n */\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/services/search.ts"],"sourcesContent":["/**\n * FTS5 Query Builder\n * Converts natural language queries to FTS5 match expressions\n */\n\nconst STOPWORDS = new Set([\n 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'been', 'but', 'by', 'do', 'does',\n 'for', 'from', 'had', 'has', 'have', 'how', 'i', 'if', 'in', 'is', 'it', 'of',\n 'on', 'or', 'should', 'that', 'the', 'their', 'this', 'to', 'was', 'what',\n 'when', 'where', 'which', 'who', 'why', 'will', 'with', 'you', 'your',\n]);\n\nexport function normalizeSearchTokens(query: string): string[] {\n const matches = query.normalize('NFKC').match(/[\\p{L}\\p{N}]+/gu) ?? [];\n const seen = new Set<string>();\n\n return matches.flatMap(token => {\n const normalized = token.toLowerCase();\n if (normalized.length === 1 || STOPWORDS.has(normalized) || seen.has(normalized)) {\n return [];\n }\n seen.add(normalized);\n return [normalized];\n });\n}\n\nfunction buildPrefixTerms(query: string): string[] {\n return normalizeSearchTokens(query).map(token => `\"${token}\"*`);\n}\n\n/**\n * Build FTS5 query from natural language input\n * \n * Strategy:\n * - Split query into words\n * - Use prefix matching (*) for partial matches\n * - Escape special characters\n */\nexport function buildFtsQuery(query: string): string {\n return buildPrefixTerms(query).join(' ');\n}\n\nexport function buildBroadFtsQuery(query: string): string {\n return buildPrefixTerms(query).join(' OR ');\n}\n\n/**\n * Build FTS5 query with column boosting\n * Uses bm25() with weights: title=10, content=5, tags=1\n */\nexport function buildSearchQuery(\n ftsQuery: string,\n scope?: string | null,\n limit = 5\n): { sql: string; params: unknown[] } {\n const params: unknown[] = [];\n\n let sql = `\n SELECT \n k.id,\n k.title,\n k.content,\n k.tags,\n k.scope,\n k.created_at,\n k.updated_at,\n bm25(knowledge_fts, 10.0, 5.0, 1.0) as bm25_score,\n 1.0 as token_coverage\n FROM knowledge k\n JOIN knowledge_fts fts ON k.rowid = fts.rowid\n WHERE knowledge_fts MATCH ?\n `;\n params.push(ftsQuery);\n\n if (scope) {\n sql += ` AND (k.scope = ? OR k.scope = 'global')`;\n params.push(scope);\n }\n\n sql += ` ORDER BY bm25_score LIMIT ?`;\n params.push(limit);\n\n return { sql, params };\n}\n\nexport function buildBroadSearchQuery(\n ftsQuery: string,\n tokens: string[],\n scope?: string | null,\n limit = 5\n): { sql: string; params: unknown[] } {\n const tokenMatches = tokens.map(() =>\n '(CASE WHEN k.rowid IN (SELECT rowid FROM knowledge_fts WHERE knowledge_fts MATCH ?) THEN 1 ELSE 0 END)'\n ).join(' + ');\n const params: unknown[] = tokens.map(token => `\"${token}\"*`);\n let sql = `\n SELECT\n k.id, k.title, k.content, k.tags, k.scope, k.created_at, k.updated_at,\n bm25(knowledge_fts, 10.0, 5.0, 1.0) as bm25_score,\n CAST((${tokenMatches}) AS REAL) / ${tokens.length} as token_coverage\n FROM knowledge k\n JOIN knowledge_fts fts ON k.rowid = fts.rowid\n WHERE knowledge_fts MATCH ?\n `;\n params.push(ftsQuery);\n\n if (scope) {\n sql += ` AND (k.scope = ? OR k.scope = 'global')`;\n params.push(scope);\n }\n\n sql += ` ORDER BY token_coverage DESC, bm25_score LIMIT ?`;\n params.push(limit);\n return { sql, params };\n}\n\n/**\n * Build simple search query without FTS (fallback for empty queries)\n */\nexport function buildSimpleQuery(\n scope?: string | null,\n limit = 5\n): { sql: string; params: unknown[] } {\n const params: unknown[] = [];\n\n let sql = `\n SELECT \n id, title, content, tags, scope, created_at, updated_at,\n 0 as bm25_score,\n 1.0 as token_coverage\n FROM knowledge\n `;\n\n if (scope) {\n sql += ` WHERE scope = ? OR scope = 'global'`;\n params.push(scope);\n }\n\n sql += ` ORDER BY created_at DESC LIMIT ?`;\n params.push(limit);\n\n return { sql, params };\n}\n"],"names":["STOPWORDS","Set","normalizeSearchTokens","query","matches","normalize","match","seen","flatMap","token","normalized","toLowerCase","length","has","add","buildPrefixTerms","map","buildFtsQuery","join","buildBroadFtsQuery","buildSearchQuery","ftsQuery","scope","limit","params","sql","push","buildBroadSearchQuery","tokens","tokenMatches","buildSimpleQuery"],"mappings":"AAAA;;;CAGC,GAED,MAAMA,YAAY,IAAIC,IAAI;IACtB;IAAK;IAAM;IAAO;IAAO;IAAM;IAAM;IAAM;IAAQ;IAAO;IAAM;IAAM;IACtE;IAAO;IAAQ;IAAO;IAAO;IAAQ;IAAO;IAAK;IAAM;IAAM;IAAM;IAAM;IACzE;IAAM;IAAM;IAAU;IAAQ;IAAO;IAAS;IAAQ;IAAM;IAAO;IACnE;IAAQ;IAAS;IAAS;IAAO;IAAO;IAAQ;IAAQ;IAAO;CAClE;AAED,OAAO,SAASC,sBAAsBC,KAAa;IAC/C,MAAMC,UAAUD,MAAME,SAAS,CAAC,QAAQC,KAAK,CAAC,sBAAsB,EAAE;IACtE,MAAMC,OAAO,IAAIN;IAEjB,OAAOG,QAAQI,OAAO,CAACC,CAAAA;QACnB,MAAMC,aAAaD,MAAME,WAAW;QACpC,IAAID,WAAWE,MAAM,KAAK,KAAKZ,UAAUa,GAAG,CAACH,eAAeH,KAAKM,GAAG,CAACH,aAAa;YAC9E,OAAO,EAAE;QACb;QACAH,KAAKO,GAAG,CAACJ;QACT,OAAO;YAACA;SAAW;IACvB;AACJ;AAEA,SAASK,iBAAiBZ,KAAa;IACnC,OAAOD,sBAAsBC,OAAOa,GAAG,CAACP,CAAAA,QAAS,CAAC,CAAC,EAAEA,MAAM,EAAE,CAAC;AAClE;AAEA;;;;;;;CAOC,GACD,OAAO,SAASQ,cAAcd,KAAa;IACvC,OAAOY,iBAAiBZ,OAAOe,IAAI,CAAC;AACxC;AAEA,OAAO,SAASC,mBAAmBhB,KAAa;IAC5C,OAAOY,iBAAiBZ,OAAOe,IAAI,CAAC;AACxC;AAEA;;;CAGC,GACD,OAAO,SAASE,iBACZC,QAAgB,EAChBC,KAAqB,EACrBC,QAAQ,CAAC;IAET,MAAMC,SAAoB,EAAE;IAE5B,IAAIC,MAAM,CAAC;;;;;;;;;;;;;;EAcb,CAAC;IACCD,OAAOE,IAAI,CAACL;IAEZ,IAAIC,OAAO;QACPG,OAAO,CAAC,wCAAwC,CAAC;QACjDD,OAAOE,IAAI,CAACJ;IAChB;IAEAG,OAAO,CAAC,4BAA4B,CAAC;IACrCD,OAAOE,IAAI,CAACH;IAEZ,OAAO;QAAEE;QAAKD;IAAO;AACzB;AAEA,OAAO,SAASG,sBACZN,QAAgB,EAChBO,MAAgB,EAChBN,KAAqB,EACrBC,QAAQ,CAAC;IAET,MAAMM,eAAeD,OAAOZ,GAAG,CAAC,IAC5B,0GACFE,IAAI,CAAC;IACP,MAAMM,SAAoBI,OAAOZ,GAAG,CAACP,CAAAA,QAAS,CAAC,CAAC,EAAEA,MAAM,EAAE,CAAC;IAC3D,IAAIgB,MAAM,CAAC;;;;YAIH,EAAEI,aAAa,aAAa,EAAED,OAAOhB,MAAM,CAAC;;;;EAItD,CAAC;IACCY,OAAOE,IAAI,CAACL;IAEZ,IAAIC,OAAO;QACPG,OAAO,CAAC,wCAAwC,CAAC;QACjDD,OAAOE,IAAI,CAACJ;IAChB;IAEAG,OAAO,CAAC,iDAAiD,CAAC;IAC1DD,OAAOE,IAAI,CAACH;IACZ,OAAO;QAAEE;QAAKD;IAAO;AACzB;AAEA;;CAEC,GACD,OAAO,SAASM,iBACZR,KAAqB,EACrBC,QAAQ,CAAC;IAET,MAAMC,SAAoB,EAAE;IAE5B,IAAIC,MAAM,CAAC;;;;;;EAMb,CAAC;IAEC,IAAIH,OAAO;QACPG,OAAO,CAAC,oCAAoC,CAAC;QAC7CD,OAAOE,IAAI,CAACJ;IAChB;IAEAG,OAAO,CAAC,iCAAiC,CAAC;IAC1CD,OAAOE,IAAI,CAACH;IAEZ,OAAO;QAAEE;QAAKD;IAAO;AACzB"}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ export interface SearchKnowledgeResult {
|
|
|
51
51
|
results: SearchResultItem[];
|
|
52
52
|
totalMatches: number;
|
|
53
53
|
query: string;
|
|
54
|
+
strategy: 'strict' | 'broad' | 'recent';
|
|
54
55
|
}
|
|
55
56
|
export type ListKnowledgeSort = 'updated-desc' | 'created-desc' | 'title-asc';
|
|
56
57
|
export interface ListKnowledgeInput {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,aAAa;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,WAAW,MAAM,EAAE,GAAG,QAAQ,MAAM,EAAE,CAAC;AAE/E,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IAClC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["export interface KnowledgeItem {\n id: string;\n title: string;\n content: string;\n tags: string[];\n scope: string;\n normalizedTitle: string;\n contentHash: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport type KnowledgeScope = 'global' | `project:${string}` | `repo:${string}`;\n\nexport interface StoreKnowledgeInput {\n title: string;\n content: string;\n tags?: string[];\n scope?: string;\n}\n\nexport interface StoreKnowledgeResult {\n success: boolean;\n id?: string;\n message: string;\n}\n\nexport interface UpdateKnowledgeInput {\n id: string;\n title?: string;\n content?: string;\n tags?: string[];\n scope?: string;\n}\n\nexport interface UpdateKnowledgeResult {\n success: boolean;\n id: string;\n message: string;\n}\n\nexport interface SearchKnowledgeInput {\n query: string;\n contextTags?: string[];\n scope?: string;\n limit?: number;\n}\n\nexport interface SearchResultItem {\n id: string;\n title: string;\n content: string;\n tags: string[];\n scope: string;\n score: number;\n}\n\nexport interface SearchKnowledgeResult {\n results: SearchResultItem[];\n totalMatches: number;\n query: string;\n}\n\nexport type ListKnowledgeSort = 'updated-desc' | 'created-desc' | 'title-asc';\n\nexport interface ListKnowledgeInput {\n query?: string;\n tags?: string[];\n scope?: string;\n limit?: number;\n offset?: number;\n sort?: ListKnowledgeSort;\n}\n\nexport interface ListKnowledgeResult {\n items: KnowledgeItem[];\n total: number;\n}\n\nexport interface KnowledgeSummaryResult {\n totalItems: number;\n scopes: Array<{ scope: string; count: number }>;\n tags: Array<{ tag: string; count: number }>;\n recency: Array<{ bucket: string; count: number }>;\n}\n\nexport interface KnowledgeRow {\n id: string;\n title: string;\n content: string;\n tags: string;\n scope: string;\n normalized_title: string;\n content_hash: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface MetaRow {\n key: string;\n value: string;\n}\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/types/index.ts"],"sourcesContent":["export interface KnowledgeItem {\n id: string;\n title: string;\n content: string;\n tags: string[];\n scope: string;\n normalizedTitle: string;\n contentHash: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport type KnowledgeScope = 'global' | `project:${string}` | `repo:${string}`;\n\nexport interface StoreKnowledgeInput {\n title: string;\n content: string;\n tags?: string[];\n scope?: string;\n}\n\nexport interface StoreKnowledgeResult {\n success: boolean;\n id?: string;\n message: string;\n}\n\nexport interface UpdateKnowledgeInput {\n id: string;\n title?: string;\n content?: string;\n tags?: string[];\n scope?: string;\n}\n\nexport interface UpdateKnowledgeResult {\n success: boolean;\n id: string;\n message: string;\n}\n\nexport interface SearchKnowledgeInput {\n query: string;\n contextTags?: string[];\n scope?: string;\n limit?: number;\n}\n\nexport interface SearchResultItem {\n id: string;\n title: string;\n content: string;\n tags: string[];\n scope: string;\n score: number;\n}\n\nexport interface SearchKnowledgeResult {\n results: SearchResultItem[];\n totalMatches: number;\n query: string;\n strategy: 'strict' | 'broad' | 'recent';\n}\n\nexport type ListKnowledgeSort = 'updated-desc' | 'created-desc' | 'title-asc';\n\nexport interface ListKnowledgeInput {\n query?: string;\n tags?: string[];\n scope?: string;\n limit?: number;\n offset?: number;\n sort?: ListKnowledgeSort;\n}\n\nexport interface ListKnowledgeResult {\n items: KnowledgeItem[];\n total: number;\n}\n\nexport interface KnowledgeSummaryResult {\n totalItems: number;\n scopes: Array<{ scope: string; count: number }>;\n tags: Array<{ tag: string; count: number }>;\n recency: Array<{ bucket: string; count: number }>;\n}\n\nexport interface KnowledgeRow {\n id: string;\n title: string;\n content: string;\n tags: string;\n scope: string;\n normalized_title: string;\n content_hash: string;\n created_at: string;\n updated_at: string;\n}\n\nexport interface MetaRow {\n key: string;\n value: string;\n}\n"],"names":[],"mappings":"AAmGA,WAGC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-devkit/memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local MCP memory for AI coding agents to reuse project decisions, conventions, and fixes across sessions",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
52
52
|
"@typescript-eslint/eslint-plugin": "8.60.1",
|
|
53
53
|
"@typescript-eslint/parser": "8.60.1",
|
|
54
|
-
"better-sqlite3": "
|
|
54
|
+
"better-sqlite3": "12.11.1",
|
|
55
55
|
"uuid": "^14.0.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|