kibi-mcp 0.22.0 → 1.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 (45) hide show
  1. package/bin/kibi-mcp +12 -11
  2. package/dist/diagnostics.js +61 -1
  3. package/dist/env.js +5 -1
  4. package/dist/runtime/mcp-runtime.js +36 -3
  5. package/dist/server/docs.js +31 -42
  6. package/dist/server/icons.js +27 -0
  7. package/dist/server/json-schema-to-zod.js +18 -1
  8. package/dist/server/session.js +25 -51
  9. package/dist/server/tool-registration.js +35 -8
  10. package/dist/server/tools-runtime.js +4 -4
  11. package/dist/server/tools.js +86 -7
  12. package/dist/server.js +16 -1
  13. package/dist/tools/check-impact.js +1 -1
  14. package/dist/tools/check.js +1 -1
  15. package/dist/tools/core-module.js +11 -3
  16. package/dist/tools/coverage.js +1 -1
  17. package/dist/tools/delete.js +1 -1
  18. package/dist/tools/discovery-adapter.js +5 -0
  19. package/dist/tools/entity-query.js +1 -1
  20. package/dist/tools/find-gaps.js +9 -1
  21. package/dist/tools/graph.js +9 -1
  22. package/dist/tools/model-requirement.js +2 -2
  23. package/dist/tools/mutation-context.js +15 -9
  24. package/dist/tools/plan-bootstrap.js +21 -0
  25. package/dist/tools/prolog-list.js +1 -1
  26. package/dist/tools/query.js +1 -1
  27. package/dist/tools/relationship-validation.js +1 -1
  28. package/dist/tools/search.js +1 -1
  29. package/dist/tools/semantic-advisor.js +1 -1
  30. package/dist/tools/skills.js +2 -2
  31. package/dist/tools/sparql.js +1 -1
  32. package/dist/tools/status.js +1 -1
  33. package/dist/tools/suggest-predicates.js +1 -1
  34. package/dist/tools/symbols.js +310 -68
  35. package/dist/tools/upsert.js +3 -3
  36. package/dist/tools/validate-upsert.js +1 -1
  37. package/dist/tools-config.js +77 -3
  38. package/dist/workspace.js +3 -2
  39. package/package.json +13 -8
  40. package/dist/tools/autopilot-candidates.js +0 -554
  41. package/dist/tools/autopilot-discovery.js +0 -898
  42. package/dist/tools/autopilot-generate.js +0 -47
  43. package/dist/tools/autopilot-test-compat.js +0 -88
  44. package/dist/tools/autopilot-test-parsers.js +0 -120
  45. package/dist/tools/check-config.js +0 -67
package/bin/kibi-mcp CHANGED
@@ -19,11 +19,11 @@
19
19
  const args = globalThis.Bun?.argv?.slice(2) ?? process.argv.slice(2);
20
20
 
21
21
  async function importStartupResolution() {
22
- try {
23
- return await import("../dist/startup-resolution.js");
24
- } catch {
25
- return await import("../src/startup-resolution.ts");
26
- }
22
+ // Published packages contain the compiled runtime only. Do not replace a
23
+ // real dependency/import failure with a source-tree fallback: doing so
24
+ // hides package contract errors and makes startup depend on an unpackaged
25
+ // TypeScript tree.
26
+ return await import("../dist/startup-resolution.js");
27
27
  }
28
28
 
