kibi-mcp 0.22.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.
Files changed (47) hide show
  1. package/bin/kibi-mcp +12 -11
  2. package/dist/diagnostics.js +68 -8
  3. package/dist/env.js +5 -1
  4. package/dist/runtime/mcp-runtime.js +39 -3
  5. package/dist/server/docs.js +31 -42
  6. package/dist/server/icons.js +27 -0
  7. package/dist/server/jobs.js +107 -0
  8. package/dist/server/json-schema-to-zod.js +33 -9
  9. package/dist/server/session.js +129 -133
  10. package/dist/server/tool-registration.js +46 -9
  11. package/dist/server/tools-runtime.js +10 -10
  12. package/dist/server/tools.js +143 -7
  13. package/dist/server.js +16 -1
  14. package/dist/startup-resolution.js +15 -17
  15. package/dist/tools/check-impact.js +1 -1
  16. package/dist/tools/check.js +1 -1
  17. package/dist/tools/core-module.js +19 -8
  18. package/dist/tools/coverage.js +1 -1
  19. package/dist/tools/delete.js +1 -1
  20. package/dist/tools/discovery-adapter.js +5 -0
  21. package/dist/tools/entity-query.js +1 -1
  22. package/dist/tools/find-gaps.js +9 -1
  23. package/dist/tools/graph.js +9 -1
  24. package/dist/tools/model-requirement.js +2 -2
  25. package/dist/tools/mutation-context.js +15 -9
  26. package/dist/tools/plan-bootstrap.js +21 -0
  27. package/dist/tools/prolog-list.js +1 -1
  28. package/dist/tools/query.js +1 -1
  29. package/dist/tools/relationship-validation.js +1 -1
  30. package/dist/tools/search.js +1 -1
  31. package/dist/tools/semantic-advisor.js +1 -1
  32. package/dist/tools/skills.js +2 -2
  33. package/dist/tools/sparql.js +1 -1
  34. package/dist/tools/status.js +1 -1
  35. package/dist/tools/suggest-predicates.js +1 -1
  36. package/dist/tools/symbols.js +310 -68
  37. package/dist/tools/upsert.js +3 -3
  38. package/dist/tools/validate-upsert.js +1 -1
  39. package/dist/tools-config.js +77 -3
  40. package/dist/workspace.js +10 -8
  41. package/package.json +15 -9
  42. package/dist/tools/autopilot-candidates.js +0 -554
  43. package/dist/tools/autopilot-discovery.js +0 -898
  44. package/dist/tools/autopilot-generate.js +0 -47
  45. package/dist/tools/autopilot-test-compat.js +0 -88
  46. package/dist/tools/autopilot-test-parsers.js +0 -120
  47. package/dist/tools/check-config.js +0 -67
@@ -16,9 +16,11 @@
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
18
  import process from "node:process";
19
- import { executeOperation, } from "kibi-cli/operations/runtime-types";
19
+ import { executeOperation } from "kibi-runtime";
20
+ import { operationData, toKibiResult } from "kibi-runtime";
20
21
  import { isMcpDebugEnabled } from "../env.js";
21
22
  import { appendDiagnosticErrorUsage, appendDiagnosticSuccessUsage, } from "./diagnostic-usage.js";
23
+ import { getJob, jobSnapshot } from "./jobs.js";
22
24
  import { jsonSchemaToZod } from "./json-schema-to-zod.js";
23
25
  import { registerConfiguredTools } from "./tool-registration.js";
24
26
  import { DEFAULT_TOOLS_RUNTIME, _resetSessionModulePromise, _setToolsServerDepsForTests, } from "./tools-runtime.js";
@@ -26,6 +28,10 @@ export { jsonSchemaToZod } from "./json-schema-to-zod.js";
26
28
  export { _resetSessionModulePromise, _setToolsServerDepsForTests };
27
29
  const DEFAULT_TOOL_TIMEOUT_MS = 90_000;
