kibi-mcp 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/docs.js +64 -50
- package/dist/server/tools.js +12 -0
- package/dist/tools/autopilot-candidates.js +229 -0
- package/dist/tools/autopilot-discovery.js +243 -0
- package/dist/tools/autopilot-generate.js +271 -0
- package/dist/tools/briefing-generate.js +529 -0
- package/dist/tools-config.js +61 -1
- package/package.json +2 -2
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { rankEntities } from "kibi-cli/search-ranking";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { classifyActivationState, } from "./autopilot-discovery.js";
|
|
5
|
+
import { runJsonModuleQuery, toPrologList } from "./core-module.js";
|
|
6
|
+
import { loadEntities } from "./entity-query.js";
|
|
7
|
+
import { handleKbStatus } from "./status.js";
|
|
8
|
+
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
9
|
+
const ALLOWED_TYPES = [
|
|
10
|
+
"req",
|
|
11
|
+
"adr",
|
|
12
|
+
"scenario",
|
|
13
|
+
"test",
|
|
14
|
+
"fact",
|
|
15
|
+
"flag",
|
|
16
|
+
"symbol",
|
|
17
|
+
];
|
|
18
|
+
const TYPE_PRIORITY = {
|
|
19
|
+
req: 7,
|
|
20
|
+
adr: 6,
|
|
21
|
+
scenario: 5,
|
|
22
|
+
test: 4,
|
|
23
|
+
fact: 3,
|
|
24
|
+
flag: 2,
|
|
25
|
+
symbol: 1,
|
|
26
|
+
};
|
|
27
|
+
const GRAPH_RELATIONSHIPS = [
|
|
28
|
+
"implements",
|
|
29
|
+
"covered_by",
|
|
30
|
+
"specified_by",
|
|
31
|
+
"verified_by",
|
|
32
|
+
"constrained_by",
|
|
33
|
+
"constrains",
|
|
34
|
+
"requires_property",
|
|
35
|
+
"guards",
|
|
36
|
+
"relates_to",
|
|
37
|
+
];
|
|
38
|
+
function activationReasonFor(state) {
|
|
39
|
+
switch (state) {
|
|
40
|
+
case "vendored_only":
|
|
41
|
+
return "Workspace appears to contain vendored Kibi sources only; briefing generation is disabled.";
|
|
42
|
+
case "root_partial":
|
|
43
|
+
return "Workspace root is partially configured; briefing generation is disabled until Kibi inputs fully resolve.";
|
|
44
|
+
case "root_active_seeded":
|
|
45
|
+
return "KB attached and ready for citation-backed briefing generation in a seeded workspace.";
|
|
46
|
+
case "root_active_thin":
|
|
47
|
+
return "KB attached but thin; briefing generation may lack enough evidence.";
|
|
48
|
+
default:
|
|
49
|
+
return "Workspace root is not fully initialized; briefing generation is disabled until Kibi is attached.";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function inferTextOnlyActivationState(workspaceRoot) {
|
|
53
|
+
try {
|
|
54
|
+
const vendoredMarkers = [
|
|
55
|
+
["kibi", "opencode.json"],
|
|
56
|
+
["kibi", "package.json"],
|
|
57
|
+
["kibi", "packages", "mcp"],
|
|
58
|
+
["kibi", "documentation"],
|
|
59
|
+
];
|
|
60
|
+
const hasVendoredTree = vendoredMarkers.some((segments) => pathExists(path.join(workspaceRoot, ...segments)));
|
|
61
|
+
const hasRootConfig = pathExists(path.join(workspaceRoot, ".kb", "config.json"));
|
|
62
|
+
if (!hasRootConfig && hasVendoredTree) {
|
|
63
|
+
return "vendored_only";
|
|
64
|
+
}
|
|
65
|
+
if (!hasRootConfig) {
|
|
66
|
+
return "root_uninitialized";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Fall through to the conservative thin state below.
|
|
71
|
+
}
|
|
72
|
+
return "root_active_thin";
|
|
73
|
+
}
|
|
74
|
+
function pathExists(candidatePath) {
|
|
75
|
+
try {
|
|
76
|
+
return fs.existsSync(candidatePath);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function unknownFreshness() {
|
|
83
|
+
return {
|
|
84
|
+
state: "unknown",
|
|
85
|
+
syncState: "unknown",
|
|
86
|
+
dirty: false,
|
|
87
|
+
syncedAt: null,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function normalizeTaskText(taskText) {
|
|
91
|
+
return (taskText ?? "").trim();
|
|
92
|
+
}
|
|
93
|
+
function normalizeSeedIds(seedIds) {
|
|
94
|
+
const normalized = [];
|
|
95
|
+
const seen = new Set();
|
|
96
|
+
for (const seedId of seedIds ?? []) {
|
|
97
|
+
const trimmed = String(seedId ?? "").trim();
|
|
98
|
+
if (!trimmed || seen.has(trimmed))
|
|
99
|
+
continue;
|
|
100
|
+
seen.add(trimmed);
|
|
101
|
+
normalized.push(trimmed);
|
|
102
|
+
}
|
|
103
|
+
return normalized;
|
|
104
|
+
}
|
|
105
|
+
function normalizeSourceFiles(workspaceRoot, sourceFiles) {
|
|
106
|
+
const normalized = [];
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const normalizedRoot = path.resolve(workspaceRoot);
|
|
109
|
+
for (const sourceFile of sourceFiles ?? []) {
|
|
110
|
+
const trimmed = String(sourceFile ?? "").trim();
|
|
111
|
+
if (!trimmed)
|
|
112
|
+
continue;
|
|
113
|
+
const candidateAbsolute = path.resolve(path.isAbsolute(trimmed)
|
|
114
|
+
? trimmed
|
|
115
|
+
: path.join(normalizedRoot, trimmed));
|
|
116
|
+
const relative = path.relative(normalizedRoot, candidateAbsolute);
|
|
117
|
+
const repoRelative = !relative.startsWith("..") && !path.isAbsolute(relative)
|
|
118
|
+
? relative
|
|
119
|
+
: trimmed;
|
|
120
|
+
const normalizedPath = repoRelative
|
|
121
|
+
.split(path.sep)
|
|
122
|
+
.join("/")
|
|
123
|
+
.replace(/^\.\//, "")
|
|
124
|
+
.replace(/^\//, "");
|
|
125
|
+
if (!normalizedPath || seen.has(normalizedPath))
|
|
126
|
+
continue;
|
|
127
|
+
seen.add(normalizedPath);
|
|
128
|
+
normalized.push(normalizedPath);
|
|
129
|
+
}
|
|
130
|
+
return normalized;
|
|
131
|
+
}
|
|
132
|
+
function isAllowedType(type) {
|
|
133
|
+
return ALLOWED_TYPES.includes(type);
|
|
134
|
+
}
|
|
135
|
+
function stripOuterSingleQuotes(value) {
|
|
136
|
+
return value.startsWith("'") && value.endsWith("'") && value.length >= 2
|
|
137
|
+
? value.slice(1, -1)
|
|
138
|
+
: value;
|
|
139
|
+
}
|
|
140
|
+
function candidateKey(entity) {
|
|
141
|
+
return `${String(entity.type ?? "")}::${String(entity.id ?? "")}`;
|
|
142
|
+
}
|
|
143
|
+
function normalizeEntity(entity) {
|
|
144
|
+
const type = stripOuterSingleQuotes(String(entity.type ?? "").trim());
|
|
145
|
+
if (!isAllowedType(type))
|
|
146
|
+
return null;
|
|
147
|
+
return {
|
|
148
|
+
...entity,
|
|
149
|
+
id: String(entity.id ?? "").trim(),
|
|
150
|
+
type,
|
|
151
|
+
title: String(entity.title ?? "").trim(),
|
|
152
|
+
status: String(entity.status ?? "").trim(),
|
|
153
|
+
source: entity.source ? String(entity.source).trim().split(path.sep).join("/") : undefined,
|
|
154
|
+
textRef: entity.textRef
|
|
155
|
+
? String(entity.textRef).trim()
|
|
156
|
+
: entity.text_ref
|
|
157
|
+
? String(entity.text_ref).trim()
|
|
158
|
+
: undefined,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function addCandidate(candidates, entity, scoreDelta, reason) {
|
|
162
|
+
const normalizedEntity = normalizeEntity(entity);
|
|
163
|
+
if (!normalizedEntity)
|
|
164
|
+
return;
|
|
165
|
+
const key = candidateKey(normalizedEntity);
|
|
166
|
+
const existing = candidates.get(key);
|
|
167
|
+
if (existing) {
|
|
168
|
+
existing.score += scoreDelta;
|
|
169
|
+
if (!existing.reasons.includes(reason)) {
|
|
170
|
+
existing.reasons.push(reason);
|
|
171
|
+
}
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
candidates.set(key, {
|
|
175
|
+
entity: normalizedEntity,
|
|
176
|
+
score: scoreDelta,
|
|
177
|
+
reasons: [reason],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function toFreshness(statusPayload) {
|
|
181
|
+
const syncState = String(statusPayload.syncState ?? "unknown");
|
|
182
|
+
if (statusPayload.dirty || syncState === "stale") {
|
|
183
|
+
return {
|
|
184
|
+
state: "stale",
|
|
185
|
+
syncState,
|
|
186
|
+
dirty: Boolean(statusPayload.dirty),
|
|
187
|
+
syncedAt: statusPayload.syncedAt ?? null,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (syncState === "fresh") {
|
|
191
|
+
return {
|
|
192
|
+
state: "fresh",
|
|
193
|
+
syncState,
|
|
194
|
+
dirty: Boolean(statusPayload.dirty),
|
|
195
|
+
syncedAt: statusPayload.syncedAt ?? null,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
state: "unknown",
|
|
200
|
+
syncState,
|
|
201
|
+
dirty: Boolean(statusPayload.dirty),
|
|
202
|
+
syncedAt: statusPayload.syncedAt ?? null,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function summarizeCandidateReason(reasons) {
|
|
206
|
+
return reasons.join(", ");
|
|
207
|
+
}
|
|
208
|
+
function sortedEntities(candidates) {
|
|
209
|
+
return Array.from(candidates.values())
|
|
210
|
+
.map(({ entity, score, reasons }) => {
|
|
211
|
+
const type = String(entity.type);
|
|
212
|
+
const typeBonus = TYPE_PRIORITY[type] ?? 0;
|
|
213
|
+
return {
|
|
214
|
+
id: String(entity.id ?? ""),
|
|
215
|
+
type,
|
|
216
|
+
title: String(entity.title ?? ""),
|
|
217
|
+
status: String(entity.status ?? ""),
|
|
218
|
+
...(entity.source ? { source: String(entity.source) } : {}),
|
|
219
|
+
...(entity.textRef ? { textRef: String(entity.textRef) } : {}),
|
|
220
|
+
score: score + typeBonus,
|
|
221
|
+
reason: summarizeCandidateReason(reasons),
|
|
222
|
+
};
|
|
223
|
+
})
|
|
224
|
+
.sort((left, right) => {
|
|
225
|
+
if (right.score !== left.score) {
|
|
226
|
+
return right.score - left.score;
|
|
227
|
+
}
|
|
228
|
+
const leftPriority = TYPE_PRIORITY[left.type] ?? 0;
|
|
229
|
+
const rightPriority = TYPE_PRIORITY[right.type] ?? 0;
|
|
230
|
+
if (rightPriority !== leftPriority) {
|
|
231
|
+
return rightPriority - leftPriority;
|
|
232
|
+
}
|
|
233
|
+
return left.id.localeCompare(right.id);
|
|
234
|
+
})
|
|
235
|
+
.slice(0, 8);
|
|
236
|
+
}
|
|
237
|
+
function selectCitationIds(entities, predicate) {
|
|
238
|
+
return entities.filter(predicate).map((entity) => entity.id);
|
|
239
|
+
}
|
|
240
|
+
function asSearchableText(entity) {
|
|
241
|
+
return `${entity.title} ${entity.source ?? ""} ${entity.textRef ?? ""}`.toLowerCase();
|
|
242
|
+
}
|
|
243
|
+
function buildConstraints(entities) {
|
|
244
|
+
const statements = [];
|
|
245
|
+
const adrCitationIds = selectCitationIds(entities, (entity) => entity.type === "adr" &&
|
|
246
|
+
asSearchableText(entity).includes("read-only") &&
|
|
247
|
+
asSearchableText(entity).includes("mcp"));
|
|
248
|
+
if (adrCitationIds.length > 0) {
|
|
249
|
+
statements.push({
|
|
250
|
+
statement: "Keep the briefing generator read-only and MCP-owned.",
|
|
251
|
+
citationIds: adrCitationIds,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
const deterministicCitationIds = selectCitationIds(entities, (entity) => {
|
|
255
|
+
if (entity.type !== "req" && entity.type !== "test")
|
|
256
|
+
return false;
|
|
257
|
+
const searchable = asSearchableText(entity);
|
|
258
|
+
return searchable.includes("deterministic") || searchable.includes("citation");
|
|
259
|
+
});
|
|
260
|
+
if (deterministicCitationIds.length > 0) {
|
|
261
|
+
statements.push({
|
|
262
|
+
statement: "Return deterministic, citation-backed start-task output.",
|
|
263
|
+
citationIds: deterministicCitationIds,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return statements;
|
|
267
|
+
}
|
|
268
|
+
function buildRegressionRisks(entities) {
|
|
269
|
+
const statements = [];
|
|
270
|
+
const orderingCitationIds = selectCitationIds(entities, (entity) => entity.type === "test" &&
|
|
271
|
+
(asSearchableText(entity).includes("deterministic") ||
|
|
272
|
+
asSearchableText(entity).includes("briefing output")));
|
|
273
|
+
if (orderingCitationIds.length > 0) {
|
|
274
|
+
statements.push({
|
|
275
|
+
statement: "Do not let repeated calls change entity, citation, or prompt ordering.",
|
|
276
|
+
citationIds: orderingCitationIds,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
const budgetCitationIds = selectCitationIds(entities, (entity) => entity.type === "fact" &&
|
|
280
|
+
(asSearchableText(entity).includes("prompt") ||
|
|
281
|
+
asSearchableText(entity).includes("budget")));
|
|
282
|
+
if (budgetCitationIds.length > 0) {
|
|
283
|
+
statements.push({
|
|
284
|
+
statement: "Do not exceed the OpenCode prompt budget.",
|
|
285
|
+
citationIds: budgetCitationIds,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
return statements;
|
|
289
|
+
}
|
|
290
|
+
function buildMissingEvidence(_entities) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
function bulletForEntity(entity) {
|
|
294
|
+
const searchable = asSearchableText(entity);
|
|
295
|
+
if (entity.type === "req" &&
|
|
296
|
+
searchable.includes("deterministic") &&
|
|
297
|
+
searchable.includes("citation")) {
|
|
298
|
+
return `- ${entity.id}: Keep start-task briefings deterministic and citation-backed.`;
|
|
299
|
+
}
|
|
300
|
+
if (entity.type === "adr" &&
|
|
301
|
+
searchable.includes("read-only") &&
|
|
302
|
+
searchable.includes("mcp")) {
|
|
303
|
+
return `- ${entity.id}: Keep the MCP tool read-only; do not repair or mutate the workspace.`;
|
|
304
|
+
}
|
|
305
|
+
if (entity.type === "test" &&
|
|
306
|
+
(searchable.includes("deterministic") || searchable.includes("briefing output"))) {
|
|
307
|
+
return `- ${entity.id}: Repeated calls must preserve entity, citation, and prompt ordering.`;
|
|
308
|
+
}
|
|
309
|
+
if (entity.type === "fact" &&
|
|
310
|
+
(searchable.includes("prompt") || searchable.includes("budget"))) {
|
|
311
|
+
return `- ${entity.id}: Keep the prompt block within 120 words and 5 bullets.`;
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
function buildPromptBlock(entities) {
|
|
316
|
+
const bullets = entities
|
|
317
|
+
.map((entity) => bulletForEntity(entity))
|
|
318
|
+
.filter((bullet) => bullet !== null)
|
|
319
|
+
.slice(0, 5);
|
|
320
|
+
const promptBlock = bullets.join("\n");
|
|
321
|
+
const words = promptBlock.split(/\s+/).filter(Boolean);
|
|
322
|
+
if (bullets.length > 5 || words.length > 120) {
|
|
323
|
+
return "";
|
|
324
|
+
}
|
|
325
|
+
return promptBlock;
|
|
326
|
+
}
|
|
327
|
+
function buildCitations(entities) {
|
|
328
|
+
return entities.map((entity) => ({
|
|
329
|
+
id: entity.id,
|
|
330
|
+
type: entity.type,
|
|
331
|
+
title: entity.title,
|
|
332
|
+
...(entity.source ? { source: entity.source } : {}),
|
|
333
|
+
...(entity.textRef ? { textRef: entity.textRef } : {}),
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
function roundScore(score) {
|
|
337
|
+
return Math.max(0, Math.min(1, Math.round(score * 100) / 100));
|
|
338
|
+
}
|
|
339
|
+
function buildConfidence(activationState, freshness, entities, missingEvidence, promptBlock) {
|
|
340
|
+
const reasons = [];
|
|
341
|
+
let score = entities.length > 0 ? 0.82 : 0.35;
|
|
342
|
+
if (activationState === "root_active_seeded") {
|
|
343
|
+
score += 0.13;
|
|
344
|
+
reasons.push("Seeded workspace provides broad KB evidence.");
|
|
345
|
+
}
|
|
346
|
+
else if (activationState === "root_active_thin") {
|
|
347
|
+
score -= 0.2;
|
|
348
|
+
reasons.push("Thin workspace reduces available evidence.");
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
score -= 0.4;
|
|
352
|
+
reasons.push("Workspace posture does not support reliable briefing generation.");
|
|
353
|
+
}
|
|
354
|
+
if (freshness.state !== "fresh") {
|
|
355
|
+
score -= 0.25;
|
|
356
|
+
reasons.push("KB freshness is not clean enough for a ready briefing.");
|
|
357
|
+
}
|
|
358
|
+
if (freshness.dirty) {
|
|
359
|
+
score -= 0.1;
|
|
360
|
+
reasons.push("Workspace is dirty, so citations may be stale.");
|
|
361
|
+
}
|
|
362
|
+
if (missingEvidence.length > 0) {
|
|
363
|
+
score -= 0.15;
|
|
364
|
+
reasons.push("Some briefing claims are missing supporting evidence.");
|
|
365
|
+
}
|
|
366
|
+
if (!promptBlock) {
|
|
367
|
+
score -= 0.1;
|
|
368
|
+
reasons.push("Prompt block could not be assembled within the prompt budget.");
|
|
369
|
+
}
|
|
370
|
+
const rounded = roundScore(score);
|
|
371
|
+
return {
|
|
372
|
+
score: rounded,
|
|
373
|
+
level: rounded >= 0.8 ? "high" : rounded >= 0.55 ? "medium" : "low",
|
|
374
|
+
reasons,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
async function expandGraphNeighbors(prolog, seedIds) {
|
|
378
|
+
if (seedIds.length === 0) {
|
|
379
|
+
return new Map();
|
|
380
|
+
}
|
|
381
|
+
const payload = await runJsonModuleQuery(prolog, "discovery.pl", `discovery:graph_expand_json(${toPrologList(seedIds)}, ${toPrologList(GRAPH_RELATIONSHIPS)}, 'both', 1, [], 200, 500, JsonString)`, "Briefing graph expansion");
|
|
382
|
+
const seedSet = new Set(seedIds);
|
|
383
|
+
const connected = new Set();
|
|
384
|
+
for (const edge of payload.edges ?? []) {
|
|
385
|
+
const from = String(edge.from ?? "");
|
|
386
|
+
const to = String(edge.to ?? "");
|
|
387
|
+
if (seedSet.has(from) && to)
|
|
388
|
+
connected.add(to);
|
|
389
|
+
if (seedSet.has(to) && from)
|
|
390
|
+
connected.add(from);
|
|
391
|
+
}
|
|
392
|
+
const neighbors = new Map();
|
|
393
|
+
for (const node of payload.nodes ?? []) {
|
|
394
|
+
const normalized = normalizeEntity(node);
|
|
395
|
+
if (!normalized)
|
|
396
|
+
continue;
|
|
397
|
+
const nodeId = String(normalized.id ?? "");
|
|
398
|
+
if (seedSet.has(nodeId) || !connected.has(nodeId))
|
|
399
|
+
continue;
|
|
400
|
+
neighbors.set(nodeId, normalized);
|
|
401
|
+
}
|
|
402
|
+
return neighbors;
|
|
403
|
+
}
|
|
404
|
+
async function loadByIds(prolog, ids) {
|
|
405
|
+
const groups = await Promise.all(ids.map((id) => loadEntities(prolog, { id })));
|
|
406
|
+
return groups.flat();
|
|
407
|
+
}
|
|
408
|
+
async function loadBySourceFiles(prolog, sourceFiles) {
|
|
409
|
+
const groups = await Promise.all(sourceFiles.map((sourceFile) => loadEntities(prolog, { sourceFile })));
|
|
410
|
+
return groups.flat();
|
|
411
|
+
}
|
|
412
|
+
function buildTldr(briefingState, entities) {
|
|
413
|
+
if (briefingState === "ready") {
|
|
414
|
+
const citedIds = entities.slice(0, 4).map((entity) => entity.id).join(", ");
|
|
415
|
+
return `Ready briefing assembled from ${entities.length} cited entities: ${citedIds}.`;
|
|
416
|
+
}
|
|
417
|
+
return "No reliable briefing is available from the current workspace posture and freshness state.";
|
|
418
|
+
}
|
|
419
|
+
export async function handleKbBriefingGenerate(// implements REQ-mcp-kibi-briefing-v1
|
|
420
|
+
prolog, args) {
|
|
421
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
422
|
+
const taskText = normalizeTaskText(args.taskText);
|
|
423
|
+
const sourceFiles = normalizeSourceFiles(workspaceRoot, args.sourceFiles);
|
|
424
|
+
const seedIds = normalizeSeedIds(args.seedIds);
|
|
425
|
+
if (!taskText && sourceFiles.length === 0 && seedIds.length === 0) {
|
|
426
|
+
throw new Error("Briefing generation failed: at least one of taskText, sourceFiles, or seedIds must be provided");
|
|
427
|
+
}
|
|
428
|
+
const useTextOnlyFastPath = taskText.length > 0 && sourceFiles.length === 0 && seedIds.length === 0;
|
|
429
|
+
const activationState = useTextOnlyFastPath
|
|
430
|
+
? inferTextOnlyActivationState(workspaceRoot)
|
|
431
|
+
: await classifyActivationState(workspaceRoot, prolog);
|
|
432
|
+
const activationReason = activationReasonFor(activationState);
|
|
433
|
+
const freshness = useTextOnlyFastPath
|
|
434
|
+
? unknownFreshness()
|
|
435
|
+
: toFreshness((await handleKbStatus(prolog, {})).structuredContent);
|
|
436
|
+
if (activationState === "root_uninitialized" ||
|
|
437
|
+
activationState === "root_partial" ||
|
|
438
|
+
activationState === "vendored_only" ||
|
|
439
|
+
freshness.state === "stale") {
|
|
440
|
+
const confidence = buildConfidence(activationState, freshness, [], [], "");
|
|
441
|
+
return {
|
|
442
|
+
content: [{ type: "text", text: "No briefing is available." }],
|
|
443
|
+
structuredContent: {
|
|
444
|
+
briefingState: "no_briefing",
|
|
445
|
+
activationState,
|
|
446
|
+
activationReason,
|
|
447
|
+
freshness,
|
|
448
|
+
confidence,
|
|
449
|
+
tldr: buildTldr("no_briefing", []),
|
|
450
|
+
promptBlock: "",
|
|
451
|
+
entities: [],
|
|
452
|
+
constraints: [],
|
|
453
|
+
regressionRisks: [],
|
|
454
|
+
missingEvidence: [],
|
|
455
|
+
citations: [],
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
const candidates = new Map();
|
|
460
|
+
for (const entity of await loadByIds(prolog, seedIds)) {
|
|
461
|
+
addCandidate(candidates, entity, 100, "seed hit");
|
|
462
|
+
}
|
|
463
|
+
for (const entity of await loadBySourceFiles(prolog, sourceFiles)) {
|
|
464
|
+
addCandidate(candidates, entity, 90, "source-file hit");
|
|
465
|
+
}
|
|
466
|
+
const rankedIds = [];
|
|
467
|
+
if (taskText) {
|
|
468
|
+
const allEntities = (await loadEntities(prolog, {})).filter((entity) => isAllowedType(String(entity.type ?? "")));
|
|
469
|
+
const matches = await rankEntities(allEntities, taskText, workspaceRoot);
|
|
470
|
+
matches.forEach((match, index) => {
|
|
471
|
+
rankedIds.push(String(match.entity.id ?? ""));
|
|
472
|
+
addCandidate(candidates, match.entity, 70 - index, `text-search hit (#${index + 1})`);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
const graphSeeds = Array.from(new Set([
|
|
476
|
+
...seedIds,
|
|
477
|
+
...sourceFiles.flatMap(() => []),
|
|
478
|
+
...Array.from(candidates.values()).map((candidate) => String(candidate.entity.id ?? "")),
|
|
479
|
+
...rankedIds,
|
|
480
|
+
].filter(Boolean)));
|
|
481
|
+
const graphNeighbors = await expandGraphNeighbors(prolog, graphSeeds);
|
|
482
|
+
for (const neighbor of graphNeighbors.values()) {
|
|
483
|
+
addCandidate(candidates, neighbor, 40, "graph neighbor");
|
|
484
|
+
}
|
|
485
|
+
const entities = sortedEntities(candidates);
|
|
486
|
+
const constraints = buildConstraints(entities);
|
|
487
|
+
const regressionRisks = buildRegressionRisks(entities);
|
|
488
|
+
const missingEvidence = buildMissingEvidence(entities);
|
|
489
|
+
const promptBlock = buildPromptBlock(entities);
|
|
490
|
+
const citations = buildCitations(entities);
|
|
491
|
+
const confidence = buildConfidence(activationState, freshness, entities, missingEvidence, promptBlock);
|
|
492
|
+
const briefingState = confidence.score >= 0.55 ? "ready" : "no_briefing";
|
|
493
|
+
if (briefingState === "no_briefing") {
|
|
494
|
+
return {
|
|
495
|
+
content: [{ type: "text", text: "No briefing is available." }],
|
|
496
|
+
structuredContent: {
|
|
497
|
+
briefingState,
|
|
498
|
+
activationState,
|
|
499
|
+
activationReason,
|
|
500
|
+
freshness,
|
|
501
|
+
confidence,
|
|
502
|
+
tldr: buildTldr("no_briefing", []),
|
|
503
|
+
promptBlock: "",
|
|
504
|
+
entities: [],
|
|
505
|
+
constraints: [],
|
|
506
|
+
regressionRisks: [],
|
|
507
|
+
missingEvidence: [],
|
|
508
|
+
citations: [],
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
content: [{ type: "text", text: `Briefing ready with ${entities.length} cited entities.` }],
|
|
514
|
+
structuredContent: {
|
|
515
|
+
briefingState,
|
|
516
|
+
activationState,
|
|
517
|
+
activationReason,
|
|
518
|
+
freshness,
|
|
519
|
+
confidence,
|
|
520
|
+
tldr: buildTldr(briefingState, entities),
|
|
521
|
+
promptBlock,
|
|
522
|
+
entities,
|
|
523
|
+
constraints,
|
|
524
|
+
regressionRisks,
|
|
525
|
+
missingEvidence,
|
|
526
|
+
citations,
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
}
|
package/dist/tools-config.js
CHANGED
|
@@ -377,13 +377,73 @@ const BASE_TOOLS = [
|
|
|
377
377
|
"deprecated-adr-no-successor",
|
|
378
378
|
"domain-contradictions",
|
|
379
379
|
"strict-fact-shape",
|
|
380
|
+
"strict-req-fact-pairing",
|
|
380
381
|
],
|
|
381
382
|
},
|
|
382
|
-
description: "Optional rule subset. Allowed: must-priority-coverage, symbol-coverage, symbol-traceability, no-dangling-refs, no-cycles, required-fields, deprecated-adr-no-successor, domain-contradictions, strict-fact-shape. If omitted, server runs all.",
|
|
383
|
+
description: "Optional rule subset. Allowed: must-priority-coverage, symbol-coverage, symbol-traceability, no-dangling-refs, no-cycles, required-fields, deprecated-adr-no-successor, domain-contradictions, strict-fact-shape, strict-req-fact-pairing. If omitted, server runs all.",
|
|
383
384
|
},
|
|
384
385
|
},
|
|
385
386
|
},
|
|
386
387
|
},
|
|
388
|
+
{
|
|
389
|
+
name: "kb_autopilot_generate",
|
|
390
|
+
description: "Generate autopilot candidate batches for KB population. Read-only analysis that returns activation state, candidate entities with evidence, payoff summary, and exact applyPlan payloads for later kb_upsert calls. No mutation side effects.",
|
|
391
|
+
inputSchema: {
|
|
392
|
+
type: "object",
|
|
393
|
+
properties: {
|
|
394
|
+
includeGenericMarkdown: {
|
|
395
|
+
type: "boolean",
|
|
396
|
+
default: true,
|
|
397
|
+
description: "Whether to include generic markdown file content as candidate facts. Default: true.",
|
|
398
|
+
},
|
|
399
|
+
minConfidence: {
|
|
400
|
+
type: "number",
|
|
401
|
+
default: 0.8,
|
|
402
|
+
minimum: 0.6,
|
|
403
|
+
maximum: 0.95,
|
|
404
|
+
description: "Minimum confidence threshold for candidates. Clamped to [0.60, 0.95]. Default: 0.80.",
|
|
405
|
+
},
|
|
406
|
+
maxCandidates: {
|
|
407
|
+
type: "integer",
|
|
408
|
+
default: 50,
|
|
409
|
+
minimum: 1,
|
|
410
|
+
maximum: 200,
|
|
411
|
+
description: "Maximum number of candidates to return. Clamped to [1, 200]. Default: 50.",
|
|
412
|
+
},
|
|
413
|
+
entityTypes: {
|
|
414
|
+
type: "array",
|
|
415
|
+
items: {
|
|
416
|
+
type: "string",
|
|
417
|
+
enum: ["req", "scenario", "test", "adr", "fact", "symbol"],
|
|
418
|
+
},
|
|
419
|
+
description: "Optional filter to limit candidate generation to specific entity types.",
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: "kb_briefing_generate",
|
|
426
|
+
description: "Generate a deterministic, read-only, start-task briefing from task text, source files, and seed IDs. No mutation side effects.",
|
|
427
|
+
inputSchema: {
|
|
428
|
+
type: "object",
|
|
429
|
+
properties: {
|
|
430
|
+
taskText: {
|
|
431
|
+
type: "string",
|
|
432
|
+
description: "Optional task description used to rank relevant cited entities for the briefing.",
|
|
433
|
+
},
|
|
434
|
+
sourceFiles: {
|
|
435
|
+
type: "array",
|
|
436
|
+
items: { type: "string" },
|
|
437
|
+
description: "Optional source-file paths used to gather cited entities for the briefing.",
|
|
438
|
+
},
|
|
439
|
+
seedIds: {
|
|
440
|
+
type: "array",
|
|
441
|
+
items: { type: "string" },
|
|
442
|
+
description: "Optional seed entity IDs used to anchor the briefing graph expansion.",
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
}
|
|
387
447
|
];
|
|
388
448
|
/**
|
|
389
449
|
* Inject _diagnostic_telemetry schema into tool inputs when diagnostic mode is enabled.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
6
6
|
"ajv": "^8.18.0",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"fast-glob": "^3.2.12",
|
|
10
10
|
"gray-matter": "^4.0.3",
|
|
11
11
|
"js-yaml": "^4.1.0",
|
|
12
|
-
"kibi-cli": "^0.6.
|
|
12
|
+
"kibi-cli": "^0.6.2",
|
|
13
13
|
"kibi-core": "^0.5.1",
|
|
14
14
|
"mcpcat": "^0.1.12",
|
|
15
15
|
"ts-morph": "^23.0.0",
|