kibi-cli 0.7.0 → 0.8.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/commands/sync/staging.d.ts +3 -0
- package/dist/commands/sync/staging.d.ts.map +1 -1
- package/dist/commands/sync/staging.js +58 -1
- package/dist/commands/sync.d.ts +18 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +19 -4
- package/dist/public/operational-artifacts.d.ts +2 -0
- package/dist/public/operational-artifacts.d.ts.map +1 -0
- package/dist/public/operational-artifacts.js +4 -0
- package/dist/search-ranking.d.ts.map +1 -1
- package/dist/search-ranking.js +132 -25
- package/package.json +5 -1
- package/src/public/operational-artifacts.ts +5 -0
|
@@ -8,11 +8,14 @@ interface StagingDeps {
|
|
|
8
8
|
cwd: () => string;
|
|
9
9
|
existsSync: typeof existsSync;
|
|
10
10
|
fg: typeof fg;
|
|
11
|
+
isProcessAlive: (pid: number) => boolean;
|
|
11
12
|
mkdirSync: typeof mkdirSync;
|
|
12
13
|
moduleDir: string;
|
|
13
14
|
renameSync: typeof renameSync;
|
|
14
15
|
rmSync: typeof rmSync;
|
|
15
16
|
}
|
|
17
|
+
export declare function createUniqueStagingPath(currentBranch: string, rootDir: string, pid?: number, now?: number): string;
|
|
18
|
+
export declare function cleanupAbandonedStagingDirectories(stagingPath: string, deps?: Partial<StagingDeps>): Promise<void>;
|
|
16
19
|
export declare function prepareStagingEnvironment(stagingPath: string, livePath: string, rebuild: boolean, deps?: Partial<StagingDeps>): Promise<void>;
|
|
17
20
|
export declare function atomicPublish(stagingPath: string, livePath: string, deps?: Partial<StagingDeps>): void;
|
|
18
21
|
export declare function cleanupStaging(stagingPath: string, deps?: Partial<StagingDeps>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/staging.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,MAAM,WAAW,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,UAAU,WAAW;IACnB,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,EAAE,EAAE,OAAO,EAAE,CAAC;IACd,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,MAAM,EAAE,OAAO,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../../../src/commands/sync/staging.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,OAAO,EAAE,MAAM,WAAW,CAAC;AAC3B,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAEnE,UAAU,WAAW;IACnB,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,GAAG,EAAE,MAAM,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,EAAE,EAAE,OAAO,EAAE,CAAC;IACd,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACzC,SAAS,EAAE,OAAO,SAAS,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,OAAO,UAAU,CAAC;IAC9B,MAAM,EAAE,OAAO,MAAM,CAAC;CACvB;AA8BD,wBAAgB,uBAAuB,CACrC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EACf,GAAG,SAAc,EACjB,GAAG,SAAa,GACf,MAAM,CAOR;AAGD,wBAAsB,kCAAkC,CACtD,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkDf;AAED,wBAAsB,yBAAyB,CAE7C,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,OAAO,CAAC,IAAI,CAAC,CAaf;AA0CD,wBAAgB,aAAa,CAE3B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,IAAI,CAeN;AAED,wBAAgB,cAAc,CAE5B,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,GAC1B,IAAI,CAKN"}
|
|
@@ -27,6 +27,17 @@ function resolveDeps(overrides) {
|
|
|
27
27
|
cwd: () => process.cwd(),
|
|
28
28
|
existsSync,
|
|
29
29
|
fg,
|
|
30
|
+
isProcessAlive: (pid) => {
|
|
31
|
+
try {
|
|
32
|
+
process.kill(pid, 0);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return !(error instanceof Error &&
|
|
37
|
+
"code" in error &&
|
|
38
|
+
error.code === "ESRCH");
|
|
39
|
+
}
|
|
40
|
+
},
|
|
30
41
|
mkdirSync,
|
|
31
42
|
moduleDir: import.meta.dirname,
|
|
32
43
|
renameSync,
|
|
@@ -34,11 +45,54 @@ function resolveDeps(overrides) {
|
|
|
34
45
|
...overrides,
|
|
35
46
|
};
|
|
36
47
|
}
|
|
48
|
+
// implements REQ-003
|
|
49
|
+
export function createUniqueStagingPath(currentBranch, rootDir, pid = process.pid, now = Date.now()) {
|
|
50
|
+
return path.join(rootDir, ".kb", "branches", `${currentBranch}.staging.${pid}.${now}`);
|
|
51
|
+
}
|
|
52
|
+
// implements REQ-003
|
|
53
|
+
export async function cleanupAbandonedStagingDirectories(stagingPath, deps) {
|
|
54
|
+
const resolved = resolveDeps(deps);
|
|
55
|
+
const stagingDir = path.dirname(stagingPath);
|
|
56
|
+
const stagingBase = path.basename(stagingPath);
|
|
57
|
+
const match = /^(?<branch>.+)\.staging\.(?<pid>\d+)\.(?<timestamp>\d+)$/.exec(stagingBase);
|
|
58
|
+
if (!match?.groups) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const branch = match.groups.branch;
|
|
62
|
+
if (!branch) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const candidates = await resolved.fg(`${branch}.staging.*`, {
|
|
66
|
+
cwd: stagingDir,
|
|
67
|
+
absolute: true,
|
|
68
|
+
onlyDirectories: true,
|
|
69
|
+
suppressErrors: true,
|
|
70
|
+
});
|
|
71
|
+
for (const candidate of candidates) {
|
|
72
|
+
if (candidate === stagingPath) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const candidateBase = path.basename(candidate);
|
|
76
|
+
const candidateMatch = new RegExp(`^${escapeRegex(branch)}\\.staging\\.(\\d+)\\.(\\d+)$`).exec(candidateBase);
|
|
77
|
+
if (!candidateMatch) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const candidatePidText = candidateMatch[1];
|
|
81
|
+
if (!candidatePidText) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const candidatePid = Number.parseInt(candidatePidText, 10);
|
|
85
|
+
if (!Number.isFinite(candidatePid) || resolved.isProcessAlive(candidatePid)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
cleanupStaging(candidate, resolved);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
37
91
|
export async function prepareStagingEnvironment(
|
|
38
92
|
// implements REQ-003
|
|
39
93
|
stagingPath, livePath, rebuild, deps) {
|
|
40
94
|
const resolved = resolveDeps(deps);
|
|
41
|
-
|
|
95
|
+
await cleanupAbandonedStagingDirectories(stagingPath, resolved);
|
|
42
96
|
cleanupStaging(stagingPath, resolved);
|
|
43
97
|
resolved.mkdirSync(stagingPath, { recursive: true });
|
|
44
98
|
if (!rebuild && resolved.existsSync(livePath)) {
|
|
@@ -107,3 +161,6 @@ stagingPath, deps) {
|
|
|
107
161
|
resolved.rmSync(stagingPath, { recursive: true, force: true });
|
|
108
162
|
}
|
|
109
163
|
}
|
|
164
|
+
function escapeRegex(value) {
|
|
165
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
166
|
+
}
|
package/dist/commands/sync.d.ts
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import type { SyncSummary } from "../diagnostics.js";
|
|
2
|
+
import { PrologProcess } from "../prolog.js";
|
|
2
3
|
export declare class SyncError extends Error {
|
|
3
4
|
constructor(message: string);
|
|
4
5
|
}
|
|
5
6
|
export interface SyncResult extends SyncSummary {
|
|
6
7
|
exitCode?: number;
|
|
7
8
|
}
|
|
9
|
+
interface SyncCommandRuntimeContext {
|
|
10
|
+
currentBranch: string;
|
|
11
|
+
livePath: string;
|
|
12
|
+
rebuild: boolean;
|
|
13
|
+
stagingPath: string;
|
|
14
|
+
validateOnly: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface SyncCommandRuntime {
|
|
17
|
+
afterAttach?: (context: SyncCommandRuntimeContext) => Promise<void> | void;
|
|
18
|
+
beforeSave?: (context: SyncCommandRuntimeContext & {
|
|
19
|
+
kbModified: boolean;
|
|
20
|
+
}) => Promise<void> | void;
|
|
21
|
+
createProlog?: (options: {
|
|
22
|
+
timeout: number;
|
|
23
|
+
}) => PrologProcess;
|
|
24
|
+
}
|
|
8
25
|
export declare function syncCommand(options?: {
|
|
9
26
|
validateOnly?: boolean;
|
|
10
27
|
rebuild?: boolean;
|
|
11
|
-
}): Promise<SyncResult>;
|
|
28
|
+
}, runtime?: SyncCommandRuntime): Promise<SyncResult>;
|
|
12
29
|
export { normalizeMarkdownPath } from "./sync/discovery.js";
|
|
13
30
|
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/commands/sync.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAc,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAejE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AA4B7C,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAGD,MAAM,WAAW,UAAW,SAAQ,WAAW;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,yBAAyB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,kBAAkB;IAC1B,WAAW,CAAC,EAAE,CACZ,OAAO,EAAE,yBAAyB,KAC/B,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,CACX,OAAO,EAAE,yBAAyB,GAAG;QAAE,UAAU,EAAE,OAAO,CAAA;KAAE,KACzD,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,aAAa,CAAC;CAChE;AAGD,wBAAsB,WAAW,CAC/B,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,EAC3D,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,UAAU,CAAC,CAmbrB;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/commands/sync.js
CHANGED
|
@@ -29,7 +29,7 @@ import { discoverSourceFiles, } from "./sync/discovery.js";
|
|
|
29
29
|
import { processExtractions } from "./sync/extraction.js";
|
|
30
30
|
import { refreshManifestCoordinates } from "./sync/manifest.js";
|
|
31
31
|
import { persistEntities, persistRelationships } from "./sync/persistence.js";
|
|
32
|
-
import { atomicPublish, cleanupStaging, prepareStagingEnvironment, } from "./sync/staging.js";
|
|
32
|
+
import { atomicPublish, cleanupStaging, createUniqueStagingPath, prepareStagingEnvironment, } from "./sync/staging.js";
|
|
33
33
|
export class SyncError extends Error {
|
|
34
34
|
constructor(message) {
|
|
35
35
|
super(message);
|
|
@@ -37,7 +37,7 @@ export class SyncError extends Error {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
// implements REQ-003, REQ-007
|
|
40
|
-
export async function syncCommand(options = {}) {
|
|
40
|
+
export async function syncCommand(options = {}, runtime = {}) {
|
|
41
41
|
const validateOnly = options.validateOnly ?? false;
|
|
42
42
|
const rebuild = options.rebuild ?? false;
|
|
43
43
|
const startTime = Date.now();
|
|
@@ -45,6 +45,7 @@ export async function syncCommand(options = {}) {
|
|
|
45
45
|
const entityCounts = {};
|
|
46
46
|
let published = false;
|
|
47
47
|
let currentBranch;
|
|
48
|
+
let stagingPath;
|
|
48
49
|
const getCurrentCommit = () => {
|
|
49
50
|
try {
|
|
50
51
|
return execSync("git rev-parse HEAD", {
|
|
@@ -199,16 +200,25 @@ export async function syncCommand(options = {}) {
|
|
|
199
200
|
if (!kbExists && !rebuild) {
|
|
200
201
|
diagnostics.push(createKbMissingDiagnostic(currentBranch, livePath));
|
|
201
202
|
}
|
|
202
|
-
|
|
203
|
+
stagingPath = createUniqueStagingPath(currentBranch, process.cwd());
|
|
204
|
+
const runtimeContext = {
|
|
205
|
+
currentBranch,
|
|
206
|
+
livePath,
|
|
207
|
+
rebuild,
|
|
208
|
+
stagingPath,
|
|
209
|
+
validateOnly,
|
|
210
|
+
};
|
|
203
211
|
await prepareStagingEnvironment(stagingPath, livePath, rebuild);
|
|
204
212
|
try {
|
|
205
|
-
const prolog =
|
|
213
|
+
const prolog = runtime.createProlog?.({ timeout: 120000 }) ??
|
|
214
|
+
new PrologProcess({ timeout: 120000 });
|
|
206
215
|
await prolog.start();
|
|
207
216
|
const attachResult = await prolog.query(`kb_attach('${stagingPath}')`);
|
|
208
217
|
if (!attachResult.success) {
|
|
209
218
|
await prolog.terminate();
|
|
210
219
|
throw new SyncError(`Failed to attach to staging KB: ${attachResult.error || "Unknown error"}`);
|
|
211
220
|
}
|
|
221
|
+
await runtime.afterAttach?.(runtimeContext);
|
|
212
222
|
const entityIds = new Set();
|
|
213
223
|
for (const { entity } of results) {
|
|
214
224
|
entityCounts[entity.type] = (entityCounts[entity.type] || 0) + 1;
|
|
@@ -263,6 +273,7 @@ export async function syncCommand(options = {}) {
|
|
|
263
273
|
if (kbModified) {
|
|
264
274
|
prolog.invalidateCache();
|
|
265
275
|
}
|
|
276
|
+
await runtime.beforeSave?.({ ...runtimeContext, kbModified });
|
|
266
277
|
const saveResult = await prolog.query("kb_save");
|
|
267
278
|
if (!saveResult.success) {
|
|
268
279
|
throw new SyncError(`Failed to save staging KB: ${saveResult.error || "Unknown error"}`);
|
|
@@ -270,6 +281,7 @@ export async function syncCommand(options = {}) {
|
|
|
270
281
|
await prolog.query("kb_detach");
|
|
271
282
|
await prolog.terminate();
|
|
272
283
|
atomicPublish(stagingPath, livePath);
|
|
284
|
+
cleanupStaging(stagingPath);
|
|
273
285
|
const evictedHashes = {};
|
|
274
286
|
const evictedSeenAt = {};
|
|
275
287
|
for (const [key, hash] of Object.entries(nextHashes)) {
|
|
@@ -312,6 +324,9 @@ export async function syncCommand(options = {}) {
|
|
|
312
324
|
}
|
|
313
325
|
}
|
|
314
326
|
catch (error) {
|
|
327
|
+
if (stagingPath) {
|
|
328
|
+
cleanupStaging(stagingPath);
|
|
329
|
+
}
|
|
315
330
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
316
331
|
console.error(`Error: ${errorMessage}`);
|
|
317
332
|
const commit = getCurrentCommit();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operational-artifacts.d.ts","sourceRoot":"","sources":["../../src/public/operational-artifacts.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAInE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-ranking.d.ts","sourceRoot":"","sources":["../src/search-ranking.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;
|
|
1
|
+
{"version":3,"file":"search-ranking.d.ts","sourceRoot":"","sources":["../src/search-ranking.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgDD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACnC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC,CA8BxB;AAmGD,wBAAsB,gBAAgB,CAEpC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB"}
|
package/dist/search-ranking.js
CHANGED
|
@@ -1,10 +1,47 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
const SEARCH_STOP_WORDS = new Set([
|
|
4
|
+
"to",
|
|
5
|
+
"in",
|
|
6
|
+
"out",
|
|
7
|
+
"log",
|
|
8
|
+
"logged",
|
|
9
|
+
"unable",
|
|
10
|
+
"the",
|
|
11
|
+
"a",
|
|
12
|
+
"an",
|
|
13
|
+
"is",
|
|
14
|
+
"are",
|
|
15
|
+
"was",
|
|
16
|
+
"were",
|
|
17
|
+
"be",
|
|
18
|
+
"been",
|
|
19
|
+
"being",
|
|
20
|
+
"have",
|
|
21
|
+
"has",
|
|
22
|
+
"had",
|
|
23
|
+
"do",
|
|
24
|
+
"does",
|
|
25
|
+
"did",
|
|
26
|
+
"will",
|
|
27
|
+
"would",
|
|
28
|
+
"could",
|
|
29
|
+
"should",
|
|
30
|
+
"may",
|
|
31
|
+
"might",
|
|
32
|
+
"shall",
|
|
33
|
+
"can",
|
|
34
|
+
"not",
|
|
35
|
+
]);
|
|
3
36
|
// implements REQ-mcp-search-discovery, REQ-002, REQ-003
|
|
4
37
|
export async function rankEntities(entities, query, workspaceRoot) {
|
|
38
|
+
const queryContext = buildSearchQueryContext(query);
|
|
39
|
+
if (!queryContext.rawTrimmedQuery || queryContext.signalTokens.length === 0) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
5
42
|
const matches = [];
|
|
6
43
|
for (const entity of entities) {
|
|
7
|
-
const match = await rankEntity(entity,
|
|
44
|
+
const match = await rankEntity(entity, queryContext, workspaceRoot);
|
|
8
45
|
if (match) {
|
|
9
46
|
matches.push(match);
|
|
10
47
|
}
|
|
@@ -22,9 +59,8 @@ export async function rankEntities(entities, query, workspaceRoot) {
|
|
|
22
59
|
});
|
|
23
60
|
return matches;
|
|
24
61
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const tokens = normalizedQuery.split(/\s+/).filter(Boolean);
|
|
62
|
+
// implements REQ-mcp-search-discovery
|
|
63
|
+
async function rankEntity(entity, queryContext, workspaceRoot) {
|
|
28
64
|
const reasons = [];
|
|
29
65
|
let score = 0;
|
|
30
66
|
const id = String(entity.id ?? "");
|
|
@@ -37,55 +73,56 @@ async function rankEntity(entity, query, workspaceRoot) {
|
|
|
37
73
|
const tags = Array.isArray(entity.tags)
|
|
38
74
|
? entity.tags.map((tag) => String(tag))
|
|
39
75
|
: [];
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
76
|
+
const titleForms = buildSearchTextForms(title);
|
|
77
|
+
const idForms = buildSearchTextForms(id);
|
|
78
|
+
if (isExactSearchMatch(titleForms, queryContext.phrase)) {
|
|
43
79
|
score += 100;
|
|
44
80
|
reasons.push("exact title match");
|
|
45
81
|
}
|
|
46
|
-
else if (
|
|
82
|
+
else if (isPhraseSearchMatch(titleForms, queryContext.phrase)) {
|
|
47
83
|
score += 60;
|
|
48
84
|
reasons.push("title phrase match");
|
|
49
85
|
}
|
|
50
|
-
if (
|
|
86
|
+
if (isExactSearchMatch(idForms, queryContext.phrase)) {
|
|
51
87
|
score += 90;
|
|
52
88
|
reasons.push("exact ID match");
|
|
53
89
|
}
|
|
54
|
-
else if (
|
|
90
|
+
else if (isPhraseSearchMatch(idForms, queryContext.phrase)) {
|
|
55
91
|
score += 55;
|
|
56
92
|
reasons.push("ID match");
|
|
57
93
|
}
|
|
58
94
|
const metadataFields = [type, source, owner, priority, severity];
|
|
59
|
-
const metadataMatched = metadataFields.some((field) =>
|
|
95
|
+
const metadataMatched = metadataFields.some((field) => isPhraseSearchMatch(buildSearchTextForms(field), queryContext.phrase));
|
|
60
96
|
if (metadataMatched) {
|
|
61
97
|
score += 20;
|
|
62
98
|
reasons.push("metadata match");
|
|
63
99
|
}
|
|
64
|
-
const matchingTags = tags.filter((tag) =>
|
|
100
|
+
const matchingTags = tags.filter((tag) => isPhraseSearchMatch(buildSearchTextForms(tag), queryContext.phrase));
|
|
65
101
|
if (matchingTags.length > 0) {
|
|
66
102
|
score += 30;
|
|
67
103
|
reasons.push("tag match");
|
|
68
104
|
}
|
|
69
|
-
const titleTokenMatches = countTokenMatches(
|
|
105
|
+
const titleTokenMatches = countTokenMatches(titleForms, queryContext.signalTokens);
|
|
70
106
|
if (titleTokenMatches > 0) {
|
|
71
107
|
score += titleTokenMatches * 8;
|
|
72
108
|
reasons.push("title token coverage");
|
|
73
109
|
}
|
|
74
|
-
const bodyText = await loadMarkdownBody(source, workspaceRoot)
|
|
110
|
+
const bodyText = (await loadMarkdownBody(source, workspaceRoot)) ??
|
|
111
|
+
getInlineBodyText(entity);
|
|
75
112
|
let snippet;
|
|
76
113
|
if (bodyText) {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
114
|
+
const bodyForms = buildSearchTextForms(bodyText);
|
|
115
|
+
if (isPhraseSearchMatch(bodyForms, queryContext.phrase)) {
|
|
79
116
|
score += 15;
|
|
80
117
|
reasons.push("markdown body match");
|
|
81
|
-
snippet = buildSnippet(bodyText,
|
|
118
|
+
snippet = buildSnippet(bodyText, queryContext.phrase);
|
|
82
119
|
}
|
|
83
120
|
else {
|
|
84
|
-
const bodyTokenMatches = countTokenMatches(
|
|
121
|
+
const bodyTokenMatches = countTokenMatches(bodyForms, queryContext.signalTokens);
|
|
85
122
|
if (bodyTokenMatches > 0) {
|
|
86
123
|
score += bodyTokenMatches * 3;
|
|
87
124
|
reasons.push("markdown body token coverage");
|
|
88
|
-
snippet = buildSnippet(bodyText,
|
|
125
|
+
snippet = buildSnippet(bodyText, queryContext.phrase);
|
|
89
126
|
}
|
|
90
127
|
}
|
|
91
128
|
}
|
|
@@ -126,6 +163,7 @@ source, workspaceRoot) {
|
|
|
126
163
|
return null;
|
|
127
164
|
}
|
|
128
165
|
}
|
|
166
|
+
// implements REQ-mcp-search-discovery
|
|
129
167
|
function stripFrontmatter(content) {
|
|
130
168
|
const trimmedContent = content.trimStart();
|
|
131
169
|
if (!trimmedContent.startsWith("---")) {
|
|
@@ -143,19 +181,88 @@ function stripFrontmatter(content) {
|
|
|
143
181
|
}
|
|
144
182
|
return trimmedContent.slice(match.index + match[0].length);
|
|
145
183
|
}
|
|
146
|
-
|
|
147
|
-
|
|
184
|
+
// implements REQ-mcp-search-discovery
|
|
185
|
+
function buildSearchQueryContext(query) {
|
|
186
|
+
return {
|
|
187
|
+
phrase: buildSearchTextForms(query),
|
|
188
|
+
signalTokens: tokenizeSignalTerms(query),
|
|
189
|
+
rawTrimmedQuery: query.trim(),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// implements REQ-mcp-search-discovery
|
|
193
|
+
function buildSearchTextForms(value) {
|
|
194
|
+
const normalized = normalizeSearchText(value);
|
|
195
|
+
return {
|
|
196
|
+
normalized,
|
|
197
|
+
compact: normalized.replace(/\s+/g, ""),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// implements REQ-mcp-search-discovery
|
|
201
|
+
function normalizeSearchText(value) {
|
|
202
|
+
return value
|
|
203
|
+
.trim()
|
|
204
|
+
.toLowerCase()
|
|
205
|
+
.replace(/[-_]+/g, " ")
|
|
206
|
+
.replace(/[^a-z0-9\s]+/g, " ")
|
|
207
|
+
.trim()
|
|
208
|
+
.split(/\s+/)
|
|
209
|
+
.filter(Boolean)
|
|
210
|
+
.map(singularizeSimplePlural)
|
|
211
|
+
.join(" ");
|
|
212
|
+
}
|
|
213
|
+
// implements REQ-mcp-search-discovery
|
|
214
|
+
function tokenizeSignalTerms(value) {
|
|
215
|
+
return Array.from(new Set(normalizeSearchText(value)
|
|
216
|
+
.split(/\s+/)
|
|
217
|
+
.filter((token) => token && !SEARCH_STOP_WORDS.has(token))));
|
|
148
218
|
}
|
|
219
|
+
// implements REQ-mcp-search-discovery
|
|
220
|
+
function singularizeSimplePlural(token) {
|
|
221
|
+
if (token.length <= 4 ||
|
|
222
|
+
!token.endsWith("s") ||
|
|
223
|
+
token.endsWith("ss") ||
|
|
224
|
+
token.endsWith("us") ||
|
|
225
|
+
token.endsWith("is")) {
|
|
226
|
+
return token;
|
|
227
|
+
}
|
|
228
|
+
return token.slice(0, -1);
|
|
229
|
+
}
|
|
230
|
+
// implements REQ-mcp-search-discovery
|
|
231
|
+
function isExactSearchMatch(haystack, needle) {
|
|
232
|
+
return (haystack.normalized === needle.normalized ||
|
|
233
|
+
(needle.compact !== "" && haystack.compact === needle.compact));
|
|
234
|
+
}
|
|
235
|
+
// implements REQ-mcp-search-discovery
|
|
236
|
+
function isPhraseSearchMatch(haystack, needle) {
|
|
237
|
+
return (haystack.normalized.includes(needle.normalized) ||
|
|
238
|
+
(needle.compact !== "" && haystack.compact.includes(needle.compact)));
|
|
239
|
+
}
|
|
240
|
+
// implements REQ-mcp-search-discovery
|
|
149
241
|
function countTokenMatches(haystack, tokens) {
|
|
150
|
-
return tokens.filter((token) => haystack.includes(token)).length;
|
|
242
|
+
return tokens.filter((token) => haystack.normalized.includes(token) || haystack.compact.includes(token)).length;
|
|
243
|
+
}
|
|
244
|
+
// implements REQ-mcp-search-discovery
|
|
245
|
+
function getInlineBodyText(entity) {
|
|
246
|
+
const candidates = [
|
|
247
|
+
entity.body,
|
|
248
|
+
entity.markdownBody,
|
|
249
|
+
entity.markdown_body,
|
|
250
|
+
entity.content,
|
|
251
|
+
];
|
|
252
|
+
for (const candidate of candidates) {
|
|
253
|
+
if (typeof candidate === "string" && candidate.trim() !== "") {
|
|
254
|
+
return candidate;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
151
258
|
}
|
|
152
|
-
|
|
259
|
+
// implements REQ-mcp-search-discovery
|
|
260
|
+
function buildSnippet(bodyText, queryForms) {
|
|
153
261
|
const lines = bodyText
|
|
154
262
|
.split(/\r?\n/)
|
|
155
263
|
.map((line) => line.trim())
|
|
156
264
|
.filter(Boolean);
|
|
157
|
-
const
|
|
158
|
-
const matchedLine = lines.find((line) => normalize(line).includes(normalizedQuery)) ?? lines[0];
|
|
265
|
+
const matchedLine = lines.find((line) => isPhraseSearchMatch(buildSearchTextForms(line), queryForms)) ?? lines[0];
|
|
159
266
|
if (!matchedLine) {
|
|
160
267
|
return undefined;
|
|
161
268
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -78,6 +78,10 @@
|
|
|
78
78
|
"./brief-config": {
|
|
79
79
|
"types": "./dist/public/brief-config.d.ts",
|
|
80
80
|
"default": "./dist/public/brief-config.js"
|
|
81
|
+
},
|
|
82
|
+
"./operational-artifacts": {
|
|
83
|
+
"types": "./dist/public/operational-artifacts.d.ts",
|
|
84
|
+
"default": "./dist/public/operational-artifacts.js"
|
|
81
85
|
}
|
|
82
86
|
},
|
|
83
87
|
"types": "./dist/cli.d.ts",
|