kibi-mcp 1.0.0 → 2.1.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/diagnostics.js +8 -8
- package/dist/env.js +21 -30
- package/dist/runtime/mcp-runtime.js +25 -3
- package/dist/server/jobs.js +107 -0
- package/dist/server/json-schema-to-zod.js +31 -10
- package/dist/server/session.js +104 -82
- package/dist/server/tool-registration.js +22 -9
- package/dist/server/tools-runtime.js +28 -21
- package/dist/server/tools.js +128 -28
- package/dist/startup-resolution.js +15 -17
- package/dist/tools/check-impact.js +1 -1
- package/dist/tools/core-module.js +8 -5
- package/dist/tools/discovery-adapter.js +30 -7
- package/dist/tools/model-requirement.js +3 -3
- package/dist/tools/semantic-advisor.js +2 -2
- package/dist/tools/suggest-predicates.js +10 -9
- package/dist/tools-config.js +3 -3
- package/dist/workspace.js +10 -35
- package/package.json +6 -9
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getSpec, statusSpec } from "kibi-runtime";
|
|
2
|
-
import { executeApplyPlan, executeCompileIntent, executeDelete,
|
|
2
|
+
import { executeApplyPlan, executeCompileIntent, executeDelete, executeIngestProof, executeUpsert, validateUpsertSpec, } from "kibi-runtime";
|
|
3
|
+
import { startJob } from "./jobs.js";
|
|
3
4
|
// implements REQ-002, REQ-013
|
|
4
5
|
export function registerConfiguredTools(server, runtime, registerTool) {
|
|
5
6
|
const toolDef = (name) => {
|
|
@@ -26,6 +27,9 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
26
27
|
name,
|
|
27
28
|
effects: publicSpec.effects,
|
|
28
29
|
requiresProlog: publicSpec.requiresProlog,
|
|
30
|
+
...(publicSpec.agentVisibleStructuredData === true
|
|
31
|
+
? { agentVisibleStructuredData: true }
|
|
32
|
+
: {}),
|
|
29
33
|
execute: (args, context) => execute(context, args),
|
|
30
34
|
};
|
|
31
35
|
registerTool(server, name, definition.description, definition.inputSchema, async () => undefined, runtime, spec, definition.annotations, definition.outputSchema);
|
|
@@ -80,7 +84,7 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
80
84
|
});
|
|
81
85
|
register({
|
|
82
86
|
name: "kb_semantic_advisor",
|
|
83
|
-
execute: async (
|
|
87
|
+
execute: async (context, args) => runtime.handleKbSemanticAdvisor(args, context),
|
|
84
88
|
});
|
|
85
89
|
register({
|
|
86
90
|
name: "kb_upsert",
|
|
@@ -96,15 +100,24 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
96
100
|
});
|
|
97
101
|
register({
|
|
98
102
|
name: "kb_check",
|
|
99
|
-
execute: async (context, args) =>
|
|
103
|
+
execute: async (context, args) => {
|
|
104
|
+
const { async: asyncMode, ...checkArgs } = args;
|
|
105
|
+
if (asyncMode !== true) {
|
|
106
|
+
return runtime.handleKbCheck(prologFor(context), args);
|
|
107
|
+
}
|
|
108
|
+
// Long-KB full checks can exceed the tool timeout. Detach into a job
|
|
109
|
+
// and return the kibi.job.v1 receipt immediately; the agent polls
|
|
110
|
+
// kb_job_status for the terminal state and full result.
|
|
111
|
+
return startJob("kb_check", () => runtime.handleKbCheck(prologFor(context), checkArgs));
|
|
112
|
+
},
|
|
100
113
|
});
|
|
101
114
|
register({
|
|
102
115
|
name: "kb_model_requirement",
|
|
103
|
-
execute: async (context, args) => runtime.handleKbModelRequirement(prologFor(context), args),
|
|
116
|
+
execute: async (context, args) => runtime.handleKbModelRequirement(prologFor(context), args, withSessionProlog(context)),
|
|
104
117
|
});
|
|
105
118
|
register({
|
|
106
119
|
name: "kb_suggest_predicates",
|
|
107
|
-
execute: async (context, args) => runtime.handleKbSuggestPredicates(prologFor(context), args),
|
|
120
|
+
execute: async (context, args) => runtime.handleKbSuggestPredicates(prologFor(context), args, withSessionProlog(context)),
|
|
108
121
|
});
|
|
109
122
|
register({
|
|
110
123
|
name: "kb_plan_bootstrap",
|
|
@@ -123,9 +136,9 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
123
136
|
: executeApplyPlan(args, context),
|
|
124
137
|
});
|
|
125
138
|
register({
|
|
126
|
-
name: "
|
|
127
|
-
execute: async (context, args) => runtime.
|
|
128
|
-
? runtime.
|
|
129
|
-
:
|
|
139
|
+
name: "kb_ingest_proof",
|
|
140
|
+
execute: async (context, args) => runtime.handleKbIngestProof
|
|
141
|
+
? runtime.handleKbIngestProof(args, context)
|
|
142
|
+
: executeIngestProof(args, context),
|
|
130
143
|
});
|
|
131
144
|
}
|
|
@@ -49,10 +49,15 @@ function adaptProlog(prolog) {
|
|
|
49
49
|
if (existing) {
|
|
50
50
|
return existing;
|
|
51
51
|
}
|
|
52
|
+
const engine = prolog;
|
|
53
|
+
const queryEntities = engine.queryEntities;
|
|
54
|
+
const searchEntities = engine.searchEntities;
|
|
55
|
+
const storageStatus = engine.storageStatus;
|
|
56
|
+
const queryStatusJson = engine.queryStatusJson;
|
|
52
57
|
let lastResult = null;
|
|
53
58
|
const port = {
|
|
54
|
-
query: async (goal) => {
|
|
55
|
-
lastResult = await
|
|
59
|
+
query: async (goal, signal) => {
|
|
60
|
+
lastResult = await engine.query(goal, signal);
|
|
56
61
|
return lastResult;
|
|
57
62
|
},
|
|
58
63
|
nextSolution: async () => {
|
|
@@ -60,39 +65,41 @@ function adaptProlog(prolog) {
|
|
|
60
65
|
lastResult = null;
|
|
61
66
|
return result;
|
|
62
67
|
},
|
|
63
|
-
save: () =>
|
|
64
|
-
...(typeof
|
|
65
|
-
"function"
|
|
68
|
+
save: (signal) => engine.query("kb_save", signal),
|
|
69
|
+
...(typeof queryEntities === "function"
|
|
66
70
|
? {
|
|
67
|
-
|
|
71
|
+
// The captured method uses .call(engine, ...) below to preserve the
|
|
72
|
+
// EngineClient receiver (unbound queryStatusJson breaks this.command).
|
|
73
|
+
queryEntities: (input, signal) => queryEntities.call(engine, input, signal),
|
|
68
74
|
}
|
|
69
75
|
: {}),
|
|
70
|
-
...(typeof
|
|
71
|
-
"function"
|
|
76
|
+
...(typeof searchEntities === "function"
|
|
72
77
|
? {
|
|
73
|
-
searchEntities: (input) =>
|
|
78
|
+
searchEntities: (input, signal) => searchEntities.call(engine, input, signal),
|
|
74
79
|
}
|
|
75
80
|
: {}),
|
|
76
|
-
...(typeof
|
|
77
|
-
"function"
|
|
81
|
+
...(typeof storageStatus === "function"
|
|
78
82
|
? {
|
|
79
|
-
storageStatus: () =>
|
|
83
|
+
storageStatus: () => storageStatus.call(engine),
|
|
80
84
|
}
|
|
81
85
|
: {}),
|
|
82
|
-
...(typeof
|
|
83
|
-
"function"
|
|
86
|
+
...(typeof queryStatusJson === "function"
|
|
84
87
|
? {
|
|
85
|
-
queryStatusJson: () =>
|
|
88
|
+
queryStatusJson: (signal) => queryStatusJson.call(engine, signal),
|
|
86
89
|
}
|
|
87
90
|
: {}),
|
|
88
91
|
};
|
|
89
92
|
prologPorts.set(prolog, port);
|
|
90
93
|
return port;
|
|
91
94
|
}
|
|
95
|
+
/** Test seam: adapt a session engine so AbortSignal reaches EngineClient/PrologPort methods. */
|
|
96
|
+
export function _adaptPrologForTests(prolog) {
|
|
97
|
+
return adaptProlog(prolog);
|
|
98
|
+
}
|
|
92
99
|
const operationRuntime = createMcpRuntime({
|
|
93
100
|
workspaceRoot: resolveWorkspaceRoot(),
|
|
94
|
-
activeBranchName: async () => (await getSessionModule()).
|
|
95
|
-
attachedBranchKbPath: async () => (await getSessionModule()).
|
|
101
|
+
activeBranchName: async () => (await getSessionModule()).getActiveBranchName(),
|
|
102
|
+
attachedBranchKbPath: async () => (await getSessionModule()).getAttachedBranchKbPath(),
|
|
96
103
|
ensureProlog: async () => (await getSessionModule()).ensureProlog(),
|
|
97
104
|
adaptProlog,
|
|
98
105
|
fs: nodeFilesystem,
|
|
@@ -100,7 +107,7 @@ const operationRuntime = createMcpRuntime({
|
|
|
100
107
|
net: { fetch: (input, init) => globalThis.fetch(input, init) },
|
|
101
108
|
refreshAttachedBranchStamp: async () => {
|
|
102
109
|
const session = await getSessionModule();
|
|
103
|
-
const kbPath = session.
|
|
110
|
+
const kbPath = session.getAttachedBranchKbPath();
|
|
104
111
|
if (kbPath) {
|
|
105
112
|
try {
|
|
106
113
|
session.updateAttachedBranchStamp(await readBranchKbStamp(kbPath));
|
|
@@ -124,12 +131,12 @@ export const DEFAULT_TOOLS_RUNTIME = {
|
|
|
124
131
|
// runtime interface with looser Record<string, unknown> inputSchema. The cast is safe
|
|
125
132
|
// because the tool definitions are statically authored and validated at startup.
|
|
126
133
|
tools: TOOLS,
|
|
127
|
-
activeBranchName: async () => (await getSessionModule()).
|
|
134
|
+
activeBranchName: async () => (await getSessionModule()).getActiveBranchName(),
|
|
128
135
|
ensureProlog: async () => (await getSessionModule()).ensureProlog(),
|
|
129
136
|
resetProlog: async (reason) => (await getSessionModule()).resetProlog(reason),
|
|
130
137
|
inFlightRequests: async () => (await getSessionModule()).inFlightRequests,
|
|
131
|
-
isShuttingDown: async () => (await getSessionModule()).
|
|
132
|
-
prologProcess: async () => (await getSessionModule()).
|
|
138
|
+
isShuttingDown: async () => (await getSessionModule()).getIsShuttingDown(),
|
|
139
|
+
prologProcess: async () => (await getSessionModule()).getPrologProcess(),
|
|
133
140
|
operationRuntime,
|
|
134
141
|
handleKbCheck,
|
|
135
142
|
handleKbCoverage,
|
package/dist/server/tools.js
CHANGED
|
@@ -20,6 +20,7 @@ import { executeOperation } from "kibi-runtime";
|
|
|
20
20
|
import { operationData, toKibiResult } from "kibi-runtime";
|
|
21
21
|
import { isMcpDebugEnabled } from "../env.js";
|
|
22
22
|
import { appendDiagnosticErrorUsage, appendDiagnosticSuccessUsage, } from "./diagnostic-usage.js";
|
|
23
|
+
import { getJob, jobSnapshot } from "./jobs.js";
|
|
23
24
|
import { jsonSchemaToZod } from "./json-schema-to-zod.js";
|
|
24
25
|
import { registerConfiguredTools } from "./tool-registration.js";
|
|
25
26
|
import { DEFAULT_TOOLS_RUNTIME, _resetSessionModulePromise, _setToolsServerDepsForTests, } from "./tools-runtime.js";
|
|
@@ -27,10 +28,32 @@ export { jsonSchemaToZod } from "./json-schema-to-zod.js";
|
|
|
27
28
|
export { _resetSessionModulePromise, _setToolsServerDepsForTests };
|
|
28
29
|
const DEFAULT_TOOL_TIMEOUT_MS = 90_000;
|
|
29
30
|
const TOOL_TIMEOUT_ENV = "KIBI_MCP_TOOL_TIMEOUT_MS";
|
|
30
|
-
// Give cancellation
|
|
31
|
-
//
|
|
32
|
-
//
|
|
31
|
+
// Give cancellation a short bounded grace period, but do not hold the MCP
|
|
32
|
+
// request open for the full five-second shutdown budget when a handler never
|
|
33
|
+
// observes AbortSignal.
|
|
33
34
|
const TOOL_TIMEOUT_GRACE_MS = 100;
|
|
35
|
+
// Mutations that ignore AbortSignal may leave the journal indeterminate. Only
|
|
36
|
+
// then tear down the shared session engine — never on ordinary read timeouts,
|
|
37
|
+
// which would reject sibling tools with "Kibi engine connection closed".
|
|
38
|
+
const TOOL_TIMEOUT_WEDGE_MS = 1_000;
|
|
39
|
+
function isMutationEffect(effects) {
|
|
40
|
+
return (effects?.some((effect) => effect === "kb-write" || effect === "workspace-write") ?? false);
|
|
41
|
+
}
|
|
42
|
+
/** Embed envelope data in content text for opted-in tools (hosts that hide structuredContent).
|
|
43
|
+
* Preserves non-text content parts; only augments/replaces text parts. */
|
|
44
|
+
function withAgentVisibleText(content, data) {
|
|
45
|
+
const textParts = content.filter((part) => part.type === "text" && typeof part.text === "string");
|
|
46
|
+
const nonTextParts = content.filter((part) => part.type !== "text");
|
|
47
|
+
const summary = textParts
|
|
48
|
+
.map((part) => part.text)
|
|
49
|
+
.join(" ")
|
|
50
|
+
.replace(/\s+/g, " ")
|
|
51
|
+
.trim();
|
|
52
|
+
const payload = JSON.stringify(data);
|
|
53
|
+
// Prefer JSON-first so first-line-only host summaries still include machine data.
|
|
54
|
+
const text = summary.length > 0 ? `${payload}\n${summary}` : payload;
|
|
55
|
+
return [{ type: "text", text }, ...nonTextParts];
|
|
56
|
+
}
|
|
34
57
|
// implements REQ-008
|
|
35
58
|
function debugLog(...args) {
|
|
36
59
|
if (isMcpDebugEnabled()) {
|
|
@@ -63,27 +86,28 @@ async function withToolTimeout(toolName, operation, onTimeout) {
|
|
|
63
86
|
const timeoutMs = getToolTimeoutMs();
|
|
64
87
|
let timeout;
|
|
65
88
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
timeout
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.then(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
90
|
+
timeout = setTimeout(() => {
|
|
91
|
+
const error = createToolTimeoutError(toolName, timeoutMs);
|
|
92
|
+
// Do not report the timeout until cancellation/reset has completed
|
|
93
|
+
// and the original request has reached a terminal state. This is
|
|
94
|
+
// especially important for source-first mutations: a caller must
|
|
95
|
+
// never retry while the authoritative journal is still deciding its
|
|
96
|
+
// outcome.
|
|
97
|
+
void onTimeout(error, timeoutMs)
|
|
98
|
+
.then(async () => {
|
|
99
|
+
await Promise.race([
|
|
100
|
+
operation.then(() => undefined, () => undefined),
|
|
101
|
+
new Promise((resolve) => setTimeout(resolve, TOOL_TIMEOUT_GRACE_MS)),
|
|
102
|
+
]);
|
|
103
|
+
})
|
|
104
|
+
.finally(() => reject(error));
|
|
105
|
+
}, timeoutMs);
|
|
106
|
+
});
|
|
107
|
+
// When abort settles `operation` before this timeout promise rejects,
|
|
108
|
+
// the late reject must not become an unhandled rejection.
|
|
109
|
+
timeoutPromise.catch(() => undefined);
|
|
110
|
+
return await Promise.race([operation, timeoutPromise]);
|
|
87
111
|
}
|
|
88
112
|
finally {
|
|
89
113
|
if (timeout) {
|
|
@@ -138,15 +162,30 @@ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations, outputSchema) {
|
|
|
138
162
|
try {
|
|
139
163
|
// Execute handler
|
|
140
164
|
const result = await withToolTimeout(name, handlerPromise, async (error) => {
|
|
141
|
-
resetAttempted = true;
|
|
142
165
|
controller.abort(error);
|
|
166
|
+
// Read/discovery timeouts must cancel in-flight RPCs without
|
|
167
|
+
// terminating the shared session engine. A global resetProlog here
|
|
168
|
+
// rejects sibling tools with "Kibi engine connection closed".
|
|
169
|
+
if (!isMutationEffect(operationSpec.effects)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const settled = await Promise.race([
|
|
173
|
+
handlerPromise.then(() => true, () => true),
|
|
174
|
+
new Promise((resolve) => setTimeout(() => resolve(false), TOOL_TIMEOUT_WEDGE_MS)),
|
|
175
|
+
]);
|
|
176
|
+
if (settled) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
resetAttempted = true;
|
|
143
180
|
try {
|
|
144
181
|
await runtime.resetProlog(`tool timeout: ${name}`);
|
|
145
182
|
resetSucceeded = true;
|
|
146
183
|
}
|
|
147
|
-
catch (
|
|
184
|
+
catch (resetFailure) {
|
|
148
185
|
resetError =
|
|
149
|
-
|
|
186
|
+
resetFailure instanceof Error
|
|
187
|
+
? resetFailure.message
|
|
188
|
+
: String(resetFailure);
|
|
150
189
|
}
|
|
151
190
|
});
|
|
152
191
|
const data = operationData(result);
|
|
@@ -168,8 +207,13 @@ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations, outputSchema) {
|
|
|
168
207
|
typeof result === "object" &&
|
|
169
208
|
!Array.isArray(result) &&
|
|
170
209
|
"content" in result) {
|
|
210
|
+
const withContent = result;
|
|
211
|
+
const content = operationSpec.agentVisibleStructuredData === true
|
|
212
|
+
? withAgentVisibleText(withContent.content, data)
|
|
213
|
+
: withContent.content;
|
|
171
214
|
return {
|
|
172
215
|
...result,
|
|
216
|
+
content,
|
|
173
217
|
structuredContent: envelope,
|
|
174
218
|
};
|
|
175
219
|
}
|
|
@@ -194,7 +238,7 @@ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations, outputSchema) {
|
|
|
194
238
|
});
|
|
195
239
|
}
|
|
196
240
|
if (isToolTimeoutError(error) &&
|
|
197
|
-
operationSpec.effects
|
|
241
|
+
isMutationEffect(operationSpec.effects)) {
|
|
198
242
|
const recoveryActions = [
|
|
199
243
|
{
|
|
200
244
|
operation: "kb_status",
|
|
@@ -263,4 +307,60 @@ export function registerAllTools(server,
|
|
|
263
307
|
// INTENTIONAL: same generic bridge cast as addTool — see comment there.
|
|
264
308
|
runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
265
309
|
registerConfiguredTools(server, runtime, addTool);
|
|
310
|
+
registerJobStatusTool(server);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* kb_job_status polls background jobs started by long-running operations
|
|
314
|
+
* (currently `kb_check` with `async: true`). This is an MCP-server-native
|
|
315
|
+
* companion to the job receipts, not part of the canonical CLI operation
|
|
316
|
+
* catalog: jobs live in the server process and have no CLI counterpart.
|
|
317
|
+
*/
|
|
318
|
+
function registerJobStatusTool(server) {
|
|
319
|
+
addTool(server, "kb_job_status", "Poll a background job started with async:true (e.g. kb_check async jobs). Returns the kibi.job.v1 state: running, succeeded (with the full result), or failed (with the error).", {
|
|
320
|
+
type: "object",
|
|
321
|
+
properties: {
|
|
322
|
+
jobId: {
|
|
323
|
+
type: "string",
|
|
324
|
+
description: "The jobId from the kibi.job.v1 receipt.",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
required: ["jobId"],
|
|
328
|
+
}, async (args) => {
|
|
329
|
+
const jobId = typeof args.jobId === "string" ? args.jobId : "";
|
|
330
|
+
const job = jobId === "" ? undefined : getJob(jobId);
|
|
331
|
+
if (job === undefined) {
|
|
332
|
+
return {
|
|
333
|
+
content: [
|
|
334
|
+
{
|
|
335
|
+
type: "text",
|
|
336
|
+
text: JSON.stringify({
|
|
337
|
+
kibiProtocol: 1,
|
|
338
|
+
jobVersion: "kibi.job.v1",
|
|
339
|
+
jobId,
|
|
340
|
+
status: "unknown",
|
|
341
|
+
error: "No such job. Jobs are process-local and dropped on server restart; check the jobId or re-run the operation with async:true.",
|
|
342
|
+
}),
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
structuredContent: {
|
|
346
|
+
kibiProtocol: 1,
|
|
347
|
+
jobVersion: "kibi.job.v1",
|
|
348
|
+
jobId,
|
|
349
|
+
status: "unknown",
|
|
350
|
+
error: "No such job. Jobs are process-local and dropped on server restart; check the jobId or re-run the operation with async:true.",
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
const snapshot = jobSnapshot(job);
|
|
355
|
+
return {
|
|
356
|
+
content: [{ type: "text", text: JSON.stringify(snapshot) }],
|
|
357
|
+
structuredContent: snapshot,
|
|
358
|
+
};
|
|
359
|
+
}, undefined, undefined, {
|
|
360
|
+
title: "Poll a Kibi background job",
|
|
361
|
+
readOnlyHint: true,
|
|
362
|
+
destructiveHint: false,
|
|
363
|
+
idempotentHint: true,
|
|
364
|
+
openWorldHint: false,
|
|
365
|
+
});
|
|
266
366
|
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
-
import
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
19
|
import { createRequire } from "node:module";
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { fileURLToPath } from "node:url";
|
|
@@ -28,35 +28,33 @@ function toEntrypointPath(entrypointUrl) {
|
|
|
28
28
|
return path.resolve(entrypointUrl);
|
|
29
29
|
}
|
|
30
30
|
function readJson(filePath) {
|
|
31
|
-
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
31
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
32
|
+
}
|
|
33
|
+
export function nextAncestorDirectory(current) {
|
|
34
|
+
const parent = path.dirname(current);
|
|
35
|
+
return parent === current ? undefined : parent;
|
|
32
36
|
}
|
|
33
37
|
function nearestPackageJson(startPath) {
|
|
34
38
|
let current = path.dirname(startPath);
|
|
35
|
-
while (
|
|
39
|
+
while (current !== undefined) {
|
|
36
40
|
const candidate = path.join(current, "package.json");
|
|
37
|
-
if (existsSync(candidate)) {
|
|
41
|
+
if (fs.existsSync(candidate)) {
|
|
38
42
|
return candidate;
|
|
39
43
|
}
|
|
40
|
-
|
|
41
|
-
if (parent === current) {
|
|
42
|
-
throw new Error(`Unable to find package.json for ${startPath}`);
|
|
43
|
-
}
|
|
44
|
-
current = parent;
|
|
44
|
+
current = nextAncestorDirectory(current);
|
|
45
45
|
}
|
|
46
|
+
throw new Error(`Unable to find package.json for ${startPath}`);
|
|
46
47
|
}
|
|
47
48
|
function nearestPackageJsonFromDirectory(startPath) {
|
|
48
49
|
let current = path.resolve(startPath);
|
|
49
|
-
while (
|
|
50
|
+
while (current !== undefined) {
|
|
50
51
|
const candidate = path.join(current, "package.json");
|
|
51
|
-
if (existsSync(candidate)) {
|
|
52
|
+
if (fs.existsSync(candidate)) {
|
|
52
53
|
return candidate;
|
|
53
54
|
}
|
|
54
|
-
|
|
55
|
-
if (parent === current) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
current = parent;
|
|
55
|
+
current = nextAncestorDirectory(current);
|
|
59
56
|
}
|
|
57
|
+
return null;
|
|
60
58
|
}
|
|
61
59
|
function packageInfoFromEntrypoint(entrypoint) {
|
|
62
60
|
const normalizedEntrypoint = path.resolve(entrypoint);
|
|
@@ -82,7 +80,7 @@ export function resolveProjectLocalMcp(cwd) {
|
|
|
82
80
|
}
|
|
83
81
|
const projectRequire = createRequire(projectPackageJson);
|
|
84
82
|
const resolved = projectRequire.resolve(PACKAGE_NAME);
|
|
85
|
-
const entrypoint = realpathSync.native?.(resolved) ?? realpathSync(resolved);
|
|
83
|
+
const entrypoint = fs.realpathSync.native?.(resolved) ?? fs.realpathSync(resolved);
|
|
86
84
|
return packageInfoFromEntrypoint(entrypoint);
|
|
87
85
|
}
|
|
88
86
|
catch (error) {
|
|
@@ -22,7 +22,7 @@ export function hasImpactOptions(args) {
|
|
|
22
22
|
args.includeWorkingTreeDiff ||
|
|
23
23
|
(args.sourceFiles && args.sourceFiles.length > 0));
|
|
24
24
|
}
|
|
25
|
-
export function analyzeKbCheckImpact(workspaceRoot, args) {
|
|
25
|
+
export async function analyzeKbCheckImpact(workspaceRoot, args) {
|
|
26
26
|
if (!hasImpactOptions(args)) {
|
|
27
27
|
return undefined;
|
|
28
28
|
}
|
|
@@ -2,6 +2,13 @@ import path from "node:path";
|
|
|
2
2
|
import { PrologProcess, resolveKbPlPath } from "kibi-runtime";
|
|
3
3
|
import { escapeAtomContent } from "kibi-runtime";
|
|
4
4
|
import { getCoreModulePathOverride, getKbPlPathOverride } from "../env.js";
|
|
5
|
+
export function parseMaybeDoubleEncodedJson(rawJson) {
|
|
6
|
+
let parsed = JSON.parse(rawJson);
|
|
7
|
+
if (typeof parsed === "string") {
|
|
8
|
+
parsed = JSON.parse(parsed);
|
|
9
|
+
}
|
|
10
|
+
return parsed;
|
|
11
|
+
}
|
|
5
12
|
function isPrologProcess(value) {
|
|
6
13
|
return (value instanceof PrologProcess ||
|
|
7
14
|
(value !== null &&
|
|
@@ -60,11 +67,7 @@ export async function runJsonModuleQuery(prolog, fileName, goal, errorLabel) {
|
|
|
60
67
|
if (!rawJson) {
|
|
61
68
|
throw new Error(`${errorLabel} query returned no JsonString binding`);
|
|
62
69
|
}
|
|
63
|
-
|
|
64
|
-
if (typeof parsed === "string") {
|
|
65
|
-
parsed = JSON.parse(parsed);
|
|
66
|
-
}
|
|
67
|
-
return parsed;
|
|
70
|
+
return parseMaybeDoubleEncodedJson(rawJson);
|
|
68
71
|
}
|
|
69
72
|
async function runInteractiveModuleQuery(prolog, modulePath, goal, errorLabel) {
|
|
70
73
|
const loadResult = await prolog.query(`use_module('${modulePath}')`);
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
2
2
|
export function createDiscoveryContext(prolog, baseContext) {
|
|
3
3
|
// implements REQ-kibi-operation-interface-parity
|
|
4
|
+
// Keep this adapter aligned with tools-runtime adaptProlog: indexed discovery
|
|
5
|
+
// and typed status must reach EngineClient command frames (not query-only
|
|
6
|
+
// fallbacks), and AbortSignal must forward for MCP tool cancellation.
|
|
7
|
+
const engine = prolog;
|
|
8
|
+
const queryEntities = engine.queryEntities;
|
|
9
|
+
const searchEntities = engine.searchEntities;
|
|
10
|
+
const storageStatus = engine.storageStatus;
|
|
11
|
+
const queryStatusJson = engine.queryStatusJson;
|
|
4
12
|
let lastResult = null;
|
|
5
13
|
const mode = prolog
|
|
6
14
|
.useOneShotMode;
|
|
15
|
+
const signal = baseContext?.signal ?? new AbortController().signal;
|
|
7
16
|
const port = {
|
|
8
|
-
query: async (goal) => {
|
|
9
|
-
lastResult = await
|
|
17
|
+
query: async (goal, querySignal) => {
|
|
18
|
+
lastResult = await engine.query(goal, querySignal ?? signal);
|
|
10
19
|
return lastResult;
|
|
11
20
|
},
|
|
12
21
|
oneShotMode: mode === undefined
|
|
@@ -17,18 +26,32 @@ export function createDiscoveryContext(prolog, baseContext) {
|
|
|
17
26
|
lastResult = null;
|
|
18
27
|
return result;
|
|
19
28
|
},
|
|
20
|
-
save: () =>
|
|
21
|
-
...(typeof
|
|
22
|
-
"function"
|
|
29
|
+
save: (saveSignal) => engine.query("kb_save", saveSignal ?? signal),
|
|
30
|
+
...(typeof queryEntities === "function"
|
|
23
31
|
? {
|
|
24
|
-
|
|
32
|
+
queryEntities: (input, querySignal) => queryEntities.call(engine, input, querySignal ?? signal),
|
|
33
|
+
}
|
|
34
|
+
: {}),
|
|
35
|
+
...(typeof searchEntities === "function"
|
|
36
|
+
? {
|
|
37
|
+
searchEntities: (input, querySignal) => searchEntities.call(engine, input, querySignal ?? signal),
|
|
38
|
+
}
|
|
39
|
+
: {}),
|
|
40
|
+
...(typeof storageStatus === "function"
|
|
41
|
+
? {
|
|
42
|
+
storageStatus: () => storageStatus.call(engine),
|
|
43
|
+
}
|
|
44
|
+
: {}),
|
|
45
|
+
...(typeof queryStatusJson === "function"
|
|
46
|
+
? {
|
|
47
|
+
queryStatusJson: (statusSignal) => queryStatusJson.call(engine, statusSignal ?? signal),
|
|
25
48
|
}
|
|
26
49
|
: {}),
|
|
27
50
|
};
|
|
28
51
|
return {
|
|
29
52
|
...(baseContext ?? {}),
|
|
30
53
|
workspaceRoot: baseContext?.workspaceRoot ?? resolveWorkspaceRoot(),
|
|
31
|
-
signal
|
|
54
|
+
signal,
|
|
32
55
|
clock: baseContext?.clock ?? (() => new Date()),
|
|
33
56
|
prolog: port,
|
|
34
57
|
};
|
|
@@ -2,11 +2,11 @@ import { modelRequirementSpec } from "kibi-runtime";
|
|
|
2
2
|
import { estimateNormativeSignalConfidence, extractRequirementClaim, getWorkspaceMigrationWarning, strictWriteSetToApplyPlan, writeSetPrimaryEntityId, } from "kibi-runtime";
|
|
3
3
|
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
4
4
|
export { estimateNormativeSignalConfidence, extractRequirementClaim, getWorkspaceMigrationWarning, strictWriteSetToApplyPlan, writeSetPrimaryEntityId, };
|
|
5
|
-
export async function handleKbModelRequirement(_prolog, args) {
|
|
6
|
-
const
|
|
5
|
+
export async function handleKbModelRequirement(_prolog, args, context) {
|
|
6
|
+
const resolved = context ?? {
|
|
7
7
|
workspaceRoot: resolveWorkspaceRoot(),
|
|
8
8
|
signal: new AbortController().signal,
|
|
9
9
|
clock: () => new Date(),
|
|
10
10
|
};
|
|
11
|
-
return modelRequirementSpec.execute(args,
|
|
11
|
+
return modelRequirementSpec.execute(args, resolved);
|
|
12
12
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { suggestPredicatesSpec } from "kibi-runtime";
|
|
2
2
|
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
3
3
|
import { createDiscoveryContext } from "./discovery-adapter.js";
|
|
4
|
-
export async function handleKbSuggestPredicates(prolog, args) {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
export async function handleKbSuggestPredicates(prolog, args, context) {
|
|
5
|
+
const resolved = context ??
|
|
6
|
+
(prolog === null
|
|
7
|
+
? {
|
|
8
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
9
|
+
signal: new AbortController().signal,
|
|
10
|
+
clock: () => new Date(),
|
|
11
|
+
}
|
|
12
|
+
: createDiscoveryContext(prolog));
|
|
13
|
+
return suggestPredicatesSpec.execute(args, resolved);
|
|
13
14
|
}
|
package/dist/tools-config.js
CHANGED
|
@@ -38,7 +38,7 @@ const MCP_TOOL_ORDER = [
|
|
|
38
38
|
"kb_plan_bootstrap",
|
|
39
39
|
"kb_compile_intent",
|
|
40
40
|
"kb_apply_plan",
|
|
41
|
-
"
|
|
41
|
+
"kb_ingest_proof",
|
|
42
42
|
];
|
|
43
43
|
// implements REQ-002
|
|
44
44
|
const TOOL_ANNOTATIONS = {
|
|
@@ -134,8 +134,8 @@ const TOOL_ANNOTATIONS = {
|
|
|
134
134
|
idempotentHint: false,
|
|
135
135
|
openWorldHint: false,
|
|
136
136
|
},
|
|
137
|
-
|
|
138
|
-
title: "Ingest
|
|
137
|
+
kb_ingest_proof: {
|
|
138
|
+
title: "Ingest proof-run evidence",
|
|
139
139
|
readOnlyHint: false,
|
|
140
140
|
destructiveHint: false,
|
|
141
141
|
idempotentHint: false,
|