28
30
  const TOOL_TIMEOUT_ENV = "KIBI_MCP_TOOL_TIMEOUT_MS";
31
+ // Give cancellation and the Prolog reset a short bounded grace period, but do
32
+ // not hold the MCP request open for the full five-second shutdown budget when
33
+ // a handler never observes AbortSignal.
34
+ const TOOL_TIMEOUT_GRACE_MS = 100;
29
35
  // implements REQ-008
30
36
  function debugLog(...args) {
31
37
  if (isMcpDebugEnabled()) {
@@ -45,7 +51,13 @@ function getToolTimeoutMs() {
45
51
  }
46
52
  // implements REQ-002
47
53
  function createToolTimeoutError(toolName, timeoutMs) {
48
- return new Error(`Tool ${toolName} timed out after ${timeoutMs}ms`);
54
+ const error = new Error(`Tool ${toolName} timed out after ${timeoutMs}ms`);
55
+ Object.assign(error, { code: "KIBI_TOOL_TIMEOUT", toolName, timeoutMs });
56
+ return error;
57
+ }
58
+ function isToolTimeoutError(value) {
59
+ return (value instanceof Error &&
60
+ value.code === "KIBI_TOOL_TIMEOUT");
49
61
  }
50
62
  // implements REQ-002
51
63
  async function withToolTimeout(toolName, operation, onTimeout) {
@@ -57,8 +69,19 @@ async function withToolTimeout(toolName, operation, onTimeout) {
57
69
  new Promise((_, reject) => {
58
70
  timeout = setTimeout(() => {
59
71
  const error = createToolTimeoutError(toolName, timeoutMs);
60
- reject(error);
61
- void onTimeout(error, timeoutMs);
72
+ // Do not report the timeout until cancellation/reset has completed
73
+ // and the original request has reached a terminal state. This is
74
+ // especially important for source-first mutations: a caller must
75
+ // never retry while the authoritative journal is still deciding its
76
+ // outcome.
77
+ void onTimeout(error, timeoutMs)
78
+ .then(async () => {
79
+ await Promise.race([
80
+ operation.then(() => undefined, () => undefined),
81
+ new Promise((resolve) => setTimeout(resolve, TOOL_TIMEOUT_GRACE_MS)),
82
+ ]);
83
+ })
84
+ .finally(() => reject(error));
62
85
  }, timeoutMs);
63
86
  }),
64
87
  ]);
@@ -74,7 +97,7 @@ export function addTool(server, name, description, inputSchema, handler,
74
97
  // INTENTIONAL: DEFAULT_TOOLS_RUNTIME is typed as ToolsRuntime<PrologProcess>; the
75
98
  // generic TProlog parameter exists so tests can inject a mock type. The cast is safe
76
99
  // because the runtime object satisfies the full ToolsRuntime contract at runtime.
77
- runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations) {
100
+ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations, outputSchema) {
78
101
  const wrappedHandler = async (args) => {
79
102
  const startedAt = new Date();
80
103
  const diagnosticModeEnabled = runtime.diagnosticModeEnabled();
@@ -127,6 +150,8 @@ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations) {
127
150
  error instanceof Error ? error.message : String(error);
128
151
  }
129
152
  });
153
+ const data = operationData(result);
154
+ const envelope = toKibiResult(operationSpec, data);
130
155
  // Log usage in diagnostic mode
131
156
  if (diagnosticModeEnabled) {
132
157
  await appendDiagnosticSuccessUsage({
@@ -137,10 +162,22 @@ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations) {
137
162
  businessArgs,
138
163
  telemetry,
139
164
  startedAt,
140
- result,
165
+ result: envelope,
141
166
  });
142
167
  }
143
- return result;
168
+ if (result !== null &&
169
+ typeof result === "object" &&
170
+ !Array.isArray(result) &&
171
+ "content" in result) {
172
+ return {
173
+ ...result,
174
+ structuredContent: envelope,
175
+ };
176
+ }
177
+ return {
178
+ content: [{ type: "text", text: JSON.stringify(envelope) }],
179
+ structuredContent: envelope,
180
+ };
144
181
  }