29
29
  async function serverEntrypointUrl() {
@@ -32,7 +32,10 @@ async function serverEntrypointUrl() {
32
32
  if (existsSync(distUrl)) {
33
33
  return distUrl.href;
34
34
  }
35
- return new URL("../src/server.ts", import.meta.url).href;
35
+ throw new Error(
36
+ `[KIBI-MCP] Missing compiled server entrypoint: ${distUrl.pathname}. ` +
37
+ "Run the package build before launching kibi-mcp.",
38
+ );
36
39
  }
37
40
 
38
41
  async function buildResolution() {
@@ -64,11 +67,9 @@ async function importServer(entrypoint) {
64
67
  const { pathToFileURL } = await import("node:url");
65
68
  return await import(pathToFileURL(entrypoint).href);
66
69
  }
67
- try {
68
- return await import("../dist/server.js");
69
- } catch {
70
- return await import("../src/server.ts");
71
- }
70
+ // Preserve the original import error. In particular, an incompatible
71
+ // kibi-cli export must not be replaced with a misleading source fallback.
72
+ return await import("../dist/server.js");
72
73
  }
73
74
 
74
75
  function printHelp() {
@@ -144,8 +144,46 @@ const RECEIPT_GAP_CODES = new Set([
144
144
  "stale_verification_receipt",
145
145
  "failed_verification_receipt",
146
146
  "invalid_verification_receipt",
147
+ "verification_contract_mismatch",
147
148
  "verification_snapshot_unavailable",
148
149
  ]);
150
+ const MAX_COVERAGE_RECEIPT_GAPS = 50;
151
+ function coverageReceiptGapDetails(rows) {
152
+ const details = rows
153
+ .flatMap((row) => {
154
+ const codes = [...new Set(stringArray(row.proofGaps))]
155
+ .filter((code) => RECEIPT_GAP_CODES.has(code))
156
+ .sort();
157
+ if (codes.length === 0)
158
+ return [];
159
+ const stages = isRecord(row.proofStages) ? row.proofStages : undefined;
160
+ const passingE2e = isRecord(stages?.passingE2e)
161
+ ? stages.passingE2e
162
+ : undefined;
163
+ const testIds = [
164
+ "missingReceiptTests",
165
+ "staleReceiptTests",
166
+ "failedReceiptTests",
167
+ "invalidReceiptTests",
168
+ "contractMismatchReceiptTests",
169
+ "snapshotUnavailableTests",
170
+ ].flatMap((key) => stringArray(passingE2e?.[key]));
171
+ const requirementId = typeof row.id === "string" ? row.id : "unknown";
172
+ return [
173
+ {
174
+ requirementId,
175
+ testIds: [...new Set(testIds)].sort(),
176
+ codes,
177
+ },
178
+ ];
179
+ })
180
+ .sort((left, right) => left.requirementId.localeCompare(right.requirementId));
181
+ return {
182
+ details: details.slice(0, MAX_COVERAGE_RECEIPT_GAPS),
183
+ total: details.length,
184
+ truncated: details.length > MAX_COVERAGE_RECEIPT_GAPS,
185
+ };
186
+ }
149
187
  function appendCoverageFields(fields, args, structuredContent) {
150
188
  const rows = Array.isArray(structuredContent.rows)
151
189
  ? structuredContent.rows.filter(isRecord)
@@ -166,6 +204,10 @@ function appendCoverageFields(fields, args, structuredContent) {
166
204
  fields.coverage_proof_gap_count = gapCodes.length;
167
205
  fields.coverage_gap_codes = uniqueGapCodes;
168
206
  fields.coverage_receipt_gap_count = gapCodes.filter((gap) => RECEIPT_GAP_CODES.has(gap)).length;
207
+ const receiptGaps = coverageReceiptGapDetails(rows);
208
+ fields.coverage_receipt_gaps = receiptGaps.details;
209
+ fields.coverage_receipt_gap_total = receiptGaps.total;
210
+ fields.coverage_receipt_gaps_truncated = receiptGaps.truncated;
169
211
  if (typeof scope?.complete === "boolean") {
170
212
  fields.coverage_scope_complete = scope.complete;
171
213
  }
@@ -234,7 +276,25 @@ export function deriveDiagnosticFields(toolName, args, telemetry, result) {
234
276
  fields.actor_id = telemetry.actor_id;
235
277
  }
236
278
  }
237
- const structuredContent = structuredContentFrom(result);
279
+ const envelope = structuredContentFrom(result);
280
+ const structuredContent = envelope?.kibiProtocol === 1 && isRecord(envelope.data)
281
+ ? envelope.data
282
+ : envelope;
283
+ if (envelope?.kibiProtocol === 1) {
284
+ fields.protocol_version = envelope.kibiProtocol;
285
+ fields.result_version = envelope.resultVersion ?? null;
286
+ fields.result_status = envelope.status ?? null;
287
+ fields.effect_failures = Array.isArray(envelope.effects)
288
+ ? envelope.effects.filter((effect) => effect !== null &&
289
+ typeof effect === "object" &&
290
+ !Array.isArray(effect) &&
291
+ effect.status === "failed")
292
+ : [];
293
+ fields.followed_next_actions = Array.isArray(telemetry?.followed_next_actions)
294
+ ? telemetry.followed_next_actions
295
+ : [];
296
+ fields.unsafe_original_retry = telemetry?.unsafe_original_retry === true;
297
+ }
238
298
  if (toolName === "kb_query" || toolName === "kb_search") {
239
299
  const resultCount = Number(structuredContent?.count ?? 0);
240
300
  fields.result_count = resultCount;
package/dist/env.js CHANGED
@@ -36,7 +36,11 @@ export function isMcpDebugEnabled() {
36
36
  }
37
37
  export function getBranchOverride() {
38
38
  // implements REQ-002
39
- return getTrimmedEnvValue("KIBI_BRANCH");
39
+ // Branch names are identities, not display labels. Preserve the value
40
+ // exactly so the Git-ref validator can reject whitespace or other invalid
41
+ // input instead of silently attaching a different branch.
42
+ const value = getEnvValue("KIBI_BRANCH");
43
+ return value === undefined || value.length === 0 ? undefined : value;
40
44
  }
41
45
  export function getKbPlPathOverride() {
42
46
  // implements REQ-002
@@ -1,3 +1,4 @@
1
+ import { resolveBranchAttachment } from "kibi-runtime";
1
2
  // implements REQ-kibi-operation-interface-parity
2
3
  export function createMcpRuntime(session) {
3
4
  const sessionPrologs = new WeakMap();
@@ -11,18 +12,50 @@ export function createMcpRuntime(session) {
11
12
  signal: options.signal ?? session.signal ?? new AbortController().signal,
12
13
  clock: options.clock ?? session.clock ?? (() => new Date()),
13
14
  ...(fs ? { fs } : {}),
15
+ ...(fs ? { sourceFirst: true } : {}),
14
16
  ...(git ? { git } : {}),
15
17
  ...(net ? { net } : {}),
16
18
  };
19
+ const attachment = resolveBranchAttachment(context.workspaceRoot);
20
+ if (!("error" in attachment) && attachment.migrationRequired) {
21
+ console.warn(`[KIBI-MCP] Legacy branch attachment: Git '${attachment.gitBranch}' is reading KB '${attachment.kbBranch}'. Run 'kibi branch migrate --from ${attachment.kbBranch} --to ${attachment.gitBranch} --apply'; writes are blocked until then.`);
22
+ }
23
+ const withAttachment = "error" in attachment
24
+ ? context
25
+ : { ...context, branchAttachment: attachment };
17
26
  if (!spec.requiresProlog) {
18
- return context;
27
+ // Preserve explicit host/test injection without asking the shared MCP
28
+ // session to initialise a store merely to report its condition.
29
+ const providedProlog = options.prolog;
30
+ let lazyProlog;
31
+ const lazyContext = {};
32
+ const ensureProlog = async () => {
33
+ if (providedProlog !== undefined)
34
+ return providedProlog;
35
+ if (lazyProlog !== undefined)
36
+ return session.adaptProlog(lazyProlog);
37
+ lazyProlog = await session.ensureProlog();
38
+ if (lazyContext.current !== undefined)
39
+ sessionPrologs.set(lazyContext.current, lazyProlog);
40
+ return session.adaptProlog(lazyProlog);
41
+ };
42
+ const operationContext = {
43
+ ...withAttachment,
44
+ ...(options.prolog ? { prolog: options.prolog } : {}),
45
+ ensureProlog,
46
+ };
47
+ lazyContext.current = operationContext;
48
+ return operationContext;
49
+ }
50
+ if (!("error" in attachment) && attachment.migrationRequired) {
51
+ throw new Error(`Legacy branch attachment requires explicit migration before '${spec.name}'; run kibi branch migrate --from ${attachment.kbBranch} --to ${attachment.gitBranch} --apply`);
19
52
  }
20
53
  if (options.prolog) {
21
- return { ...context, prolog: options.prolog };
54
+ return { ...withAttachment, prolog: options.prolog };
22
55
  }
23
56
  const sessionProlog = await session.ensureProlog();
24
57
  const operationContext = {
25
- ...context,
58
+ ...withAttachment,
26
59
  prolog: session.adaptProlog(sessionProlog),
27
60
  };
28
61
  sessionPrologs.set(operationContext, sessionProlog);
@@ -16,6 +16,7 @@
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
18
  import { TOOLS } from "../tools-config.js";
19
+ import { KIBI_ICONS } from "./icons.js";
19
20
  const ACTIVE_TOOLS = TOOLS;
20
21
  function renderToolsDoc() {
21
22
  const lines = [
@@ -41,58 +42,32 @@ function renderToolsDoc() {
41
42
  export const PROMPTS = [
42
43
  // implements REQ-002, REQ-013, REQ-mcp-search-discovery
43
44
  {
44
- name: "init-kibi",
45
- description: "Activation workflow to populate a new or empty Kibi KB from an existing repository.",
45
+ name: "kibi-bootstrap",
46
+ title: "Bootstrap Kibi knowledge",
47
+ description: "Bootstrap Kibi knowledge for an existing repository through the canonical planner and apply operation.",
46
48
  text: [
47
- "# Kibi Interactive Activation Workflow",
49
+ "# Kibi Bootstrap",
48
50
  "",
49
- "Use this post-hoc workflow to onboard a new or empty repository into Kibi through interactive discovery.",
51
+ "Route an explicit bootstrap request to the canonical `kibi-bootstrap` skill and planner.",
50
52
  "",
51
- "## Step 1: Gather Declared Context",
53
+ "## Route",
52
54
  "",
53
- "The agent must ask at most 4 bounded questions to gather declared intent from the user:",
54
- "1. **Project Summary**: What is the core purpose of this project?",
55
- "2. **Source of Truth**: Where is the primary documentation (canonical requirements, ADRs)?",
56
- "3. **Priority Root**: In a monorepo, which package should be prioritized?",
57
- "4. **Verification Anchors**: Where are the primary tests or verification configs located?",
55
+ "Inspect `kb_status.bootstrap` and follow its typed `nextAction`. If infrastructure is missing, run `kibi init` before planning.",
58
56
  "",
59
- "## Step 2: Synthesize Candidates (read-only)",
57
+ "## Preview and Approval",
60
58
  "",
61
- "Call `kb_autopilot_generate` with the gathered context to synthesize candidate entities.",
59
+ "Call `kb_plan_bootstrap` read-only. Ask only the bounded questions returned when its status is `needs_context`; do not invent a questionnaire.",
62
60
  "",
63
- "This tool is **read-only**. It returns additive `structuredContent` with:",
64
- "- `promptBlock`: review text that must be surfaced before writes",
65
- "- `recommendedActions`: agent-facing next steps, including any REQ/SCEN/TEST authoring routed for manual handling",
66
- "- `declaredContext`: the user-provided bootstrap context",
67
- "- `confidence`: confidence summary for the generated output",
68
- "- `bootstrapMode`: current KB state (e.g., `root_uninitialized`)",
69
- "- `candidates`: synthesized entities grounded in declared context and source evidence",
70
- "- `applyPlan`: exact sequential `kb_upsert` payloads for approved candidates",
71
- "- `discoverySummary`: source-backed discovery notes",
61
+ "Show the exact returned `structuredContent.plan`, its actions, and canonical hash. Get explicit user approval, then pass that plan object unchanged to `kb_apply_plan`.",
72
62
  "",
73
- "## Step 3: Preview and Approval",
63
+ "## Verify and repair",
74
64
  "",
75
- "Surface the `promptBlock`, a summary of `candidates`, and the exact `structuredContent.applyPlan` payloads. Wait for explicit user approval before proceeding to writes.",
76
- "",
77
- "## Step 4: Apply Candidates",
78
- "",
79
- "Apply candidates sequentially using `kb_upsert`.",
80
- "1. Execute `structuredContent.applyPlan` sequentially in listed order.",
81
- "2. Confirm success of each `kb_upsert` before moving to the next.",
82
- "3. Run `kb_check` after the batch to verify KB integrity.",
83
- "",
84
- "## Rules",
85
- "- Never apply bootstrap writes without user-facing preview and explicit approval.",
86
- "- `kb_autopilot_generate` is strictly read-only; synthesis is the backend, not the actor.",
87
- "- MCP tools and the trusted project-local CLI are peer surfaces; choose by what is visible and approved here.",
88
- "- If Kibi MCP tools are visible and approved, use MCP.",
89
- "- Otherwise, in a trusted workspace, use the project-local CLI's dedicated JSON routes with `--input <file|->`.",
90
- "- If neither interface is available, Kibi operation is blocked; tell the operator to enable MCP or the trusted project-local CLI. Do not infer MCP availability from config file existence.",
91
- "- Do not read or edit `.kb/` files directly. Query before mutate. Run `kb_upsert` sequentially. Run `kb_check` before completion.",
65
+ "Inspect the typed `kb_apply_plan` result, follow any `nextActions` for recovery, and finish with `kb_check` and `kb_status`. Never read or edit `.kb` directly or reconstruct a plan.",
92
66
  ].join("\n"),
93
67
  },
94
68
  {
95
69
  name: "kibi_overview",
70
+ title: "Kibi usage overview",
96
71
  description: "High-level model for using kibi-mcp safely and effectively.",
97
72
  text: [
98
73
  "# kibi-mcp Overview",
@@ -113,7 +88,7 @@ export const PROMPTS = [
113
88
  "Core modeling principles:",
114
89
  "- Kibi has eight entity types: common authoring (req, scenario, test, fact) and supporting/system (adr, flag, event, symbol).",
115
90
  "- Encode requirements as linked facts: `req --constrains--> fact` plus `req --requires_property--> fact`.",
116
- "- High-confidence `kb_model_requirement` output is deterministic; `/init-kibi` bootstrap writes still require preview and explicit approval.",
91
+ "- High-confidence `kb_model_requirement` output is deterministic; `/kibi-bootstrap` bootstrap writes still require preview and explicit approval.",
117
92
  "- Low-confidence claims (< 0.7) are downgraded to `observation` facts to prevent false-positive contradictions.",
118
93
  "- Only strict domain facts participate in contradiction inference; observation and meta facts are non-blocking notes.",
119
94
  "- v1 contradictions are limited to exact-value, boolean/enum, numeric range, and polarity conflicts.",
@@ -127,6 +102,7 @@ export const PROMPTS = [
127
102
  },
128
103
  {
129
104
  name: "kibi_workflow",
105
+ title: "Kibi workflow steps",
130
106
  description: "Step-by-step call order for discovery, mutation, and verification.",
131
107
  text: [
132
108
  "# kibi-mcp Workflow",
@@ -149,6 +125,7 @@ export const PROMPTS = [
149
125
  },
150
126
  {
151
127
  name: "kibi_constraints",
128
+ title: "Kibi constraints",
152
129
  description: "Operational limits, validation rules, and mutation gotchas.",
153
130
  text: [
154
131
  "# kibi-mcp Constraints",
@@ -236,6 +213,7 @@ function registerDocResources() {
236
213
  {
237
214
  uri: "kibi://docs/overview",
238
215
  name: "kibi docs overview",
216
+ title: "Kibi server overview",
239
217
  description: "Full server description, purpose, and scope.",
240
218
  mimeType: "text/markdown",
241
219
  text: overview,
@@ -243,6 +221,7 @@ function registerDocResources() {
243
221
  {
244
222
  uri: "kibi://docs/tools",
245
223
  name: "kibi docs tools",
224
+ title: "Kibi tools reference",
246
225
  description: "Available tools with summaries and required parameters.",
247
226
  mimeType: "text/markdown",
248
227
  text: renderToolsDoc(),
@@ -250,6 +229,7 @@ function registerDocResources() {
250
229
  {
251
230
  uri: "kibi://docs/errors",
252
231
  name: "kibi docs errors",
232
+ title: "Kibi error guide",
253
233
  description: "Common error modes and suggested recovery actions.",
254
234
  mimeType: "text/markdown",
255
235
  text: errors,
@@ -257,6 +237,7 @@ function registerDocResources() {
257
237
  {
258
238
  uri: "kibi://docs/examples",
259
239
  name: "kibi docs examples",
240
+ title: "Kibi usage examples",
260
241
  description: "Concrete tool call sequences for common tasks.",
261
242
  mimeType: "text/markdown",
262
243
  text: examples,
@@ -266,7 +247,10 @@ function registerDocResources() {
266
247
  export const DOC_RESOURCES = registerDocResources();
267
248
  export function setupDocsAndPrompts(server) {
268
249
  for (const prompt of PROMPTS) {
269
- server.prompt(prompt.name, prompt.description, async () => ({
250
+ server.registerPrompt(prompt.name, {
251
+ title: prompt.title,
252
+ description: prompt.description,
253
+ }, async () => ({
270
254
  messages: [
271
255
  {
272
256
  role: "user",
@@ -276,7 +260,12 @@ export function setupDocsAndPrompts(server) {
276
260
  }));
277
261
  }
278
262
  for (const resource of DOC_RESOURCES) {
279
- server.resource(resource.name, resource.uri, { description: resource.description, mimeType: resource.mimeType }, async () => ({
263
+ server.registerResource(resource.name, resource.uri, {
264
+ title: resource.title,
265
+ description: resource.description,
266
+ mimeType: resource.mimeType,
267
+ icons: KIBI_ICONS,
268
+ }, async () => ({
280
269
  contents: [
281
270
  {
282
271
  uri: resource.uri,
@@ -0,0 +1,27 @@
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
+ const KIBI_LOGO_MARK_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 308 309"><rect width="308" height="309" rx="64" fill="#1D1E23"/><ellipse cx="113.3" cy="150.9" rx="51.3" ry="51.5" fill="#A2D3F4"/><path d="M228.1 197.8L227.6 165.5H156.5L95.4 165.3V135.1H156.5L227.5 135.1V101.2H252.2V197.8Z" fill="#A2D3F4"/></svg>`;
19
+ const KIBI_ICON_SRC = `data:image/svg+xml;base64,${Buffer.from(KIBI_LOGO_MARK_SVG, "utf8").toString("base64")}`;
20
+ export const KIBI_ICONS = [
21
+ {
22
+ src: KIBI_ICON_SRC,
23
+ mimeType: "image/svg+xml",
24
+ sizes: ["any"],
25
+ theme: "dark",
26
+ },
27
+ ];
@@ -73,7 +73,19 @@ export function jsonSchemaToZod(schema) {
73
73
  const union = z.union(literalSchemas);
74
74
  return description ? union.describe(description) : union;
75
75
  }
76
- const schemaType = typeof obj.type === "string" ? obj.type : undefined;
76
+ const schemaTypes = Array.isArray(obj.type)
77
+ ? obj.type.filter((value) => typeof value === "string")
78
+ : typeof obj.type === "string"
79
+ ? [obj.type]
80
+ : [];
81
+ // JSON Schema nullable fields are represented as a type union in the
82
+ // catalog. Build a real Zod union so MCP publishes and validates the same
83
+ // nullability instead of silently degrading the field to z.any().
84
+ if (schemaTypes.length > 1) {
85
+ const variants = schemaTypes.map((type) => jsonSchemaToZod({ ...obj, type }));
86
+ return z.union(variants);
87
+ }
88
+ const schemaType = schemaTypes[0];
77
89
  switch (schemaType) {
78
90
  case "object": {
79
91
  const properties = obj.properties && typeof obj.properties === "object"
@@ -178,6 +190,11 @@ export function jsonSchemaToZod(schema) {
178
190
  const description = typeof obj.description === "string" ? obj.description : undefined;
179
191
  return description ? b.describe(description) : b;
180
192
  }
193
+ case "null": {
194
+ const n = z.null();
195
+ const description = typeof obj.description === "string" ? obj.description : undefined;
196
+ return description ? n.describe(description) : n;
197
+ }
181
198
  default: {
182
199
  const anySchema = z.any();
183
200
  const description = typeof obj.description === "string" ? obj.description : undefined;
@@ -18,9 +18,9 @@
18
18
  import fs from "node:fs";
19
19
  import { createRequire } from "node:module";
20
20
  import process from "node:process";
21
- import { EngineClient } from "kibi-cli/engine";
22
- import { PrologProcess } from "kibi-cli/prolog";
23
- import { copyCleanSnapshot, getBranchDiagnostic, isValidBranchName, resolveActiveBranch, } from "kibi-cli/public/branch-resolver";
21
+ import { EngineClient } from "kibi-runtime";
22
+ import { PrologProcess } from "kibi-runtime";
23
+ import { copyCleanSnapshot, ensureBranchStoreManifest, getBranchDiagnostic, isValidBranchName, resolveActiveBranch, resolveBranchAttachment, } from "kibi-runtime";
24
24
  import { getBranchOverride, isMcpDebugEnabled } from "../env.js";
25
25
  import { resolveKbPath, resolveWorkspaceRoot } from "../workspace.js";
26
26
  import { KbRefreshError, describeBranchKbStamp, readBranchKbStamp, sameBranchKbStamp, } from "./kb-freshness.js";
@@ -32,6 +32,7 @@ const defaultSessionDeps = {
32
32
  getBranchDiagnostic,
33
33
  isValidBranchName,
34
34
  resolveActiveBranch,
35
+ resolveBranchAttachment,
35
36
  resolveKbPath,
36
37
  resolveWorkspaceRoot,
37
38
  };
@@ -90,19 +91,19 @@ export function ensureBranchKbExists(workspaceRoot, branch) {
90
91
  if (sessionDeps.fs.existsSync(branchPath)) {
91
92
  return false;
92
93
  }
93
- // Try to copy from the previously active branch if available
94
- const previousBranch = activeBranchName;
95
- const previousBranchPath = sessionDeps.resolveKbPath(workspaceRoot, previousBranch);
96
- if (previousBranch !== branch &&
97
- previousBranch !== "develop" &&
98
- sessionDeps.fs.existsSync(previousBranchPath)) {
99
- // Copy from previous branch for continuity
100
- sessionDeps.copyCleanSnapshot(previousBranchPath, branchPath);
101
- debugLog(`[KIBI-MCP] Created branch KB for '${branch}' from '${previousBranch}'`);
102
- return false;
94
+ // Branch initialization is intentionally empty. The compiled store is
95
+ // materialized from the current checkout's authored sources by sync; it is
96
+ // never copied from another branch.
97
+ // Unit/integration hosts can provide a virtual filesystem through the
98
+ // existing session dependency seam. Only the real Node filesystem needs
99
+ // the identity manifest writer; virtual hosts already model the resolved
100
+ // branch path and should not be forced to create that path on the host.
101
+ if (sessionDeps.fs === fs) {
102
+ ensureBranchStoreManifest(workspaceRoot, branch);
103
+ }
104
+ else {
105
+ sessionDeps.fs.mkdirSync(branchPath, { recursive: true });
103
106
  }
104
- // No previous branch available - create empty KB
105
- sessionDeps.fs.mkdirSync(branchPath, { recursive: true });
106
107
  debugLog(`[KIBI-MCP] Created empty branch KB for '${branch}'`);
107
108
  return true;
108
109
  }
@@ -228,14 +229,15 @@ async function ensurePrologUnsafe() {
228
229
  targetBranch = envBranch;
229
230
  }
230
231
  else {
231
- // No override - resolve active branch from git (may change between requests)
232
- const branchResult = sessionDeps.resolveActiveBranch(workspaceRoot);
232
+ // No override - resolve the exact Git branch, with read-compatible legacy
233
+ // literal-path storage detection for bridge-release repositories.
234
+ const branchResult = sessionDeps.resolveBranchAttachment(workspaceRoot);
233
235
  if ("error" in branchResult) {
234
236
  const diagnostic = sessionDeps.getBranchDiagnostic(undefined, branchResult.error);
235
237
  console.error(`[KIBI-MCP] ${diagnostic}`);
236
238
  throw new Error(`Failed to resolve active branch: ${branchResult.error}`);
237
239
  }
238
- targetBranch = branchResult.branch;
240
+ targetBranch = branchResult.kbBranch;
239
241
  }
240
242
  // Check if we need to switch branches
241
243
  if (isInitialized && useEngine && prologProcess?.isRunning()) {
@@ -322,39 +324,11 @@ async function ensurePrologUnsafe() {
322
324
  : new sessionDeps.PrologProcess({ timeout: 120000 });
323
325
  await prologProcess.start();
324
326
  await assertGeneration();
325
- // Startup debug: resolve which kibi-cli is being used and its version (best-effort).
326
- // Gate all output under KIBI_MCP_DEBUG and write only to stderr via debugLog.
327
- if (isMcpDebugEnabled()) {
328
- try {
329
- const req = sessionDeps.createRequire(import.meta.url);
330
- try {
331
- const resolved = req.resolve("kibi-cli/prolog");
332
- debugLog(`[KIBI-MCP] require.resolve('kibi-cli/prolog') -> ${resolved}`);
333
- }
334
- catch (resolveErr) {
335
- debugLog("[KIBI-MCP] require.resolve('kibi-cli/prolog') failed:", resolveErr.message);
336
- }
337
- // Try to read package.json for kibi-cli to get version. This may fail if
338
- // the package uses exports blocking package.json access — log explicit failure.
339
- try {
340
- // prefer direct package.json require; createRequire makes this ESM-friendly
341
- // eslint-disable-next-line @typescript-eslint/no-var-requires
342
- const pkg = req("kibi-cli/package.json");
343
- if (pkg && typeof pkg.version === "string") {
344
- debugLog(`[KIBI-MCP] kibi-cli version: ${pkg.version}`);
345
- }
346
- else {
347
- debugLog("[KIBI-MCP] kibi-cli package.json read but no version field");
348
- }
349
- }
350
- catch (pkgErr) {
351
- debugLog("[KIBI-MCP] Failed to read kibi-cli package.json (exports may restrict access):", pkgErr.message);
352
- }
353
- }
354
- catch (err) {
355
- debugLog("[KIBI-MCP] Failed to create require() for debug lookup:", err.message);
356
- }
357
- }
327
+ // Adapters report the package that owns runtime execution. Do not resolve
328
+ // CLI implementation paths from the MCP process; the runtime package is
329
+ // the sole first-party execution boundary.
330
+ if (isMcpDebugEnabled())
331
+ debugLog("[KIBI-MCP] Runtime boundary: kibi-runtime");
358
332
  debugLog("[KIBI-MCP] Branch selection:");
359
333
  debugLog(`[KIBI-MCP] KIBI_BRANCH env: ${envBranch || "not set"}`);
360
334
  debugLog(`[KIBI-MCP] Resolved branch: ${targetBranch}`);
@@ -1,4 +1,5 @@
1
- import { getSpec } from "kibi-cli/operations";
1
+ import { getSpec, statusSpec } from "kibi-runtime";
2
+ import { executeApplyPlan, executeCompileIntent, executeDelete, executeIngestVerification, executeUpsert, validateUpsertSpec, } from "kibi-runtime";
2
3
  // implements REQ-002, REQ-013
3
4
  export function registerConfiguredTools(server, runtime, registerTool) {
4
5
  const toolDef = (name) => {
@@ -14,6 +15,10 @@ export function registerConfiguredTools(server, runtime, registerTool) {
14
15
  }
15
16
  return prolog;
16
17
  };
18
+ const withSessionProlog = (context) => ({
19
+ ...context,
20
+ prolog: prologFor(context),
21
+ });
17
22
  const register = ({ name, execute }) => {
18
23
  const definition = toolDef(name);
19
24
  const publicSpec = getSpec(name);
@@ -23,7 +28,7 @@ export function registerConfiguredTools(server, runtime, registerTool) {
23
28
  requiresProlog: publicSpec.requiresProlog,
24
29
  execute: (args, context) => execute(context, args),
25
30
  };
26
- registerTool(server, name, definition.description, definition.inputSchema, async () => undefined, runtime, spec, definition.annotations);
31
+ registerTool(server, name, definition.description, definition.inputSchema, async () => undefined, runtime, spec, definition.annotations, definition.outputSchema);
27
32
  };
28
33
  // INTENTIONAL ARGUMENT CASTS: The `args as (unknown as)? XyzArgs` casts below
29
34
  // bridge the generic ToolHandler (which receives Record<string, unknown>) to the
@@ -39,7 +44,11 @@ export function registerConfiguredTools(server, runtime, registerTool) {
39
44
  });
40
45
  register({
41
46
  name: "kb_status",
42
- execute: async (context, args) => runtime.handleKbStatus(prologFor(context), args, context),
47
+ execute: async (context, args) =>
48
+ // Status owns its non-mutating fallback. Do not require the session engine
49
+ // here: an absent or unreadable branch store is exactly the condition it
50
+ // must be able to describe.
51
+ statusSpec.execute(args, context),
43
52
  });
44
53
  register({
45
54
  name: "kb_skills_list",
@@ -75,15 +84,15 @@ export function registerConfiguredTools(server, runtime, registerTool) {
75
84
  });
76
85
  register({
77
86
  name: "kb_upsert",
78
- execute: async (context, args) => runtime.handleKbUpsert(prologFor(context), args),
87
+ execute: async (context, args) => executeUpsert(args, withSessionProlog(context)),
79
88
  });
80
89
  register({
81
90
  name: "kb_validate_upsert",
82
- execute: async (context, args) => runtime.handleKbValidateUpsert(prologFor(context), args),
91
+ execute: async (context, args) => validateUpsertSpec.execute(args, withSessionProlog(context)),
83
92
  });
84
93
  register({
85
94
  name: "kb_delete",
86
- execute: async (context, args) => runtime.handleKbDelete(prologFor(context), args),
95
+ execute: async (context, args) => executeDelete(args, withSessionProlog(context)),
87
96
  });
88
97
  register({
89
98
  name: "kb_check",
@@ -98,7 +107,25 @@ export function registerConfiguredTools(server, runtime, registerTool) {
98
107
  execute: async (context, args) => runtime.handleKbSuggestPredicates(prologFor(context), args),
99
108
  });
100
109
  register({
101
- name: "kb_autopilot_generate",
102
- execute: async (context, args) => runtime.handleKbAutopilotGenerate(args, context),
110
+ name: "kb_plan_bootstrap",
111
+ execute: async (context, args) => runtime.handleKbPlanBootstrap(args, context),
112
+ });
113
+ register({
114
+ name: "kb_compile_intent",
115
+ execute: async (context, args) => runtime.handleKbCompileIntent
116
+ ? runtime.handleKbCompileIntent(args, context)
117
+ : executeCompileIntent(args, context),
118
+ });
119
+ register({
120
+ name: "kb_apply_plan",
121
+ execute: async (context, args) => runtime.handleKbApplyPlan
122
+ ? runtime.handleKbApplyPlan(args, context)
123
+ : executeApplyPlan(args, context),
124
+ });
125
+ register({
126
+ name: "kb_ingest_verification",
127
+ execute: async (context, args) => runtime.handleKbIngestVerification
128
+ ? runtime.handleKbIngestVerification(args, context)
129
+ : executeIngestVerification(args, context),
103
130
  });
104
131
  }