kibi-mcp 0.14.1 → 0.14.3

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.
@@ -33,6 +33,7 @@ function renderToolsDoc() {
33
33
  const required = Array.isArray(tool.inputSchema?.required)
34
34
  ? tool.inputSchema.required.join(", ")
35
35
  : "none";
36
+ lines.push(`| \`${tool.name}\` | ${tool.description} | ${required || "none"} |`);
36
37
  }
37
38
  lines.push("");
38
39
  lines.push("Modeling note: Kibi has eight core entity types grouped into common authoring (req, scenario, test, fact) and supporting/system (adr, flag, event, symbol).");
@@ -62,7 +63,7 @@ export const PROMPTS = [
62
63
  "Call `kb_autopilot_generate` with the gathered context to synthesize candidate entities.",
63
64
  "",
64
65
  "This tool is **read-only**. It returns additive `structuredContent` with:",
65
- "- `promptBlock`: review text that can be surfaced in optional human-facing briefs",
66
+ "- `promptBlock`: review text that can be surfaced for optional human review",
66
67
  "- `recommendedActions`: agent-facing next steps, including any REQ/SCEN/TEST authoring routed for manual handling",
67
68
  "- `declaredContext`: the user-provided bootstrap context",
68
69
  "- `confidence`: confidence summary for the generated output",
@@ -72,7 +73,7 @@ export const PROMPTS = [
72
73
  "",
73
74
  "## Step 3: Optional Human Review",
74
75
  "",
75
- "Surface the `promptBlock` and a summary of `candidates` when optional human review is useful. Human review is post-hoc/optional via VS Code briefs and must not block writes.",
76
+ "Surface the `promptBlock` and a summary of `candidates` when optional human review is useful. Human review is post-hoc/optional and must not block writes.",
76
77
  "",
77
78
  "## Step 4: Apply Candidates",
78
79
  "",
@@ -82,37 +83,11 @@ export const PROMPTS = [
82
83
  "3. Run `kb_check` after the batch to verify KB integrity.",
83
84
  "",
84
85
  "## Rules",
85
- "- Human review is optional and post-hoc via VS Code briefs; do not gate writes on synchronous sign-off.",
86
+ "- Human review is optional and post-hoc; do not gate writes on synchronous sign-off.",
86
87
  "- `kb_autopilot_generate` is strictly read-only; synthesis is the backend, not the actor.",
87
88
  "- Guidance must stay MCP-only; do not suggest `kibi` CLI commands.",
88
89
  ].join("\n"),
89
90
  },
90
- {
91
- name: "brief-kibi",
92
- description: "Start-task workflow for generating a citation-backed Kibi briefing before risky work.",
93
- text: [
94
- "# Kibi Briefing Workflow",
95
- "",
96
- "Use this workflow at the start of a task when you need a deterministic, citation-backed Kibi briefing.",
97
- "",
98
- "## Step 1: Generate the briefing",
99
- "",
100
- "Call `kb_briefing_generate` with any relevant `taskText`, `sourceFiles`, and `seedIds`.",
101
- "",
102
- "This tool is read-only. It returns `briefingState`, `activationState`, `activationReason`, `freshness`, `confidence`, `tldr`, `promptBlock`, `entities`, `constraints`, `regressionRisks`, `missingEvidence`, and `citations`.",
103
- "",
104
- "## Step 2: Inspect readiness",
105
- "",
106
- "Inspect `briefingState` before acting.",
107
- "- If `briefingState` is `ready`, continue using only cited output from the briefing.",
108
- "- If `briefingState` is `no_briefing`, stop and proceed without inventing briefing claims.",
109
- "",
110
- "## Step 3: Use the cited output",
111
- "",
112
- "Use `constraints`, `regressionRisks`, `missingEvidence`, and `promptBlock` only when their claims are backed by the returned `citations` and cited `entities`.",
113
- "Do not add uncited assertions, and do not treat omitted topics as verified.",
114
- ].join("\n"),
115
- },
116
91
  {
117
92
  name: "kibi_overview",
118
93
  description: "High-level model for using kibi-mcp safely and effectively.",
@@ -2,18 +2,18 @@ import { z } from "zod";
2
2
  import { DIAGNOSTIC_MODE_ENABLED, appendUsageLogLine, deriveDiagnosticFields, extractToolCallPayload, } from "../diagnostics.js";
3
3
  import { isMcpDebugEnabled } from "../env.js";
4
4
  import { TOOLS } from "../tools-config.js";
5
+ import { handleKbAutopilotGenerate, } from "../tools/autopilot-generate.js";
5
6
  import { handleKbCheck } from "../tools/check.js";
6
7
  import { handleKbCoverage } from "../tools/coverage.js";
7
8
  import { handleKbDelete } from "../tools/delete.js";
8
9
  import { handleKbFindGaps } from "../tools/find-gaps.js";
9
10
  import { handleKbGraph } from "../tools/graph.js";
11
+ import { handleKbModelRequirement, } from "../tools/model-requirement.js";
10
12
  import { handleKbQuery } from "../tools/query.js";
11
13
  import { handleKbSearch } from "../tools/search.js";
14
+ import { handleKbSkillsList, handleKbSkillsLoad, handleKbSkillsRead, } from "../tools/skills.js";
12
15
  import { handleKbStatus } from "../tools/status.js";
13
16
  import { handleKbUpsert } from "../tools/upsert.js";
14
- import { handleKbModelRequirement, } from "../tools/model-requirement.js";
15
- import { handleKbAutopilotGenerate, } from "../tools/autopilot-generate.js";
16
- import { handleKbBriefingGenerate, } from "../tools/briefing-generate.js";
17
17
  const defaultToolsServerDeps = {
18
18
  getSessionModule: () => import("./session.js"),
19
19
  };
@@ -59,10 +59,12 @@ const DEFAULT_TOOLS_RUNTIME = {
59
59
  handleKbQuery,
60
60
  handleKbSearch,
61
61
  handleKbStatus,
62
+ handleKbSkillsList,
63
+ handleKbSkillsLoad,
64
+ handleKbSkillsRead,
62
65
  handleKbUpsert,
63
66
  handleKbModelRequirement,
64
67
  handleKbAutopilotGenerate,
65
- handleKbBriefingGenerate,
66
68
  };
67
69
  // implements REQ-008
68
70
  function debugLog(...args) {
@@ -299,6 +301,9 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
299
301
  const prolog = await runtime.ensureProlog();
300
302
  return runtime.handleKbStatus(prolog, args);
301
303
  }, runtime);
304
+ addTool(server, "kb_skills_list", toolDef("kb_skills_list").description, toolDef("kb_skills_list").inputSchema, async (args) => runtime.handleKbSkillsList(args), runtime);
305
+ addTool(server, "kb_skills_load", toolDef("kb_skills_load").description, toolDef("kb_skills_load").inputSchema, async (args) => runtime.handleKbSkillsLoad(args), runtime);
306
+ addTool(server, "kb_skills_read", toolDef("kb_skills_read").description, toolDef("kb_skills_read").inputSchema, async (args) => runtime.handleKbSkillsRead(args), runtime);
302
307
  addTool(server, "kb_find_gaps", toolDef("kb_find_gaps").description, toolDef("kb_find_gaps").inputSchema, async (args) => {
303
308
  const prolog = await runtime.ensureProlog();
304
309
  return runtime.handleKbFindGaps(prolog, args);
@@ -331,8 +336,4 @@ runtime = DEFAULT_TOOLS_RUNTIME) {
331
336
  const prolog = await runtime.ensureProlog();
332
337
  return runtime.handleKbAutopilotGenerate(prolog, args);
333
338
  }, runtime);
334
- addTool(server, "kb_briefing_generate", toolDef("kb_briefing_generate").description, toolDef("kb_briefing_generate").inputSchema, async (args) => {
335
- const prolog = await runtime.ensureProlog();
336
- return runtime.handleKbBriefingGenerate(prolog, args);
337
- }, runtime);
338
339
  }