145
182
  catch (error) {
146
183
  // Log error in diagnostic mode
@@ -157,6 +194,48 @@ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations) {
157
194
  resetState: { resetAttempted, resetSucceeded, resetError },
158
195
  });
159
196
  }
197
+ if (isToolTimeoutError(error) &&
198
+ operationSpec.effects.some((effect) => effect === "kb-write" || effect === "workspace-write")) {
199
+ const recoveryActions = [
200
+ {
201
+ operation: "kb_status",
202
+ reason: "Determine whether the mutation crossed the authoritative source/journal commit boundary before taking any further action.",
203
+ required: true,
204
+ },
205
+ {
206
+ operation: "kb_apply_plan",
207
+ reason: "If status exposes a recovery journal, replay that typed journal action; never retry the timed-out mutation request.",
208
+ required: true,
209
+ },
210
+ ];
211
+ const timeoutData = {
212
+ effectFailures: [
213
+ {
214
+ kind: "mutation",
215
+ errorCode: "MUTATION_OUTCOME_UNKNOWN",
216
+ detail: {
217
+ requestId,
218
+ timeoutMs: error.timeoutMs,
219
+ },
220
+ },
221
+ ],
222
+ nextActions: recoveryActions,
223
+ };
224
+ const envelope = toKibiResult(operationSpec, timeoutData, {
225
+ status: "error",
226
+ nextActions: recoveryActions,
227
+ error: {
228
+ code: "MUTATION_OUTCOME_UNKNOWN",
229
+ message: "The mutation timed out after cancellation; its commit state is indeterminate. Inspect status and replay the typed recovery action if required.",
230
+ retryable: false,
231
+ details: { requestId, tool: name },
232
+ },
233
+ });
234
+ return {
235
+ content: [{ type: "text", text: JSON.stringify(envelope) }],
236
+ structuredContent: envelope,
237
+ };
238
+ }
160
239
  throw error;
161
240
  }
162
241
  finally {
@@ -176,6 +255,7 @@ runtime = DEFAULT_TOOLS_RUNTIME, spec, annotations) {
176
255
  server.registerTool(name, {
177
256
  description,
178
257
  inputSchema: jsonSchemaToZod(inputSchema),
258
+ ...(outputSchema ? { outputSchema: jsonSchemaToZod(outputSchema) } : {}),
179
259
  ...(annotations ? { annotations } : {}),
180
260
  }, wrappedHandler);
181
261
  }
@@ -184,4 +264,60 @@ export function registerAllTools(server,
184
264
  // INTENTIONAL: same generic bridge cast as addTool — see comment there.
185
265
  runtime = DEFAULT_TOOLS_RUNTIME) {
186
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
+ });
187
323
  }
package/dist/server.js CHANGED
@@ -21,18 +21,33 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
21
21
  import { initializeDiagnosticMode } from "./diagnostics.js";
22
22
  import { loadDefaultEnvFile } from "./env.js";
23
23
  import { setupDocsAndPrompts } from "./server/docs.js";
24
+ import { KIBI_ICONS } from "./server/icons.js";
24
25
  import { registerAllTools } from "./server/tools.js";
25
26
  import { connectTransport, setupTransportHandlers, } from "./server/transport.js";
26
27
  // Read version from package.json to prevent drift
27
28
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
28
29
  const VERSION = packageJson.version ?? "0.1.0";
