kibi-mcp 1.0.0 → 2.0.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/runtime/mcp-runtime.js +4 -1
- package/dist/server/jobs.js +107 -0
- package/dist/server/json-schema-to-zod.js +17 -10
- package/dist/server/session.js +104 -82
- package/dist/server/tool-registration.js +16 -6
- package/dist/server/tools-runtime.js +6 -6
- package/dist/server/tools.js +57 -0
- package/dist/startup-resolution.js +15 -17
- package/dist/tools/core-module.js +8 -5
- package/dist/tools-config.js +3 -3
- package/dist/workspace.js +7 -6
- package/package.json +5 -4
package/dist/diagnostics.js
CHANGED
|
@@ -140,12 +140,12 @@ function appendMutationFields(fields, args) {
|
|
|
140
140
|
fields.mutation_target = `${type}:${id}`;
|
|
141
141
|
}
|
|
142
142
|
const RECEIPT_GAP_CODES = new Set([
|
|
143
|
-
"
|
|
144
|
-
"
|
|
145
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
"
|
|
143
|
+
"missing_proof_receipt",
|
|
144
|
+
"stale_proof_receipt",
|
|
145
|
+
"failed_proof_receipt",
|
|
146
|
+
"invalid_proof_receipt",
|
|
147
|
+
"proof_contract_mismatch",
|
|
148
|
+
"proof_snapshot_unavailable",
|
|
149
149
|
]);
|
|
150
150
|
const MAX_COVERAGE_RECEIPT_GAPS = 50;
|
|
151
151
|
function coverageReceiptGapDetails(rows) {
|
|
@@ -214,8 +214,8 @@ function appendCoverageFields(fields, args, structuredContent) {
|
|
|
214
214
|
const metadata = isRecord(structuredContent.meta)
|
|
215
215
|
? structuredContent.meta
|
|
216
216
|
: undefined;
|
|
217
|
-
if (typeof metadata?.
|
|
218
|
-
fields.
|
|
217
|
+
if (typeof metadata?.proofSnapshot === "string") {
|
|
218
|
+
fields.coverage_proof_snapshot = metadata.proofSnapshot;
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
function appendPredicateSuggestionFields(fields, structuredContent) {
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { resolveBranchAttachment } from "kibi-runtime";
|
|
2
|
+
export function attachedContextWithProlog(withAttachment, prolog) {
|
|
3
|
+
return { ...withAttachment, prolog };
|
|
4
|
+
}
|
|
2
5
|
// implements REQ-kibi-operation-interface-parity
|
|
3
6
|
export function createMcpRuntime(session) {
|
|
4
7
|
const sessionPrologs = new WeakMap();
|
|
@@ -51,7 +54,7 @@ export function createMcpRuntime(session) {
|
|
|
51
54
|
throw new Error(`Legacy branch attachment requires explicit migration before '${spec.name}'; run kibi branch migrate --from ${attachment.kbBranch} --to ${attachment.gitBranch} --apply`);
|
|
52
55
|
}
|
|
53
56
|
if (options.prolog) {
|
|
54
|
-
return
|
|
57
|
+
return attachedContextWithProlog(withAttachment, options.prolog);
|
|
55
58
|
}
|
|
56
59
|
const sessionProlog = await session.ensureProlog();
|
|
57
60
|
const operationContext = {
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import process from "node:process";
|
|
19
|
+
const MAX_FINISHED_JOBS = 32;
|
|
20
|
+
const jobs = new Map();
|
|
21
|
+
let jobCounter = 0;
|
|
22
|
+
function nextJobId(tool) {
|
|
23
|
+
jobCounter += 1;
|
|
24
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
25
|
+
return `job-${tool}-${jobCounter.toString(36)}-${random}`;
|
|
26
|
+
}
|
|
27
|
+
function evictOldestFinished() {
|
|
28
|
+
for (const [id, record] of jobs) {
|
|
29
|
+
if (jobs.size <= MAX_FINISHED_JOBS)
|
|
30
|
+
break;
|
|
31
|
+
if (record.status !== "running") {
|
|
32
|
+
jobs.delete(id);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Start a background job for `tool`. The factory's promise runs detached;
|
|
38
|
+
* callers return the `kibi.job.v1` receipt immediately so the MCP request is
|
|
39
|
+
* never held open for the duration of the operation.
|
|
40
|
+
*/
|
|
41
|
+
// implements REQ-002
|
|
42
|
+
export function startJob(tool, factory) {
|
|
43
|
+
const jobId = nextJobId(tool);
|
|
44
|
+
const record = {
|
|
45
|
+
jobId,
|
|
46
|
+
tool,
|
|
47
|
+
startedAt: new Date().toISOString(),
|
|
48
|
+
status: "running",
|
|
49
|
+
};
|
|
50
|
+
jobs.set(jobId, record);
|
|
51
|
+
void (async () => {
|
|
52
|
+
try {
|
|
53
|
+
const result = await factory();
|
|
54
|
+
record.status = "succeeded";
|
|
55
|
+
record.finishedAt = new Date().toISOString();
|
|
56
|
+
record.result =
|
|
57
|
+
result !== null && typeof result === "object"
|
|
58
|
+
? result
|
|
59
|
+
: { value: result };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
record.status = "failed";
|
|
63
|
+
record.finishedAt = new Date().toISOString();
|
|
64
|
+
record.error = error instanceof Error ? error.message : String(error);
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
evictOldestFinished();
|
|
68
|
+
}
|
|
69
|
+
})().catch((error) => {
|
|
70
|
+
// Defensive: the internal bookkeeping above must never crash the server
|
|
71
|
+
// process via an unhandled rejection.
|
|
72
|
+
process.stderr.write(`[kibi-mcp] job bookkeeping error for ${jobId}: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
kibiProtocol: 1,
|
|
76
|
+
jobVersion: "kibi.job.v1",
|
|
77
|
+
jobId,
|
|
78
|
+
tool,
|
|
79
|
+
status: "running",
|
|
80
|
+
pollWith: "kb_job_status",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// implements REQ-002
|
|
84
|
+
export function getJob(jobId) {
|
|
85
|
+
return jobs.get(jobId);
|
|
86
|
+
}
|
|
87
|
+
/** Public JSON shape for kb_job_status responses. */
|
|
88
|
+
// implements REQ-002
|
|
89
|
+
export function jobSnapshot(job) {
|
|
90
|
+
return {
|
|
91
|
+
kibiProtocol: 1,
|
|
92
|
+
jobVersion: "kibi.job.v1",
|
|
93
|
+
jobId: job.jobId,
|
|
94
|
+
tool: job.tool,
|
|
95
|
+
status: job.status,
|
|
96
|
+
startedAt: job.startedAt,
|
|
97
|
+
...(job.finishedAt === undefined ? {} : { finishedAt: job.finishedAt }),
|
|
98
|
+
...(job.result === undefined ? {} : { result: job.result }),
|
|
99
|
+
...(job.error === undefined ? {} : { error: job.error }),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Test seam: drop all job state. */
|
|
103
|
+
// implements REQ-002
|
|
104
|
+
export function resetJobsForTests() {
|
|
105
|
+
jobs.clear();
|
|
106
|
+
jobCounter = 0;
|
|
107
|
+
}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
export function firstDefined(items) {
|
|
3
|
+
return items[0];
|
|
4
|
+
}
|
|
5
|
+
export function describedAnySchema(description) {
|
|
6
|
+
const anySchema = z.any();
|
|
7
|
+
return description ? anySchema.describe(description) : anySchema;
|
|
8
|
+
}
|
|
9
|
+
export function singleLiteralOrAny(literalSchemas, description) {
|
|
10
|
+
const single = firstDefined(literalSchemas);
|
|
11
|
+
if (!single) {
|
|
12
|
+
return describedAnySchema(description);
|
|
13
|
+
}
|
|
14
|
+
return description ? single.describe(description) : single;
|
|
15
|
+
}
|
|
2
16
|
function hasRequiredProperties(value, schema) {
|
|
3
17
|
if (schema === null || typeof schema !== "object")
|
|
4
18
|
return false;
|
|
@@ -64,11 +78,7 @@ export function jsonSchemaToZod(schema) {
|
|
|
64
78
|
}
|
|
65
79
|
const literalSchemas = literals.map((value) => z.literal(value));
|
|
66
80
|
if (literalSchemas.length === 1) {
|
|
67
|
-
|
|
68
|
-
if (!single) {
|
|
69
|
-
return description ? z.any().describe(description) : z.any();
|
|
70
|
-
}
|
|
71
|
-
return description ? single.describe(description) : single;
|
|
81
|
+
return singleLiteralOrAny(literalSchemas, description);
|
|
72
82
|
}
|
|
73
83
|
const union = z.union(literalSchemas);
|
|
74
84
|
return description ? union.describe(description) : union;
|
|
@@ -195,10 +205,7 @@ export function jsonSchemaToZod(schema) {
|
|
|
195
205
|
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
196
206
|
return description ? n.describe(description) : n;
|
|
197
207
|
}
|
|
198
|
-
default:
|
|
199
|
-
|
|
200
|
-
const description = typeof obj.description === "string" ? obj.description : undefined;
|
|
201
|
-
return description ? anySchema.describe(description) : anySchema;
|
|
202
|
-
}
|
|
208
|
+
default:
|
|
209
|
+
return describedAnySchema(typeof obj.description === "string" ? obj.description : undefined);
|
|
203
210
|
}
|
|
204
211
|
}
|
package/dist/server/session.js
CHANGED
|
@@ -37,35 +37,53 @@ const defaultSessionDeps = {
|
|
|
37
37
|
resolveWorkspaceRoot,
|
|
38
38
|
};
|
|
39
39
|
let sessionDeps = { ...defaultSessionDeps };
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
const state = {
|
|
41
|
+
prologProcess: null,
|
|
42
|
+
isInitialized: false,
|
|
43
|
+
activeBranchName: "develop",
|
|
44
|
+
ensurePrologTail: Promise.resolve(),
|
|
45
|
+
prologResetGeneration: 0,
|
|
46
|
+
attachedBranchKbPath: null,
|
|
47
|
+
attachedBranchStamp: null,
|
|
48
|
+
isShuttingDown: false,
|
|
49
|
+
shutdownTimeout: null,
|
|
50
|
+
};
|
|
51
|
+
// implements REQ-008
|
|
52
|
+
export function getPrologProcess() {
|
|
53
|
+
return state.prologProcess;
|
|
54
|
+
}
|
|
55
|
+
// implements REQ-mcp-kb-freshness
|
|
56
|
+
export function getActiveBranchName() {
|
|
57
|
+
return state.activeBranchName;
|
|
58
|
+
}
|
|
59
|
+
// implements REQ-008
|
|
60
|
+
export function getAttachedBranchKbPath() {
|
|
61
|
+
return state.attachedBranchKbPath;
|
|
62
|
+
}
|
|
63
|
+
// implements REQ-008
|
|
64
|
+
export function getIsShuttingDown() {
|
|
65
|
+
return state.isShuttingDown;
|
|
66
|
+
}
|
|
67
|
+
export const inFlightRequests = new Map();
|
|
47
68
|
// implements REQ-mcp-kb-freshness
|
|
48
69
|
// covered_by TEST-mcp-kb-freshness
|
|
49
70
|
export function updateAttachedBranchStamp(stamp) {
|
|
50
|
-
attachedBranchStamp = stamp;
|
|
71
|
+
state.attachedBranchStamp = stamp;
|
|
51
72
|
}
|
|
52
|
-
export let isShuttingDown = false;
|
|
53
|
-
let shutdownTimeout = null;
|
|
54
|
-
export const inFlightRequests = new Map();
|
|
55
73
|
// implements REQ-008
|
|
56
74
|
export function resetSessionStateForTests() {
|
|
57
|
-
prologProcess = null;
|
|
58
|
-
isInitialized = false;
|
|
59
|
-
activeBranchName = "develop";
|
|
60
|
-
ensurePrologTail = Promise.resolve();
|
|
61
|
-
prologResetGeneration = 0;
|
|
62
|
-
attachedBranchKbPath = null;
|
|
63
|
-
attachedBranchStamp = null;
|
|
64
|
-
isShuttingDown = false;
|
|
75
|
+
state.prologProcess = null;
|
|
76
|
+
state.isInitialized = false;
|
|
77
|
+
state.activeBranchName = "develop";
|
|
78
|
+
state.ensurePrologTail = Promise.resolve();
|
|
79
|
+
state.prologResetGeneration = 0;
|
|
80
|
+
state.attachedBranchKbPath = null;
|
|
81
|
+
state.attachedBranchStamp = null;
|
|
82
|
+
state.isShuttingDown = false;
|
|
65
83
|
inFlightRequests.clear();
|
|
66
|
-
if (shutdownTimeout) {
|
|
67
|
-
clearTimeout(shutdownTimeout);
|
|
68
|
-
shutdownTimeout = null;
|
|
84
|
+
if (state.shutdownTimeout) {
|
|
85
|
+
clearTimeout(state.shutdownTimeout);
|
|
86
|
+
state.shutdownTimeout = null;
|
|
69
87
|
}
|
|
70
88
|
}
|
|
71
89
|
export function _setSessionDepsForTests(
|
|
@@ -77,6 +95,10 @@ export function _resetSessionDepsForTests() {
|
|
|
77
95
|
// implements REQ-008
|
|
78
96
|
sessionDeps = { ...defaultSessionDeps };
|
|
79
97
|
}
|
|
98
|
+
// implements REQ-008
|
|
99
|
+
export function _setPrologProcessForTests(process) {
|
|
100
|
+
state.prologProcess = process;
|
|
101
|
+
}
|
|
80
102
|
function debugLog(...args) {
|
|
81
103
|
if (isMcpDebugEnabled()) {
|
|
82
104
|
console.error(...args);
|
|
@@ -108,13 +130,13 @@ export function ensureBranchKbExists(workspaceRoot, branch) {
|
|
|
108
130
|
return true;
|
|
109
131
|
}
|
|
110
132
|
export async function initiateGracefulShutdown(exitCode = 0) {
|
|
111
|
-
if (isShuttingDown) {
|
|
133
|
+
if (state.isShuttingDown) {
|
|
112
134
|
return;
|
|
113
135
|
}
|
|
114
|
-
isShuttingDown = true;
|
|
136
|
+
state.isShuttingDown = true;
|
|
115
137
|
debugLog(`[KIBI-MCP] Initiating graceful shutdown (exit code: ${exitCode})`);
|
|
116
|
-
const currentProlog = prologProcess;
|
|
117
|
-
prologProcess = null;
|
|
138
|
+
const currentProlog = state.prologProcess;
|
|
139
|
+
state.prologProcess = null;
|
|
118
140
|
if (currentProlog?.isRunning()) {
|
|
119
141
|
debugLog("[KIBI-MCP] Cancelling active Prolog work...");
|
|
120
142
|
try {
|
|
@@ -129,7 +151,7 @@ export async function initiateGracefulShutdown(exitCode = 0) {
|
|
|
129
151
|
if (inFlightRequests.size > 0) {
|
|
130
152
|
debugLog(`[KIBI-MCP] Waiting for ${inFlightRequests.size} in-flight requests to complete...`);
|
|
131
153
|
const timeoutPromise = new Promise((_, reject) => {
|
|
132
|
-
shutdownTimeout = setTimeout(() => {
|
|
154
|
+
state.shutdownTimeout = setTimeout(() => {
|
|
133
155
|
reject(new Error("Shutdown timeout"));
|
|
134
156
|
}, 10000); // 10 second timeout
|
|
135
157
|
});
|
|
@@ -144,9 +166,9 @@ export async function initiateGracefulShutdown(exitCode = 0) {
|
|
|
144
166
|
console.error("[KIBI-MCP] Shutdown timeout reached, forcing exit");
|
|
145
167
|
}
|
|
146
168
|
finally {
|
|
147
|
-
if (shutdownTimeout) {
|
|
148
|
-
clearTimeout(shutdownTimeout);
|
|
149
|
-
shutdownTimeout = null;
|
|
169
|
+
if (state.shutdownTimeout) {
|
|
170
|
+
clearTimeout(state.shutdownTimeout);
|
|
171
|
+
state.shutdownTimeout = null;
|
|
150
172
|
}
|
|
151
173
|
}
|
|
152
174
|
}
|
|
@@ -156,12 +178,12 @@ export async function initiateGracefulShutdown(exitCode = 0) {
|
|
|
156
178
|
// implements REQ-008
|
|
157
179
|
export async function resetProlog(reason) {
|
|
158
180
|
debugLog(`[KIBI-MCP] Resetting Prolog worker: ${reason}`);
|
|
159
|
-
prologResetGeneration += 1;
|
|
160
|
-
const current = prologProcess;
|
|
161
|
-
prologProcess = null;
|
|
162
|
-
isInitialized = false;
|
|
163
|
-
attachedBranchKbPath = null;
|
|
164
|
-
attachedBranchStamp = null;
|
|
181
|
+
state.prologResetGeneration += 1;
|
|
182
|
+
const current = state.prologProcess;
|
|
183
|
+
state.prologProcess = null;
|
|
184
|
+
state.isInitialized = false;
|
|
185
|
+
state.attachedBranchKbPath = null;
|
|
186
|
+
state.attachedBranchStamp = null;
|
|
165
187
|
if (current) {
|
|
166
188
|
try {
|
|
167
189
|
await current.terminate();
|
|
@@ -202,14 +224,14 @@ function usesBranchKbPath(kbPath) {
|
|
|
202
224
|
}
|
|
203
225
|
// implements REQ-008
|
|
204
226
|
async function ensurePrologUnsafe() {
|
|
205
|
-
const generationAtStart = prologResetGeneration;
|
|
227
|
+
const generationAtStart = state.prologResetGeneration;
|
|
206
228
|
const workspaceRoot = sessionDeps.resolveWorkspaceRoot();
|
|
207
229
|
const useEngine = sessionDeps.PrologProcess === defaultSessionDeps.PrologProcess;
|
|
208
230
|
const assertGeneration = async () => {
|
|
209
|
-
if (generationAtStart !== prologResetGeneration) {
|
|
210
|
-
const current = prologProcess;
|
|
211
|
-
prologProcess = null;
|
|
212
|
-
isInitialized = false;
|
|
231
|
+
if (generationAtStart !== state.prologResetGeneration) {
|
|
232
|
+
const current = state.prologProcess;
|
|
233
|
+
state.prologProcess = null;
|
|
234
|
+
state.isInitialized = false;
|
|
213
235
|
if (current) {
|
|
214
236
|
await current.terminate().catch((error) => {
|
|
215
237
|
console.error("[KIBI-MCP] Error terminating stale Prolog after reset generation change:", error);
|
|
@@ -240,22 +262,22 @@ async function ensurePrologUnsafe() {
|
|
|
240
262
|
targetBranch = branchResult.kbBranch;
|
|
241
263
|
}
|
|
242
264
|
// Check if we need to switch branches
|
|
243
|
-
if (isInitialized && useEngine && prologProcess?.isRunning()) {
|
|
265
|
+
if (state.isInitialized && useEngine && state.prologProcess?.isRunning()) {
|
|
244
266
|
const kbPath = sessionDeps.resolveKbPath(workspaceRoot, targetBranch);
|
|
245
|
-
if (targetBranch === activeBranchName) {
|
|
246
|
-
attachedBranchKbPath = kbPath;
|
|
247
|
-
attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
248
|
-
return prologProcess;
|
|
267
|
+
if (targetBranch === state.activeBranchName) {
|
|
268
|
+
state.attachedBranchKbPath = kbPath;
|
|
269
|
+
state.attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
270
|
+
return state.prologProcess;
|
|
249
271
|
}
|
|
250
|
-
await prologProcess.terminate();
|
|
251
|
-
prologProcess = null;
|
|
252
|
-
isInitialized = false;
|
|
253
|
-
attachedBranchKbPath = null;
|
|
254
|
-
attachedBranchStamp = null;
|
|
272
|
+
await state.prologProcess.terminate();
|
|
273
|
+
state.prologProcess = null;
|
|
274
|
+
state.isInitialized = false;
|
|
275
|
+
state.attachedBranchKbPath = null;
|
|
276
|
+
state.attachedBranchStamp = null;
|
|
255
277
|
}
|
|
256
|
-
if (isInitialized && prologProcess?.isRunning()) {
|
|
278
|
+
if (state.isInitialized && state.prologProcess?.isRunning()) {
|
|
257
279
|
const kbPath = sessionDeps.resolveKbPath(workspaceRoot, targetBranch);
|
|
258
|
-
if (targetBranch === activeBranchName) {
|
|
280
|
+
if (targetBranch === state.activeBranchName) {
|
|
259
281
|
const currentStamp = await readBranchKbStamp(kbPath);
|
|
260
282
|
if (usesBranchKbPath(kbPath) &&
|
|
261
283
|
(currentStamp.rdfMissing ||
|
|
@@ -263,29 +285,29 @@ async function ensurePrologUnsafe() {
|
|
|
263
285
|
currentStamp.errorMessage !== null)) {
|
|
264
286
|
throw new KbRefreshError(`KB refresh failed: branch KB snapshot is unstable: ${describeBranchKbStamp(currentStamp)}`);
|
|
265
287
|
}
|
|
266
|
-
const shouldRefresh = attachedBranchKbPath === kbPath &&
|
|
267
|
-
attachedBranchStamp !== null &&
|
|
288
|
+
const shouldRefresh = state.attachedBranchKbPath === kbPath &&
|
|
289
|
+
state.attachedBranchStamp !== null &&
|
|
268
290
|
usesBranchKbPath(kbPath) &&
|
|
269
|
-
!sameBranchKbStamp(currentStamp, attachedBranchStamp);
|
|
291
|
+
!sameBranchKbStamp(currentStamp, state.attachedBranchStamp);
|
|
270
292
|
if (shouldRefresh) {
|
|
271
|
-
attachedBranchStamp = await refreshAttachedBranchKbWithRetry(prologProcess, kbPath, currentStamp, assertGeneration);
|
|
293
|
+
state.attachedBranchStamp = await refreshAttachedBranchKbWithRetry(state.prologProcess, kbPath, currentStamp, assertGeneration);
|
|
272
294
|
}
|
|
273
295
|
else {
|
|
274
|
-
attachedBranchKbPath = kbPath;
|
|
275
|
-
attachedBranchStamp = currentStamp;
|
|
296
|
+
state.attachedBranchKbPath = kbPath;
|
|
297
|
+
state.attachedBranchStamp = currentStamp;
|
|
276
298
|
}
|
|
277
|
-
attachedBranchKbPath = kbPath;
|
|
278
|
-
return prologProcess;
|
|
299
|
+
state.attachedBranchKbPath = kbPath;
|
|
300
|
+
return state.prologProcess;
|
|
279
301
|
}
|
|
280
302
|
// Branch changed - need to detach and re-attach
|
|
281
|
-
debugLog(`[KIBI-MCP] Branch changed: ${activeBranchName} -> ${targetBranch}`);
|
|
303
|
+
debugLog(`[KIBI-MCP] Branch changed: ${state.activeBranchName} -> ${targetBranch}`);
|
|
282
304
|
// Persist and detach from old KB
|
|
283
|
-
const saveResult = await prologProcess.query("kb_save");
|
|
305
|
+
const saveResult = await state.prologProcess.query("kb_save");
|
|
284
306
|
await assertGeneration();
|
|
285
307
|
if (!saveResult.success) {
|
|
286
308
|
throw new Error(`Failed to save old KB before detach: ${saveResult.error || "Unknown error"}`);
|
|
287
309
|
}
|
|
288
|
-
const detachResult = await prologProcess.query("kb_detach");
|
|
310
|
+
const detachResult = await state.prologProcess.query("kb_detach");
|
|
289
311
|
await assertGeneration();
|
|
290
312
|
if (!detachResult.success) {
|
|
291
313
|
debugLog(`[KIBI-MCP] Warning: failed to detach from old KB: ${detachResult.error || "Unknown error"}`);
|
|
@@ -294,35 +316,35 @@ async function ensurePrologUnsafe() {
|
|
|
294
316
|
// Ensure new branch KB exists
|
|
295
317
|
const createdEmptyBranch = ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
296
318
|
// Attach to new branch KB
|
|
297
|
-
const attachResult = await prologProcess.query(`kb_attach('${kbPath}')`);
|
|
319
|
+
const attachResult = await state.prologProcess.query(`kb_attach('${kbPath}')`);
|
|
298
320
|
await assertGeneration();
|
|
299
321
|
if (!attachResult.success) {
|
|
300
322
|
throw new Error(`Failed to attach to new branch KB: ${attachResult.error || "Unknown error"}`);
|
|
301
323
|
}
|
|
302
324
|
if (createdEmptyBranch) {
|
|
303
|
-
const initialSaveResult = await prologProcess.query("kb_save");
|
|
325
|
+
const initialSaveResult = await state.prologProcess.query("kb_save");
|
|
304
326
|
await assertGeneration();
|
|
305
327
|
if (!initialSaveResult.success) {
|
|
306
328
|
throw new Error(`Failed to initialize empty branch KB: ${initialSaveResult.error || "Unknown error"}`);
|
|
307
329
|
}
|
|
308
330
|
}
|
|
309
|
-
activeBranchName = targetBranch;
|
|
310
|
-
attachedBranchKbPath = kbPath;
|
|
311
|
-
attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
331
|
+
state.activeBranchName = targetBranch;
|
|
332
|
+
state.attachedBranchKbPath = kbPath;
|
|
333
|
+
state.attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
312
334
|
debugLog(`[KIBI-MCP] Re-attached to branch: ${targetBranch}`);
|
|
313
335
|
debugLog(`[KIBI-MCP] KB path: ${kbPath}`);
|
|
314
|
-
return prologProcess;
|
|
336
|
+
return state.prologProcess;
|
|
315
337
|
}
|
|
316
338
|
// First initialization
|
|
317
339
|
debugLog("[KIBI-MCP] Initializing Prolog process...");
|
|
318
|
-
prologProcess = useEngine
|
|
340
|
+
state.prologProcess = useEngine
|
|
319
341
|
? new EngineClient({
|
|
320
342
|
workspaceRoot,
|
|
321
343
|
branch: targetBranch,
|
|
322
344
|
timeout: 120000,
|
|
323
345
|
})
|
|
324
346
|
: new sessionDeps.PrologProcess({ timeout: 120000 });
|
|
325
|
-
await prologProcess.start();
|
|
347
|
+
await state.prologProcess.start();
|
|
326
348
|
await assertGeneration();
|
|
327
349
|
// Adapters report the package that owns runtime execution. Do not resolve
|
|
328
350
|
// CLI implementation paths from the MCP process; the runtime package is
|
|
@@ -332,34 +354,34 @@ async function ensurePrologUnsafe() {
|
|
|
332
354
|
debugLog("[KIBI-MCP] Branch selection:");
|
|
333
355
|
debugLog(`[KIBI-MCP] KIBI_BRANCH env: ${envBranch || "not set"}`);
|
|
334
356
|
debugLog(`[KIBI-MCP] Resolved branch: ${targetBranch}`);
|
|
335
|
-
activeBranchName = targetBranch;
|
|
357
|
+
state.activeBranchName = targetBranch;
|
|
336
358
|
const createdEmptyBranch = ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
337
359
|
const kbPath = sessionDeps.resolveKbPath(workspaceRoot, targetBranch);
|
|
338
360
|
if (!useEngine) {
|
|
339
|
-
const attachResult = await prologProcess.query(`kb_attach('${kbPath}')`);
|
|
361
|
+
const attachResult = await state.prologProcess.query(`kb_attach('${kbPath}')`);
|
|
340
362
|
await assertGeneration();
|
|
341
363
|
if (!attachResult.success) {
|
|
342
364
|
throw new Error(`Failed to attach KB: ${attachResult.error || "Unknown error"}`);
|
|
343
365
|
}
|
|
344
366
|
if (createdEmptyBranch) {
|
|
345
|
-
const initialSaveResult = await prologProcess.query("kb_save");
|
|
367
|
+
const initialSaveResult = await state.prologProcess.query("kb_save");
|
|
346
368
|
await assertGeneration();
|
|
347
369
|
if (!initialSaveResult.success) {
|
|
348
370
|
throw new Error(`Failed to initialize empty branch KB: ${initialSaveResult.error || "Unknown error"}`);
|
|
349
371
|
}
|
|
350
372
|
}
|
|
351
373
|
}
|
|
352
|
-
attachedBranchKbPath = kbPath;
|
|
353
|
-
attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
354
|
-
isInitialized = true;
|
|
355
|
-
debugLog(`[KIBI-MCP] Prolog process started (PID: ${prologProcess.getPid()})`);
|
|
374
|
+
state.attachedBranchKbPath = kbPath;
|
|
375
|
+
state.attachedBranchStamp = await readBranchKbStamp(kbPath);
|
|
376
|
+
state.isInitialized = true;
|
|
377
|
+
debugLog(`[KIBI-MCP] Prolog process started (PID: ${state.prologProcess.getPid()})`);
|
|
356
378
|
debugLog(`[KIBI-MCP] KB attached: ${kbPath}`);
|
|
357
|
-
return prologProcess;
|
|
379
|
+
return state.prologProcess;
|
|
358
380
|
}
|
|
359
381
|
export async function ensureProlog() {
|
|
360
|
-
const previous = ensurePrologTail;
|
|
382
|
+
const previous = state.ensurePrologTail;
|
|
361
383
|
let release;
|
|
362
|
-
ensurePrologTail = new Promise((resolve) => {
|
|
384
|
+
state.ensurePrologTail = new Promise((resolve) => {
|
|
363
385
|
release = resolve;
|
|
364
386
|
});
|
|
365
387
|
await previous;
|
|
@@ -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) => {
|
|
@@ -96,7 +97,16 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
96
97
|
});
|
|
97
98
|
register({
|
|
98
99
|
name: "kb_check",
|
|
99
|
-
execute: async (context, args) =>
|
|
100
|
+
execute: async (context, args) => {
|
|
101
|
+
const { async: asyncMode, ...checkArgs } = args;
|
|
102
|
+
if (asyncMode !== true) {
|
|
103
|
+
return runtime.handleKbCheck(prologFor(context), args);
|
|
104
|
+
}
|
|
105
|
+
// Long-KB full checks can exceed the tool timeout. Detach into a job
|
|
106
|
+
// and return the kibi.job.v1 receipt immediately; the agent polls
|
|
107
|
+
// kb_job_status for the terminal state and full result.
|
|
108
|
+
return startJob("kb_check", () => runtime.handleKbCheck(prologFor(context), checkArgs));
|
|
109
|
+
},
|
|
100
110
|
});
|
|
101
111
|
register({
|
|
102
112
|
name: "kb_model_requirement",
|
|
@@ -123,9 +133,9 @@ export function registerConfiguredTools(server, runtime, registerTool) {
|
|
|
123
133
|
: executeApplyPlan(args, context),
|
|
124
134
|
});
|
|
125
135
|
register({
|
|
126
|
-
name: "
|
|
127
|
-
execute: async (context, args) => runtime.
|
|
128
|
-
? runtime.
|
|
129
|
-
:
|
|
136
|
+
name: "kb_ingest_proof",
|
|
137
|
+
execute: async (context, args) => runtime.handleKbIngestProof
|
|
138
|
+
? runtime.handleKbIngestProof(args, context)
|
|
139
|
+
: executeIngestProof(args, context),
|
|
130
140
|
});
|
|
131
141
|
}
|
|
@@ -91,8 +91,8 @@ function adaptProlog(prolog) {
|
|
|
91
91
|
}
|
|
92
92
|
const operationRuntime = createMcpRuntime({
|
|
93
93
|
workspaceRoot: resolveWorkspaceRoot(),
|
|
94
|
-
activeBranchName: async () => (await getSessionModule()).
|
|
95
|
-
attachedBranchKbPath: async () => (await getSessionModule()).
|
|
94
|
+
activeBranchName: async () => (await getSessionModule()).getActiveBranchName(),
|
|
95
|
+
attachedBranchKbPath: async () => (await getSessionModule()).getAttachedBranchKbPath(),
|
|
96
96
|
ensureProlog: async () => (await getSessionModule()).ensureProlog(),
|
|
97
97
|
adaptProlog,
|
|
98
98
|
fs: nodeFilesystem,
|
|
@@ -100,7 +100,7 @@ const operationRuntime = createMcpRuntime({
|
|
|
100
100
|
net: { fetch: (input, init) => globalThis.fetch(input, init) },
|
|
101
101
|
refreshAttachedBranchStamp: async () => {
|
|
102
102
|
const session = await getSessionModule();
|
|
103
|
-
const kbPath = session.
|
|
103
|
+
const kbPath = session.getAttachedBranchKbPath();
|
|
104
104
|
if (kbPath) {
|
|
105
105
|
try {
|
|
106
106
|
session.updateAttachedBranchStamp(await readBranchKbStamp(kbPath));
|
|
@@ -124,12 +124,12 @@ export const DEFAULT_TOOLS_RUNTIME = {
|
|
|
124
124
|
// runtime interface with looser Record<string, unknown> inputSchema. The cast is safe
|
|
125
125
|
// because the tool definitions are statically authored and validated at startup.
|
|
126
126
|
tools: TOOLS,
|
|
127
|
-
activeBranchName: async () => (await getSessionModule()).
|
|
127
|
+
activeBranchName: async () => (await getSessionModule()).getActiveBranchName(),
|
|
128
128
|
ensureProlog: async () => (await getSessionModule()).ensureProlog(),
|
|
129
129
|
resetProlog: async (reason) => (await getSessionModule()).resetProlog(reason),
|
|
130
130
|
inFlightRequests: async () => (await getSessionModule()).inFlightRequests,
|
|
131
|
-
isShuttingDown: async () => (await getSessionModule()).
|
|
132
|
-
prologProcess: async () => (await getSessionModule()).
|
|
131
|
+
isShuttingDown: async () => (await getSessionModule()).getIsShuttingDown(),
|
|
132
|
+
prologProcess: async () => (await getSessionModule()).getPrologProcess(),
|
|
133
133
|
operationRuntime,
|
|
134
134
|
handleKbCheck,
|
|
135
135
|
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";
|
|
@@ -263,4 +264,60 @@ export function registerAllTools(server,
|
|
|
263
264
|
// INTENTIONAL: same generic bridge cast as addTool — see comment there.
|
|
264
265
|
runtime = DEFAULT_TOOLS_RUNTIME) {
|
|
265
266
|
registerConfiguredTools(server, runtime, addTool);
|
|
267
|
+
registerJobStatusTool(server);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* kb_job_status polls background jobs started by long-running operations
|
|
271
|
+
* (currently `kb_check` with `async: true`). This is an MCP-server-native
|
|
272
|
+
* companion to the job receipts, not part of the canonical CLI operation
|
|
273
|
+
* catalog: jobs live in the server process and have no CLI counterpart.
|
|
274
|
+
*/
|
|
275
|
+
function registerJobStatusTool(server) {
|
|
276
|
+
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).", {
|
|
277
|
+
type: "object",
|
|
278
|
+
properties: {
|
|
279
|
+
jobId: {
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "The jobId from the kibi.job.v1 receipt.",
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
required: ["jobId"],
|
|
285
|
+
}, async (args) => {
|
|
286
|
+
const jobId = typeof args.jobId === "string" ? args.jobId : "";
|
|
287
|
+
const job = jobId === "" ? undefined : getJob(jobId);
|
|
288
|
+
if (job === undefined) {
|
|
289
|
+
return {
|
|
290
|
+
content: [
|
|
291
|
+
{
|
|
292
|
+
type: "text",
|
|
293
|
+
text: JSON.stringify({
|
|
294
|
+
kibiProtocol: 1,
|
|
295
|
+
jobVersion: "kibi.job.v1",
|
|
296
|
+
jobId,
|
|
297
|
+
status: "unknown",
|
|
298
|
+
error: "No such job. Jobs are process-local and dropped on server restart; check the jobId or re-run the operation with async:true.",
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
structuredContent: {
|
|
303
|
+
kibiProtocol: 1,
|
|
304
|
+
jobVersion: "kibi.job.v1",
|
|
305
|
+
jobId,
|
|
306
|
+
status: "unknown",
|
|
307
|
+
error: "No such job. Jobs are process-local and dropped on server restart; check the jobId or re-run the operation with async:true.",
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const snapshot = jobSnapshot(job);
|
|
312
|
+
return {
|
|
313
|
+
content: [{ type: "text", text: JSON.stringify(snapshot) }],
|
|
314
|
+
structuredContent: snapshot,
|
|
315
|
+
};
|
|
316
|
+
}, undefined, undefined, undefined, {
|
|
317
|
+
title: "Poll a Kibi background job",
|
|
318
|
+
readOnlyHint: true,
|
|
319
|
+
destructiveHint: false,
|
|
320
|
+
idempotentHint: true,
|
|
321
|
+
openWorldHint: false,
|
|
322
|
+
});
|
|
266
323
|
}
|
|
@@ -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) {
|
|
@@ -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}')`);
|
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,
|
package/dist/workspace.js
CHANGED
|
@@ -68,19 +68,20 @@ function readFirstEnv(keys) {
|
|
|
68
68
|
}
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
|
+
export function nextAncestorDirectory(current) {
|
|
72
|
+
const parent = path.dirname(current);
|
|
73
|
+
return parent === current ? undefined : parent;
|
|
74
|
+
}
|
|
71
75
|
function findUpwards(startDir, marker) {
|
|
72
76
|
let current = path.resolve(startDir);
|
|
73
|
-
while (
|
|
77
|
+
while (current !== undefined) {
|
|
74
78
|
const candidate = path.join(current, marker);
|
|
75
79
|
if (fs.existsSync(candidate)) {
|
|
76
80
|
return current;
|
|
77
81
|
}
|
|
78
|
-
|
|
79
|
-
if (parent === current) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
current = parent;
|
|
82
|
+
current = nextAncestorDirectory(current);
|
|
83
83
|
}
|
|
84
|
+
return null;
|
|
84
85
|
}
|
|
85
86
|
function isBranchPath(p) {
|
|
86
87
|
const parent = path.basename(path.dirname(p));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
6
6
|
"ajv": "^8.18.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"fast-glob": "^3.2.12",
|
|
10
10
|
"gray-matter": "^4.0.3",
|
|
11
11
|
"js-yaml": "^4.1.0",
|
|
12
|
-
"kibi-runtime": "^
|
|
13
|
-
"kibi-core": "^0.
|
|
12
|
+
"kibi-runtime": "^2.0.0",
|
|
13
|
+
"kibi-core": "^0.12.0",
|
|
14
14
|
"mcpcat": "^0.1.12",
|
|
15
15
|
"ts-morph": "^23.0.0",
|
|
16
16
|
"zod": "^4.3.6"
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"test": "bun test",
|
|
26
26
|
"dev": "bun run bin/kibi-mcp",
|
|
27
|
-
"
|
|
27
|
+
"clean": "rm -rf dist",
|
|
28
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
28
29
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
29
30
|
"prepack": "npm run build && node ../../scripts/verify-package-contract.mjs kibi-mcp"
|
|
30
31
|
},
|