@@ -3,8 +3,8 @@
3
3
  import { extractFromManifest } from "kibi-cli/extractors/manifest";
4
4
  import { extractFromMarkdown } from "kibi-cli/extractors/markdown";
5
5
  import { buildStrictWriteSet, modelRequirementClaims, } from "kibi-cli/public/check-types";
6
- import path from "node:path";
7
6
  import fs from "node:fs";
7
+ import path from "node:path";
8
8
  import { createRepoIgnorePolicy } from "kibi-cli/ignore-policy";
9
9
  import { estimateNormativeSignalConfidence, extractRequirementClaim, strictWriteSetToApplyPlan, writeSetPrimaryEntityId, } from "./model-requirement.js";
10
10
  function slugify(value, maxLength = 80) {
@@ -223,7 +223,8 @@ export function buildGenericMarkdownCandidates(discoveryResult, existingEntities
223
223
  let type = null;
224
224
  let confidence = 0;
225
225
  // ADR heuristic: headings that mention ADR or Architectural Decision
226
- if (/\badr\b/i.test(heading) || /architectur.*decision/i.test(heading)) {
226
+ if (/\badr\b/i.test(heading) ||
227
+ /architectur.*decision/i.test(heading)) {
227
228
  type = "adr";
228
229
  confidence = 0.9;
229
230
  }
@@ -336,7 +337,8 @@ export function collectSourceOnlyAuthoringSignals(discoveryResult, existingEntit
336
337
  evidence: [`generic_heading:${textRef}`],
337
338
  }, seen);
338
339
  }
339
- if (/\b(tests?|verification)\b/i.test(heading) && 0.82 >= minConfidence) {
340
+ if (/\b(tests?|verification)\b/i.test(heading) &&
341
+ 0.82 >= minConfidence) {
340
342
  pushSignal(signals, {
341
343
  kind: "test",
342
344
  title: `Author tests from ${heading}`,
@@ -354,7 +356,8 @@ export function collectSourceOnlyAuthoringSignals(discoveryResult, existingEntit
354
356
  for (const item of discoveryResult.evidence ?? []) {
355
357
  const confidence = typeof item.data.confidence === "number" ? item.data.confidence : 0;
356
358
  if (item.kind === "test_topology" && confidence >= minConfidence) {
357
- const sourcePath = item.absolutePath ?? path.resolve(workspaceRoot, item.relativePath ?? item.label);
359
+ const sourcePath = item.absolutePath ??
360
+ path.resolve(workspaceRoot, item.relativePath ?? item.label);
358
361
  const relativePath = item.relativePath ?? item.label;
359
362
  pushSignal(signals, {
360
363
  kind: "test",
@@ -504,7 +507,9 @@ export function buildProviderEvidenceCandidates(discoveryResult, existingEntitie
504
507
  const generatedId = `FACT-GEN-${slugify(slugSource, 64) || "evidence"}`.toUpperCase();
505
508
  if (existingEntities.ids.has(generatedId))
506
509
  continue;
507
- const textRef = relativePath.includes("#") ? relativePath : `${relativePath}`;
510
+ const textRef = relativePath.includes("#")
511
+ ? relativePath
512
+ : `${relativePath}`;
508
513
  const evidence = Array.isArray(item.data.evidence)
509
514
  ? item.data.evidence.filter((value) => typeof value === "string")
510
515
  : [];
@@ -7,11 +7,12 @@
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import fg from "fast-glob";
10
- import { createRepoIgnorePolicy } from "kibi-cli/ignore-policy";
11
10
  import * as cliSymbolCoordinator from "kibi-cli/extractors/symbols-coordinator";
11
+ import { createRepoIgnorePolicy } from "kibi-cli/ignore-policy";
12
12
  import { runJsonModuleQuery } from "./core-module.js";
13
13
  // implements REQ-001
14
14
  export const AUTOPILOT_PROVIDER_ORDER = [
15
+ // implements REQ-001
15
16
  "typed_kibi_docs",
16
17
  "generic_repo_docs",
17
18
  "repo_metadata",
@@ -323,7 +324,8 @@ function detectLanguagesFromPackageJson(packageJson) {
323
324
  const bin = packageJson.bin;
324
325
  if (typeof scripts === "object" && scripts) {
325
326
  for (const value of Object.values(scripts)) {
326
- if (typeof value === "string" && /\.(cts|mts|ts|tsx)\b|\b(tsx|ts-node)\b/i.test(value)) {
327
+ if (typeof value === "string" &&
328
+ /\.(cts|mts|ts|tsx)\b|\b(tsx|ts-node)\b/i.test(value)) {
327
329
  detected.add("typescript");
328
330
  }
329
331
  if (typeof value === "string" && /\.(cjs|mjs|js|jsx)\b/i.test(value)) {
@@ -416,11 +418,21 @@ function runRepoMetadataProvider(workspaceRoot) {
416
418
  };
417
419
  }
418
420
  function runRepoLayoutProvider(workspaceRoot, vendoredRoots) {
419
- const layoutRoots = ["src", "app", "apps", "packages", "tests", "test", "docs", "scripts"];
421
+ const layoutRoots = [
422
+ "src",
423
+ "app",
424
+ "apps",
425
+ "packages",
426
+ "tests",
427
+ "test",
428
+ "docs",
429
+ "scripts",
430
+ ];
420
431
  const evidence = [];
421
432
  for (const relativePath of layoutRoots) {
422
433
  const absolutePath = path.join(workspaceRoot, relativePath);
423
- if (!fs.existsSync(absolutePath) || !fs.statSync(absolutePath).isDirectory()) {
434
+ if (!fs.existsSync(absolutePath) ||
435
+ !fs.statSync(absolutePath).isDirectory()) {
424
436
  continue;
425
437
  }
426
438
  evidence.push({
@@ -547,7 +559,8 @@ function runSourceSymbolsProvider(workspaceRoot, vendoredRoots) {
547
559
  const scanWarnings = [];
548
560
  for (const absolutePath of sortUnique(sourceFiles)) {
549
561
  const relativePath = toRelativePosixPath(workspaceRoot, absolutePath);
550
- const language = SOURCE_LANGUAGE_EXTENSIONS[path.extname(absolutePath).toLowerCase()] ?? "unknown";
562
+ const language = SOURCE_LANGUAGE_EXTENSIONS[path.extname(absolutePath).toLowerCase()] ??
563
+ "unknown";
551
564
  detectedLanguages.add(language);
552
565
  try {
553
566
  const content = fs.readFileSync(absolutePath, "utf8");
@@ -558,7 +571,8 @@ function runSourceSymbolsProvider(workspaceRoot, vendoredRoots) {
558
571
  language,
559
572
  providerId: null,
560
573
  module: {
561
- title: path.basename(relativePath, path.extname(relativePath)) || relativePath,
574
+ title: path.basename(relativePath, path.extname(relativePath)) ||
575
+ relativePath,
562
576
  analysisMode: "fallback",
563
577
  fallbackReason: "provider_unavailable",
564
578
  },
@@ -1,11 +1,11 @@
1
1
  import path from "node:path";
2
2
  import fg from "fast-glob";
3
3
  import { createRepoIgnorePolicy } from "kibi-cli/ignore-policy";
4
- import { buildNormativeRequirementCandidates, collectSourceOnlyAuthoringSignals, buildGenericMarkdownCandidates, buildProviderEvidenceCandidates, buildTypedMarkdownCandidates, buildSymbolManifestCandidates, } from "./autopilot-candidates.js";
5
- import { getWorkspaceMigrationWarning } from "./model-requirement.js";
4
+ import { resolveWorkspaceRoot } from "../workspace.js";
5
+ import { buildGenericMarkdownCandidates, buildNormativeRequirementCandidates, buildProviderEvidenceCandidates, buildSymbolManifestCandidates, buildTypedMarkdownCandidates, collectSourceOnlyAuthoringSignals, } from "./autopilot-candidates.js";
6
6
  import { discoverProviderEvidence, resolveActivationPolicy, } from "./autopilot-discovery.js";
7
7
  import { loadEntities } from "./entity-query.js";
8
- import { resolveWorkspaceRoot } from "../workspace.js";
8
+ import { getWorkspaceMigrationWarning } from "./model-requirement.js";
9
9
  function clamp(value, min, max) {
10
10
  return Math.max(min, Math.min(max, value));
11
11
  }
@@ -115,8 +115,9 @@ function buildPromptBlock(workspaceRoot, activationState, activationMode, activa
115
115
  else if (declaredContext.verificationAnchors.length > 0) {
116
116
  bullets.push(`- Verify after kb_check with ${listSummary(declaredContext.verificationAnchors, 2)}.`);
117
117
  }
118
- if (activationMode === "attached_thin_handoff" || activationMode === "attached_seeded_handoff") {
119
- bullets.push("- Handoff: use kb_search, kb_briefing_generate, or kb_find_gaps to work with existing KB.");
118
+ if (activationMode === "attached_thin_handoff" ||
119
+ activationMode === "attached_seeded_handoff") {
120
+ bullets.push("- Handoff: use kb_search, kb_query, or gap/coverage tools to work with existing KB.");
120
121
  }
121
122
  if (scanWarnings.length > 0) {
122
123
  bullets.push(`- Scan diagnostics: ${scanWarnings.length} warning(s) during evidence collection.`);
@@ -159,7 +160,8 @@ function buildRecommendedActions(workspaceRoot, activationMode, activationReason
159
160
  const candidateIds = candidateRecords
160
161
  .map((candidate) => String(candidate.candidateId ?? ""))
161
162
  .filter(Boolean);
162
- const isActiveRepo = activationMode === "attached_thin_handoff" || activationMode === "attached_seeded_handoff";
163
+ const isActiveRepo = activationMode === "attached_thin_handoff" ||
164
+ activationMode === "attached_seeded_handoff";
163
165
  actions.push({
164
166
  order: order++,
165
167
  kind: "query",
@@ -176,7 +178,7 @@ function buildRecommendedActions(workspaceRoot, activationMode, activationReason
176
178
  actions.push({
177
179
  order: order++,
178
180
  kind: "handoff",
179
- description: "Use kb_briefing_generate with task-relevant seed IDs for a citation-backed briefing.",
181
+ description: "Use kb_query or kb_graph with task-relevant IDs to inspect cited KB context.",
180
182
  });
181
183
  actions.push({
182
184
  order: order++,
@@ -190,7 +192,8 @@ function buildRecommendedActions(workspaceRoot, activationMode, activationReason
190
192
  actions.push({
191
193
  order: order++,
192
194
  kind: "handoff",
193
- description: handoffMessage ?? blockedActivationMessage(activationMode, activationReason),
195
+ description: handoffMessage ??
196
+ blockedActivationMessage(activationMode, activationReason),
194
197
  });
195
198
  }
196
199
  else if (candidateIds.length > 0) {
@@ -276,7 +279,11 @@ function buildConfidence(activationMode, applyBlocked, declaredContext, candidat
276
279
  }
277
280
  const rounded = roundScore(score);
278
281
  const level = rounded > 0.7 ? "high" : rounded >= 0.4 ? "medium" : "low";
279
- const policy = level === "high" ? "full_actions" : level === "medium" ? "review_required" : "handoff_only";
282
+ const policy = level === "high"
283
+ ? "full_actions"
284
+ : level === "medium"
285
+ ? "review_required"
286
+ : "handoff_only";
280
287
  if (policy === "review_required") {
281
288
  reasons.push("Medium confidence: review recommended before applying.");
282
289
  }
@@ -295,7 +302,8 @@ function buildTldr(activationMode, applyBlocked, candidateRecords, sourceOnlySig
295
302
  if (candidateRecords.length > 0 || sourceOnlySignals.length > 0) {
296
303
  return `Bootstrap guidance is ready in ${activationMode}: ${candidateRecords.length} safe candidate(s), ${sourceOnlySignals.length} source-only authoring follow-up(s), and apply remains blocked.`;
297
304
  }
298
- return handoffMessage ?? blockedActivationMessage(activationMode, activationReason);
305
+ return (handoffMessage ??
306
+ blockedActivationMessage(activationMode, activationReason));
299
307
  }
300
308
  if (candidateRecords.length > 0 || sourceOnlySignals.length > 0) {
301
309
  return `Bootstrap output is ready with ${candidateRecords.length} safe candidate(s) and ${sourceOnlySignals.length} source-only authoring follow-up(s).`;
@@ -352,7 +360,8 @@ function splitDiscoveredSources(workspaceRoot, candidates) {
352
360
  }
353
361
  return { markdownFiles, manifestFiles };
354
362
  }
355
- export async function handleKbAutopilotGenerate(// implements REQ-mcp-init-kibi-autopilot-v1
363
+ export async function handleKbAutopilotGenerate(
364
+ // implements REQ-mcp-init-kibi-autopilot-v1
356
365
  _prolog, args) {
357
366
  const { includeGenericMarkdown = true, minConfidence = 0.8, maxCandidates = 50, entityTypes, bootstrapContext, } = args;
358
367
  const normalizedMinConfidence = clamp(minConfidence, 0.6, 0.95);
@@ -473,7 +482,9 @@ _prolog, args) {
473
482
  const sourcePath = String(candidate.sourcePath || "");
474
483
  const textRef = extractTextRefFromApplyPlan(candidate.applyPlan);
475
484
  const titleKey = normalizeTitle(entityType, title);
476
- const upsert = Array.isArray(candidate.applyPlan) ? candidate.applyPlan[0] : null;
485
+ const upsert = Array.isArray(candidate.applyPlan)
486
+ ? candidate.applyPlan[0]
487
+ : null;
477
488
  let upsertId = "";
478
489
  if (upsert && typeof upsert === "object") {
479
490
  const upsertRecord = upsert;
@@ -545,7 +556,8 @@ _prolog, args) {
545
556
  const explain = repoIgnore.explain(rel);
546
557
  if (explain.ignored) {
547
558
  // avoid duplicating existing suppressed entries for the same source
548
- if (!suppressed.some((s) => String(s.sourcePath ?? "") === rel && s.reason === "ignored_source")) {
559
+ if (!suppressed.some((s) => String(s.sourcePath ?? "") === rel &&
560
+ s.reason === "ignored_source")) {
549
561
  suppressed.push({
550
562
  candidateId: String("") /* no candidate id for ignored source */,
551
563
  reason: "ignored_source",
@@ -567,7 +579,9 @@ _prolog, args) {
567
579
  const recommendedActions = buildRecommendedActions(workspaceRoot, activation.activationMode, activation.reason, activation.handoffMessage, activation.applyBlocked, declaredContext, candidateRecords, sourceOnlySignals);
568
580
  const tldr = buildTldr(activation.activationMode, activation.applyBlocked, candidateRecords, sourceOnlySignals, activation.reason, activation.handoffMessage);
569
581
  // Apply confidence policy: medium and low confidence force applyBlocked
570
- const effectiveApplyBlocked = activation.applyBlocked || confidence.level === "medium" || confidence.level === "low";
582
+ const effectiveApplyBlocked = activation.applyBlocked ||
583
+ confidence.level === "medium" ||
584
+ confidence.level === "low";
571
585
  const effectiveTldr = confidence.level === "low" && !activation.applyBlocked
572
586
  ? `Low-confidence bootstrap (${confidence.score}): review diagnostics before proceeding. ${tldr}`
573
587
  : tldr;
@@ -1,10 +1,10 @@
1
- import { escapeAtom, parseEntityFromList, parseListOfLists } from "kibi-cli/prolog/codec";
2
- import { writeBriefPendingMarker } from "../utils/brief-marker.js";
1
+ import { escapeAtom, parseEntityFromList, parseListOfLists, } from "kibi-cli/prolog/codec";
3
2
  /**
4
3
  * Handle kb.delete tool calls
5
4
  * Prevents deletion of entities with dependents (referential integrity)
6
5
  */
7
- export async function handleKbDelete(// implements REQ-002, REQ-011
6
+ export async function handleKbDelete(
7
+ // implements REQ-002, REQ-011
8
8
  prolog, args) {
9
9
  const { ids } = args;
10
10
  if (!ids || ids.length === 0) {
@@ -13,8 +13,6 @@ prolog, args) {
13
13
  let deleted = 0;
14
14
  let skipped = 0;
15
15
  const errors = [];
16
- const pendingEntityIds = [];
17
- const pendingRelationships = [];
18
16
  try {
19
17
  for (const id of ids) {
20
18
  const safeId = escapeAtom(id);
@@ -45,7 +43,6 @@ prolog, args) {
45
43
  }
46
44
  // No dependents, safe to delete
47
45
  const entityMetadata = await loadEntityMetadataForDelete(prolog, id, safeId);
48
- const relationships = await loadOutgoingRelationshipsForDelete(prolog, safeId);
49
46
  const deleteGoal = buildDeleteGoal(safeId, entityMetadata);
50
47
  const deleteResult = await prolog.query(deleteGoal);
51
48
  if (!deleteResult.success) {
@@ -54,8 +51,6 @@ prolog, args) {
54
51
  }
55
52
  else {
56
53
  deleted++;
57
- pendingEntityIds.push(id);
58
- pendingRelationships.push(...relationships);
59
54
  }
60
55
  }
61
56
  // Save KB to disk
@@ -63,14 +58,6 @@ prolog, args) {
63
58
  if (!saveResult.success) {
64
59
  throw new Error(`Failed to save KB after delete: ${saveResult.error || "Unknown error"}`);
65
60
  }
66
- if (pendingEntityIds.length > 0 || pendingRelationships.length > 0) {
67
- writeBriefPendingMarker({
68
- ...(args._requestId ? { sessionId: args._requestId } : {}),
69
- operation: "delete",
70
- entityIds: pendingEntityIds,
71
- relationships: pendingRelationships,
72
- });
73
- }
74
61
  prolog.invalidateCache();
75
62
  return {
76
63
  content: [
@@ -96,7 +83,9 @@ async function loadEntityMetadataForDelete(prolog, id, safeId) {
96
83
  if (!result.success) {
97
84
  throw new Error(`Failed to load metadata for entity ${id}: ${result.error || "Unknown error"}`);
98
85
  }
99
- const rows = result.bindings.Results ? parseListOfLists(result.bindings.Results) : [];
86
+ const rows = result.bindings.Results
87
+ ? parseListOfLists(result.bindings.Results)
88
+ : [];
100
89
  if (rows.length === 0) {
101
90
  throw new Error(`Failed to load metadata for entity ${id}: Entity not found`);
102
91
  }
@@ -105,40 +94,11 @@ async function loadEntityMetadataForDelete(prolog, id, safeId) {
105
94
  const { id: _entityId, type: _entityType, ...props } = entity;
106
95
  return { type, props };
107
96
  }
108
- async function loadOutgoingRelationshipsForDelete(prolog, safeId) {
109
- const result = await prolog.query(`findall([Type,'${safeId}',To], (member(Type, [depends_on, verified_by, validates, specified_by, relates_to, guards, publishes, consumes, implements, covered_by, executable_for, constrains, requires_property, supersedes, constrained_by]), kb_relationship(Type, '${safeId}', To)), Relationships)`);
110
- if (!result.success) {
111
- throw new Error(`Failed to load outgoing relationships for entity ${safeId}: ${result.error || "Unknown error"}`);
112
- }
113
- const rows = result.bindings.Relationships
114
- ? parseListOfLists(result.bindings.Relationships)
115
- : [];
116
- return rows.flatMap((row) => {
117
- const type = row[0];
118
- const from = row[1];
119
- const to = row[2];
120
- if (type === undefined || from === undefined || to === undefined) {
121
- return [];
122
- }
123
- return [
124
- {
125
- type: normalizeDeleteRelationshipValue(type),
126
- from: normalizeDeleteRelationshipValue(from),
127
- to: normalizeDeleteRelationshipValue(to),
128
- },
129
- ];
130
- });
131
- }
132
- function normalizeDeleteRelationshipValue(value) {
133
- const normalized = String(value);
134
- if ((normalized.startsWith("'") && normalized.endsWith("'")) ||
135
- (normalized.startsWith('"') && normalized.endsWith('"'))) {
136
- return normalized.slice(1, -1);
137
- }
138
- return normalized;
139
- }
140
97
  function buildDeleteGoal(safeId, metadata) {
141
- const auditProps = [`id='${safeId}'`, ...serializeDeleteProps(metadata.props)];
98
+ const auditProps = [
99
+ `id='${safeId}'`,
100
+ ...serializeDeleteProps(metadata.props),
101
+ ];
142
102
  return `kb_retract_entity('${safeId}', ${metadata.type}, [${auditProps.join(", ")}])`;
143
103
  }
144
104
  function serializeDeleteProps(props) {
@@ -29,11 +29,15 @@ function normalizeSourceFiles(sourceFiles) {
29
29
  return normalized;
30
30
  }
31
31
  function clampConfidence(confidence) {
32
- const numeric = typeof confidence === "number" && Number.isFinite(confidence) ? confidence : 0.8;
32
+ const numeric = typeof confidence === "number" && Number.isFinite(confidence)
33
+ ? confidence
34
+ : 0.8;
33
35
  return Math.round(Math.min(1, Math.max(0, numeric)) * 100) / 100;
34
36
  }
35
37
  function normalizeClaimValue(value) {
36
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
38
+ if (typeof value === "string" ||
39
+ typeof value === "number" ||
40
+ typeof value === "boolean") {
37
41
  if (typeof value === "number" && !Number.isFinite(value)) {
38
42
  throw new Error("Requirement modeling failed: value must be a finite number");
39
43
  }
@@ -58,7 +62,10 @@ function cleanPredicate(value) {
58
62
  return trimSentenceTail(stripListPrefix(value)) || "statement";
59
63
  }
60
64
  function fallbackSubjectFromSource(source) {
61
- const basename = path.basename(source, path.extname(source)).replace(/[-_]+/g, " ").trim();
65
+ const basename = path
66
+ .basename(source, path.extname(source))
67
+ .replace(/[-_]+/g, " ")
68
+ .trim();
62
69
  return basename || "Requirement";
63
70
  }
64
71
  function hasExplicitClaimFields(args) {
@@ -0,0 +1,75 @@
1
+ import { createHash } from "node:crypto";
2
+ import { listBundledSkills, loadBundledSkill, readBundledSkillResource, } from "kibi-cli/skills";
3
+ // implements REQ-001
4
+ export async function handleKbSkillsList(_args) {
5
+ try {
6
+ const skills = listBundledSkills();
7
+ const ids = skills.map((skill) => skill.id).join(", ") || "none";
8
+ return {
9
+ content: [
10
+ { type: "text", text: `Found ${skills.length} bundled skills: ${ids}` },
11
+ ],
12
+ structuredContent: { skills },
13
+ };
14
+ }
15
+ catch (error) {
16
+ const message = error instanceof Error ? error.message : String(error);
17
+ throw new Error(`Skills list failed: ${message}`);
18
+ }
19
+ }
20
+ // implements REQ-001
21
+ export async function handleKbSkillsLoad(args) {
22
+ try {
23
+ assertNonEmptyString(args.id, "id");
24
+ const bundle = loadBundledSkill(args.id);
25
+ const resources = bundle.manifest.resources ?? [];
26
+ const payload = {
27
+ metadata: bundle.manifest,
28
+ body: bundle.body,
29
+ resources,
30
+ contentHash: createHash("sha256")
31
+ .update(bundle.body, "utf8")
32
+ .digest("hex"),
33
+ sourceType: "bundled",
34
+ };
35
+ return {
36
+ content: [
37
+ {
38
+ type: "text",
39
+ text: `Loaded bundled skill ${bundle.manifest.id} with ${resources.length} resources`,
40
+ },
41
+ ],
42
+ structuredContent: payload,
43
+ };
44
+ }
45
+ catch (error) {
46
+ const message = error instanceof Error ? error.message : String(error);
47
+ throw new Error(`Skills load failed: ${message}`);
48
+ }
49
+ }
50
+ // implements REQ-001
51
+ export async function handleKbSkillsRead(args) {
52
+ try {
53
+ assertNonEmptyString(args.id, "id");
54
+ assertNonEmptyString(args.resource, "resource");
55
+ const resourceContent = readBundledSkillResource(args.id, args.resource);
56
+ return {
57
+ content: [
58
+ {
59
+ type: "text",
60
+ text: `Read bundled skill resource ${args.id}/${args.resource}`,
61
+ },
62
+ ],
63
+ structuredContent: { content: resourceContent },
64
+ };
65
+ }
66
+ catch (error) {
67
+ const message = error instanceof Error ? error.message : String(error);
68
+ throw new Error(`Skills read failed: ${message}`);
69
+ }
70
+ }
71
+ function assertNonEmptyString(value, field) {
72
+ if (typeof value !== "string" || value.trim() === "") {
73
+ throw new Error(`${field} must be a non-empty string`);
74
+ }
75
+ }
@@ -270,7 +270,7 @@ function normalizeCoordinateRecord(value) {
270
270
  if (!isRecord(value)) {
271
271
  return null;
272
272
  }
273
- const { sourceColumn, sourceEndColumn, sourceEndLine, sourceFile, sourceLine } = value;
273
+ const { sourceColumn, sourceEndColumn, sourceEndLine, sourceFile, sourceLine, } = value;
274
274
  if (typeof sourceFile !== "string" ||
275
275
  typeof sourceLine !== "number" ||
276
276
  typeof sourceColumn !== "number" ||
@@ -20,7 +20,6 @@ import { escapeAtom, toPrologAtom, toPrologString, } from "kibi-cli/prolog/codec
20
20
  import entitySchema from "kibi-cli/schemas/entity";
21
21
  import relationshipSchema from "kibi-cli/schemas/relationship";
22
22
  import { isMcpDebugEnabled } from "../env.js";
23
- import { writeBriefPendingMarker } from "../utils/brief-marker.js";
24
23
  import { refreshCoordinatesForSymbolId } from "./symbols.js";
25
24
  let refreshCoordinatesForSymbolIdImpl = refreshCoordinatesForSymbolId;
26
25
  const ajv = new Ajv({ strict: false });
@@ -156,16 +155,6 @@ export async function handleKbUpsert(prolog, args) {
156
155
  if (!saveResult.success) {
157
156
  throw new Error(`Failed to save KB after upsert: ${saveResult.error || "Unknown error"}`);
158
157
  }
159
- writeBriefPendingMarker({
160
- ...(args._requestId ? { sessionId: args._requestId } : {}),
161
- operation: "upsert",
162
- entityIds: [id],
163
- relationships: relationships.map((rel) => ({
164
- from: String(rel.from),
165
- to: String(rel.to),
166
- type: String(rel.type),
167
- })),
168
- });
169
158
  if (type === "symbol") {
170
159
  try {
171
160
  await refreshCoordinatesForSymbolIdImpl(id);