30
+ const SERVER_INSTRUCTIONS = [
31
+ "Kibi is a branch-local requirements knowledge base for software traceability.",
32
+ "Start with kb_search for discovery, then kb_query for exact lookups before any mutation.",
33
+ "Model requirements as linked facts via kb_model_requirement or kb_upsert (create endpoints before linking them).",
34
+ "Mutate sequentially with kb_upsert and kb_delete; validate with kb_check during iteration and run an unfiltered kb_check before completion.",
35
+ "Confirm branch freshness with kb_status. For first-time repository setup, use the kibi-bootstrap prompt.",
36
+ ].join(" ");
29
37
  export async function startServer() {
30
38
  // Load environment configuration
31
39
  loadDefaultEnvFile();
32
40
  // Initialize diagnostic mode if --diagnostic-mode flag is present
33
41
  initializeDiagnosticMode();
34
42
  // Create MCP server
35
- const server = new McpServer({ name: "kibi-mcp", version: VERSION });
43
+ const server = new McpServer({
44
+ name: "kibi-mcp",
45
+ title: "Kibi MCP",
46
+ description: "Agent-native requirements compiler: branch-local knowledge base for requirements, scenarios, tests, facts, and code-symbol traceability.",
47
+ websiteUrl: "https://github.com/Looted/kibi",
48
+ version: VERSION,
49
+ icons: KIBI_ICONS,
50
+ }, { instructions: SERVER_INSTRUCTIONS });
36
51
  // Setup documentation resources and prompts
37
52
  setupDocsAndPrompts(server);
38
53
  // Register all KB tools
@@ -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 { existsSync, readFileSync, realpathSync } from "node:fs";
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 (true) {
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
- const parent = path.dirname(current);
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 (true) {
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
- const parent = path.dirname(current);
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) {
@@ -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 { analyzeChangedFileImpact, } from "kibi-cli/public/impact-diagnostics";
18
+ import { analyzeChangedFileImpact, } from "kibi-runtime";
19
19
  export function hasImpactOptions(args) {
20
20
  return Boolean(args.includeImpactDiagnostics ||
21
21
  args.staged ||
@@ -1,4 +1,4 @@
1
- import { checkSpec } from "kibi-cli/operations";
1
+ import { checkSpec } from "kibi-runtime";
2
2
  import { resolveWorkspaceRoot } from "../workspace.js";
3
3
  /**
4
4
  * Handle kb_check tool calls - run validation rules on the KB
@@ -1,7 +1,22 @@
1
1
  import path from "node:path";
2
- import { PrologProcess, resolveKbPlPath } from "kibi-cli/prolog";
3
- import { escapeAtomContent } from "kibi-cli/prolog/codec";
2
+ import { PrologProcess, resolveKbPlPath } from "kibi-runtime";
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
+ }
12
+ function isPrologProcess(value) {
13
+ return (value instanceof PrologProcess ||
14
+ (value !== null &&
15
+ typeof value === "object" &&
16
+ typeof value.query === "function" &&
17
+ typeof value.invalidateCache ===
18
+ "function"));
19
+ }
5
20
  // implements REQ-002, REQ-013
6
21
  export function resolveCorePlPath(fileName) {
7
22
  const override = getCoreModulePathOverride(fileName);
@@ -20,7 +35,7 @@ export function resolveCorePlPath(fileName) {
20
35
  // implements REQ-002, REQ-013
21
36
  export async function runJsonModuleQuery(prolog, fileName, goal, errorLabel) {
22
37
  const modulePath = escapeAtomContent(resolveCorePlPath(fileName).replace(/\\/g, "/"));
23
- if (!(prolog instanceof PrologProcess)) {
38
+ if (!isPrologProcess(prolog)) {
24
39
  const mockedResult = await prolog.query(`(use_module('${modulePath}'), ${goal})`);
25
40
  if (!mockedResult.success) {
26
41
  throw new Error(`${errorLabel} query failed: ${mockedResult.error || "Unknown error"}`);
@@ -52,11 +67,7 @@ export async function runJsonModuleQuery(prolog, fileName, goal, errorLabel) {
52
67
  if (!rawJson) {
53
68
  throw new Error(`${errorLabel} query returned no JsonString binding`);
54
69
  }
55
- let parsed = JSON.parse(rawJson);
56
- if (typeof parsed === "string") {
57
- parsed = JSON.parse(parsed);
58
- }
59
- return parsed;
70
+ return parseMaybeDoubleEncodedJson(rawJson);
60
71
  }
61
72
  async function runInteractiveModuleQuery(prolog, modulePath, goal, errorLabel) {
62
73
  const loadResult = await prolog.query(`use_module('${modulePath}')`);
@@ -1,4 +1,4 @@
1
- import { executeCoverage, } from "kibi-cli/operations";
1
+ import { executeCoverage, } from "kibi-runtime";
2
2
  import { createDiscoveryContext } from "./discovery-adapter.js";
3
3
  // implements REQ-002, REQ-013
4
4
  export async function handleKbCoverage(prolog, args, context) {
@@ -1,4 +1,4 @@
1
- import { deleteSpec } from "kibi-cli/operations";
1
+ import { deleteSpec } from "kibi-runtime";
2
2
  import { createMutationContext } from "./mutation-context.js";
3
3
  // implements REQ-kibi-operation-interface-parity
4
4
  export async function handleKbDelete(prolog, args) {
@@ -2,11 +2,16 @@ import { resolveWorkspaceRoot } from "../workspace.js";
2
2
  export function createDiscoveryContext(prolog, baseContext) {
3
3
  // implements REQ-kibi-operation-interface-parity
4
4
  let lastResult = null;
5
+ const mode = prolog
6
+ .useOneShotMode;
5
7
  const port = {
6
8
  query: async (goal) => {
7
9
  lastResult = await prolog.query(goal);
8
10
  return lastResult;
9
11
  },
12
+ oneShotMode: mode === undefined
13
+ ? typeof globalThis.Bun !== "undefined"
14
+ : Boolean(mode),
10
15
  nextSolution: async () => {
11
16
  const result = lastResult;
12
17
  lastResult = null;
@@ -1 +1 @@
1
- export { VALID_ENTITY_TYPES, buildEntityGoal, dedupeEntities, loadEntities, paginateResults, validateEntityType, } from "kibi-cli/operations";
1
+ export { VALID_ENTITY_TYPES, buildEntityGoal, dedupeEntities, loadEntities, paginateResults, validateEntityType, } from "kibi-runtime";
@@ -1,4 +1,11 @@
1
- import { executeFindGaps } from "kibi-cli/operations";
1
+ import { executeFindGaps } from "kibi-runtime";
2
+ function oneShotMode(prolog) {
3
+ const mode = prolog
4
+ .useOneShotMode;
5
+ return mode === undefined
6
+ ? typeof globalThis.Bun !== "undefined"
7
+ : Boolean(mode);
8
+ }
2
9
  // implements REQ-002, REQ-013
3
10
  export async function handleKbFindGaps(prolog, args) {
4
11
  return executeFindGaps({ ...args }, {
@@ -7,6 +14,7 @@ export async function handleKbFindGaps(prolog, args) {
7
14
  clock: () => new Date(),
8
15
  prolog: {
9
16
  query: (goal) => prolog.query(goal),
17
+ oneShotMode: oneShotMode(prolog),
10
18
  nextSolution: async () => null,
11
19
  save: () => prolog.query("kb_save"),
12
20
  },
@@ -1,4 +1,11 @@
1
- import { executeGraph } from "kibi-cli/operations";
1
+ import { executeGraph } from "kibi-runtime";
2
+ function oneShotMode(prolog) {
3
+ const mode = prolog
4
+ .useOneShotMode;
5
+ return mode === undefined
6
+ ? typeof globalThis.Bun !== "undefined"
7
+ : Boolean(mode);
8
+ }
2
9
  // implements REQ-002, REQ-013
3
10
  export async function handleKbGraph(prolog, args) {
4
11
  return executeGraph({ ...args }, {
@@ -7,6 +14,7 @@ export async function handleKbGraph(prolog, args) {
7
14
  clock: () => new Date(),
8
15
  prolog: {
9
16
  query: (goal) => prolog.query(goal),
17
+ oneShotMode: oneShotMode(prolog),
10
18
  nextSolution: async () => null,
11
19
  save: () => prolog.query("kb_save"),
12
20
  },
@@ -1,5 +1,5 @@
1
- import { modelRequirementSpec, } from "kibi-cli/operations";
2
- import { estimateNormativeSignalConfidence, extractRequirementClaim, getWorkspaceMigrationWarning, strictWriteSetToApplyPlan, writeSetPrimaryEntityId, } from "kibi-cli/operations/modeling/model-requirement";
1
+ import { modelRequirementSpec } from "kibi-runtime";
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
5
  export async function handleKbModelRequirement(_prolog, args) {
@@ -1,13 +1,5 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import { resolveWorkspaceRoot } from "../workspace.js";
3
- const filesystem = {
4
- readFile: (filePath) => fs.readFile(filePath, "utf8"),
5
- writeFile: (filePath, data) => fs.writeFile(filePath, data, "utf8"),
6
- mkdir: async (directoryPath) => {
7
- await fs.mkdir(directoryPath, { recursive: true });
8
- },
9
- stat: (filePath) => fs.stat(filePath),
10
- };
11
3
  // implements REQ-kibi-operation-interface-parity
12
4
  export function createMutationContext(prolog) {
13
5
  const port = prolog === undefined
@@ -22,7 +14,21 @@ export function createMutationContext(prolog) {
22
14
  workspaceRoot: resolveWorkspaceRoot(),
23
15
  signal: new AbortController().signal,
24
16
  clock: () => new Date(),
25
- fs: filesystem,
17
+ // The thin direct handler remains a compiled-KB compatibility surface.
18
+ // Production MCP registration supplies sourceFirst through the shared
19
+ // operation runtime while this adapter still exposes filesystem reads for
20
+ // symbol-granularity validation.
21
+ sourceFirst: false,
22
+ ...(process.env.KIBI_WORKSPACE !== undefined ? { fs: filesystem } : {}),
26
23
  ...(port === undefined ? {} : { prolog: port }),
27
24
  };
28
25
  }
26
+ const filesystem = {
27
+ readFile: (filePath) => fs.readFile(filePath, "utf8"),
28
+ writeFile: (filePath, data) => fs.writeFile(filePath, data, "utf8"),
29
+ mkdir: async (directoryPath) => {
30
+ await fs.mkdir(directoryPath, { recursive: true });
31
+ },
32
+ stat: (filePath) => fs.stat(filePath),
33
+ unlink: (filePath) => fs.unlink(filePath),
34
+ };
@@ -0,0 +1,21 @@
1
+ import { executePlanBootstrap, } from "kibi-runtime";
2
+ import { nodeFilesystem, nodeGit } from "kibi-runtime";
3
+ import { resolveWorkspaceRoot } from "../workspace.js";
4
+ function isOperationContext(value) {
5
+ return (value !== null &&
6
+ typeof value === "object" &&
7
+ "workspaceRoot" in value &&
8
+ "signal" in value);
9
+ }
10
+ // implements REQ-mcp-kibi-bootstrap-bootstrap-v1, REQ-kibi-operation-interface-parity
11
+ // implements REQ-KIBI-BOOTSTRAP-PLAN
12
+ export async function handleKbPlanBootstrap(input, context) {
13
+ if (!isOperationContext(context))
14
+ throw new TypeError("Invalid bootstrap adapter invocation");
15
+ return executePlanBootstrap(input, {
16
+ ...context,
17
+ workspaceRoot: context.workspaceRoot || resolveWorkspaceRoot(),
18
+ fs: context.fs ?? nodeFilesystem,
19
+ git: context.git ?? nodeGit,
20
+ });
21
+ }
@@ -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 { splitTopLevel } from "kibi-cli/prolog/codec";
18
+ import { splitTopLevel } from "kibi-runtime";
19
19
  // implements REQ-002
20
20
  export function parseAtomList(raw) {
21
21
  const trimmed = raw.trim();
@@ -1,4 +1,4 @@
1
- import { VALID_ENTITY_TYPES, querySpec } from "kibi-cli/operations";
1
+ import { VALID_ENTITY_TYPES, querySpec } from "kibi-runtime";
2
2
  import { createDiscoveryContext } from "./discovery-adapter.js";
3
3
  export { VALID_ENTITY_TYPES };
4
4
  export async function handleKbQuery(prolog, args) {
@@ -1,4 +1,4 @@
1
- import { formatInvalidRelationshipError, formatInvalidRelationshipTuple, formatRelationshipSourceMismatch, validateLiveRelationshipTargets as validateSharedTargets, } from "kibi-cli/operations/mutation/relationships";
1
+ import { formatInvalidRelationshipError, formatInvalidRelationshipTuple, formatRelationshipSourceMismatch, validateLiveRelationshipTargets as validateSharedTargets, } from "kibi-runtime";
2
2
  export { formatInvalidRelationshipError, formatInvalidRelationshipTuple, formatRelationshipSourceMismatch, };
3
3
  export async function validateLiveRelationshipTargets(prolog, entity, relationships) {
4
4
  const port = {
@@ -1,4 +1,4 @@
1
- import { searchSpec } from "kibi-cli/operations";
1
+ import { searchSpec } from "kibi-runtime";
2
2
  import { createDiscoveryContext } from "./discovery-adapter.js";
3
3
  export async function handleKbSearch(prolog, args) {
4
4
  // implements REQ-kibi-operation-interface-parity, REQ-mcp-search-discovery
@@ -1,4 +1,4 @@
1
- import { executeSemanticAdvisor } from "kibi-cli/operations";
1
+ import { executeSemanticAdvisor } from "kibi-runtime";
2
2
  export async function handleKbSemanticAdvisor(args) {
3
3
  return executeSemanticAdvisor(args);
4
4
  }
@@ -1,5 +1,5 @@
1
- import { skillsListSpec, skillsLoadSpec, skillsReadSpec, } from "kibi-cli/operations";
2
- import { loadBundledSkill } from "kibi-cli/skills";
1
+ import { skillsListSpec, skillsLoadSpec, skillsReadSpec, } from "kibi-runtime";
2
+ import { loadBundledSkill } from "kibi-runtime";
3
3
  function runtimeContext() {
4
4
  return {
5
5
  workspaceRoot: process.cwd(),
@@ -1,4 +1,4 @@
1
- import { sparqlRemoteSpec } from "kibi-cli/operations";
1
+ import { sparqlRemoteSpec } from "kibi-runtime";
2
2
  // implements REQ-002, REQ-013, REQ-kibi-operation-interface-parity
3
3
  export async function handleSparql(prologOrArgs, legacyArgs) {
4
4
  const args = legacyArgs ?? ("endpoint" in prologOrArgs ? prologOrArgs : undefined);
@@ -1,4 +1,4 @@
1
- import { statusSpec } from "kibi-cli/operations";
1
+ import { statusSpec } from "kibi-runtime";
2
2
  import { createDiscoveryContext } from "./discovery-adapter.js";
3
3
  export async function handleKbStatus(prolog, args, context) {
4
4
  // implements REQ-kibi-operation-interface-parity
@@ -1,4 +1,4 @@
1
- import { suggestPredicatesSpec, } from "kibi-cli/operations";
1
+ import { suggestPredicatesSpec } from "kibi-runtime";
2
2
  import { resolveWorkspaceRoot } from "../workspace.js";
3
3
  import { createDiscoveryContext } from "./discovery-adapter.js";
4
4
  export async function handleKbSuggestPredicates(prolog, args) {