kibi-mcp 0.19.0 → 0.19.2
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/bin/kibi-mcp +1 -1
- package/dist/runtime/mcp-runtime.js +43 -0
- package/dist/server/docs.js +4 -4
- package/dist/server/json-schema-to-zod.js +73 -2
- package/dist/server/session.js +33 -15
- package/dist/server/tool-registration.js +35 -62
- package/dist/server/tools-runtime.js +52 -2
- package/dist/server/tools.js +19 -5
- package/dist/server/transport.js +9 -5
- package/dist/tools/autopilot-generate.js +37 -646
- package/dist/tools/autopilot-test-compat.js +88 -0
- package/dist/tools/autopilot-test-parsers.js +120 -0
- package/dist/tools/check.js +13 -106
- package/dist/tools/coverage.js +11 -25
- package/dist/tools/delete.js +5 -112
- package/dist/tools/discovery-adapter.js +23 -0
- package/dist/tools/entity-query.js +1 -111
- package/dist/tools/find-gaps.js +11 -26
- package/dist/tools/graph.js +11 -24
- package/dist/tools/model-requirement.js +8 -347
- package/dist/tools/mutation-context.js +28 -0
- package/dist/tools/query.js +5 -50
- package/dist/tools/relationship-validation.js +9 -116
- package/dist/tools/search.js +4 -37
- package/dist/tools/semantic-advisor.js +2 -40
- package/dist/tools/skills.js +17 -57
- package/dist/tools/sparql.js +12 -93
- package/dist/tools/status.js +5 -19
- package/dist/tools/suggest-predicates.js +11 -2448
- package/dist/tools/upsert.js +21 -816
- package/dist/tools/validate-upsert.js +12 -48
- package/dist/tools-config.js +75 -802
- package/package.json +4 -3
- package/dist/semantic-advisor/analyze-prose.js +0 -1392
- package/dist/semantic-advisor/prose-coverage-evaluator.js +0 -66
- package/dist/semantic-advisor/types.js +0 -1
|
@@ -1,656 +1,47 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
1
|
+
import { executeAutopilotGenerate, } from "kibi-cli/operations";
|
|
2
|
+
import { nodeFilesystem, nodeGit } from "kibi-cli/operations";
|
|
3
|
+
import { PrologProcess } from "kibi-cli/prolog";
|
|
4
4
|
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { getWorkspaceMigrationWarning } from "./model-requirement.js";
|
|
9
|
-
const defaultAutopilotGenerateDeps = {
|
|
10
|
-
buildGenericMarkdownCandidates,
|
|
11
|
-
buildNormativeRequirementCandidates,
|
|
12
|
-
buildProviderEvidenceCandidates,
|
|
13
|
-
buildSymbolManifestCandidates,
|
|
14
|
-
buildTypedMarkdownCandidates,
|
|
15
|
-
collectSourceOnlyAuthoringSignals,
|
|
16
|
-
discoverProviderEvidence,
|
|
17
|
-
getWorkspaceMigrationWarning,
|
|
18
|
-
loadEntities,
|
|
19
|
-
resolveActivationPolicy,
|
|
20
|
-
};
|
|
21
|
-
let autopilotGenerateDeps = defaultAutopilotGenerateDeps;
|
|
22
|
-
export function _setAutopilotGenerateDepsForTests(deps) {
|
|
23
|
-
autopilotGenerateDeps = { ...defaultAutopilotGenerateDeps, ...deps };
|
|
5
|
+
import { executeTestDependencies, resetTestDependencies, setTestDependencies, } from "./autopilot-test-compat.js";
|
|
6
|
+
export function _setAutopilotGenerateDepsForTests(dependencies) {
|
|
7
|
+
setTestDependencies(dependencies);
|
|
24
8
|
}
|
|
25
9
|
export function _resetAutopilotGenerateDepsForTests() {
|
|
26
|
-
|
|
10
|
+
resetTestDependencies();
|
|
27
11
|
}
|
|
28
|
-
function
|
|
29
|
-
return
|
|
12
|
+
function isOperationContext(value) {
|
|
13
|
+
return (value !== null &&
|
|
14
|
+
typeof value === "object" &&
|
|
15
|
+
"workspaceRoot" in value &&
|
|
16
|
+
"signal" in value);
|
|
30
17
|
}
|
|
31
|
-
function
|
|
32
|
-
const trimmed = String(value ?? "").trim();
|
|
33
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
34
|
-
}
|
|
35
|
-
function normalizeStringArray(values) {
|
|
36
|
-
const seen = new Set();
|
|
37
|
-
const normalized = [];
|
|
38
|
-
for (const value of values ?? []) {
|
|
39
|
-
const trimmed = String(value ?? "").trim();
|
|
40
|
-
if (!trimmed || seen.has(trimmed))
|
|
41
|
-
continue;
|
|
42
|
-
seen.add(trimmed);
|
|
43
|
-
normalized.push(trimmed);
|
|
44
|
-
}
|
|
45
|
-
return normalized;
|
|
46
|
-
}
|
|
47
|
-
function normalizeBootstrapContext(bootstrapContext) {
|
|
48
|
-
const projectSummary = normalizeOptionalString(bootstrapContext?.projectSummary);
|
|
18
|
+
function adaptProlog(prolog) {
|
|
49
19
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
priorityRoots: normalizeStringArray(bootstrapContext?.priorityRoots),
|
|
54
|
-
verificationAnchors: normalizeStringArray(bootstrapContext?.verificationAnchors),
|
|
20
|
+
query: (goal) => prolog.query(goal),
|
|
21
|
+
nextSolution: async () => null,
|
|
22
|
+
save: () => prolog.query("kb_save"),
|
|
55
23
|
};
|
|
56
24
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const entityType = String(candidate.entityType ?? "unknown");
|
|
78
|
-
counts[entityType] = (counts[entityType] ?? 0) + 1;
|
|
79
|
-
}
|
|
80
|
-
return counts;
|
|
81
|
-
}
|
|
82
|
-
function buildApplyPlan(candidateRecords) {
|
|
83
|
-
return candidateRecords.flatMap((candidate) => {
|
|
84
|
-
if (!Array.isArray(candidate.applyPlan))
|
|
85
|
-
return [];
|
|
86
|
-
return candidate.applyPlan.filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry));
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
function formatCandidateTypeCounts(candidateRecords) {
|
|
90
|
-
const counts = countCandidatesByType(candidateRecords);
|
|
91
|
-
return Object.keys(counts)
|
|
92
|
-
.sort()
|
|
93
|
-
.map((entityType) => `${entityType} ${counts[entityType] ?? 0}`)
|
|
94
|
-
.join(", ");
|
|
95
|
-
}
|
|
96
|
-
function summarizeSignalKinds(signals) {
|
|
97
|
-
const labels = Array.from(new Set(signals.map((signal) => signal.kind.toUpperCase())));
|
|
98
|
-
return labels.join("/");
|
|
99
|
-
}
|
|
100
|
-
function trimPromptBlock(bullets) {
|
|
101
|
-
const limitedBullets = bullets.filter(Boolean).slice(0, 5);
|
|
102
|
-
let promptBlock = limitedBullets.join("\n");
|
|
103
|
-
const words = promptBlock.split(/\s+/).filter(Boolean);
|
|
104
|
-
if (words.length <= 120)
|
|
105
|
-
return promptBlock;
|
|
106
|
-
const truncated = [];
|
|
107
|
-
let wordCount = 0;
|
|
108
|
-
for (const bullet of limitedBullets) {
|
|
109
|
-
const bulletWords = bullet.split(/\s+/).filter(Boolean);
|
|
110
|
-
if (wordCount + bulletWords.length > 120) {
|
|
111
|
-
const remaining = 120 - wordCount;
|
|
112
|
-
if (remaining > 3) {
|
|
113
|
-
truncated.push(`${bulletWords.slice(0, remaining).join(" ")}…`);
|
|
114
|
-
}
|
|
115
|
-
break;
|
|
116
|
-
}
|
|
117
|
-
truncated.push(bullet);
|
|
118
|
-
wordCount += bulletWords.length;
|
|
119
|
-
}
|
|
120
|
-
promptBlock = truncated.join("\n");
|
|
121
|
-
return promptBlock;
|
|
122
|
-
}
|
|
123
|
-
function buildPromptBlock(workspaceRoot, activationState, activationMode, activationReason, applyBlocked, declaredContext, candidateRecords, sourceOnlySignals, scanWarnings) {
|
|
124
|
-
const signalPaths = Array.from(new Set(sourceOnlySignals.map((signal) => toWorkspaceRelativePath(workspaceRoot, signal.sourcePath))));
|
|
125
|
-
const bullets = [];
|
|
126
|
-
bullets.push(applyBlocked
|
|
127
|
-
? `- Apply blocked: ${activationReason}`
|
|
128
|
-
: `- Mode: ${activationMode} (${activationState}).`);
|
|
129
|
-
if (declaredContext.projectSummary) {
|
|
130
|
-
bullets.push(`- Summary: ${declaredContext.projectSummary}`);
|
|
131
|
-
}
|
|
132
|
-
if (declaredContext.sourceOfTruthPaths.length > 0) {
|
|
133
|
-
bullets.push(`- Source of truth: ${listSummary(declaredContext.sourceOfTruthPaths, 3)}.`);
|
|
134
|
-
}
|
|
135
|
-
if (candidateRecords.length > 0) {
|
|
136
|
-
bullets.push(`- Safe candidates: ${candidateRecords.length} (${formatCandidateTypeCounts(candidateRecords)}).`);
|
|
137
|
-
}
|
|
138
|
-
if (sourceOnlySignals.length > 0) {
|
|
139
|
-
bullets.push(`- Author ${summarizeSignalKinds(sourceOnlySignals)} manually from ${listSummary(signalPaths, 3)}; keep them out of speculative candidate output.`);
|
|
140
|
-
}
|
|
141
|
-
else if (declaredContext.verificationAnchors.length > 0) {
|
|
142
|
-
bullets.push(`- Verify after kb_check with ${listSummary(declaredContext.verificationAnchors, 2)}.`);
|
|
143
|
-
}
|
|
144
|
-
if (activationMode === "attached_thin_handoff" ||
|
|
145
|
-
activationMode === "attached_seeded_handoff") {
|
|
146
|
-
bullets.push("- Handoff: use kb_search, kb_query, or gap/coverage tools to work with existing KB.");
|
|
147
|
-
}
|
|
148
|
-
if (scanWarnings.length > 0) {
|
|
149
|
-
bullets.push(`- Scan diagnostics: ${scanWarnings.length} warning(s) during evidence collection.`);
|
|
150
|
-
}
|
|
151
|
-
return trimPromptBlock(bullets);
|
|
152
|
-
}
|
|
153
|
-
function buildPayoffSummary(candidateRecords) {
|
|
154
|
-
const current = {};
|
|
155
|
-
const projectedIfAllApplied = { ...current };
|
|
156
|
-
for (const candidate of candidateRecords) {
|
|
157
|
-
const entityType = String(candidate.entityType ?? "unknown");
|
|
158
|
-
projectedIfAllApplied[entityType] =
|
|
159
|
-
(projectedIfAllApplied[entityType] ?? 0) + 1;
|
|
160
|
-
}
|
|
161
|
-
const delta = {};
|
|
162
|
-
for (const entityType of Object.keys(projectedIfAllApplied)) {
|
|
163
|
-
delta[entityType] =
|
|
164
|
-
(projectedIfAllApplied[entityType] ?? 0) - (current[entityType] ?? 0);
|
|
165
|
-
}
|
|
166
|
-
return { current, projectedIfAllApplied, delta };
|
|
167
|
-
}
|
|
168
|
-
function buildSourceOnlyActionDescription(workspaceRoot, sourceOnlySignals) {
|
|
169
|
-
const paths = Array.from(new Set(sourceOnlySignals.map((signal) => toWorkspaceRelativePath(workspaceRoot, signal.sourcePath))));
|
|
170
|
-
return `Author ${summarizeSignalKinds(sourceOnlySignals)} entities manually from source-only evidence in ${listSummary(paths, 3)}; do not auto-create them from scan output.`;
|
|
171
|
-
}
|
|
172
|
-
function buildCheckDescription(declaredContext) {
|
|
173
|
-
if (declaredContext.verificationAnchors.length > 0) {
|
|
174
|
-
return `After approved kb_upsert calls, run kb_check and verify ${listSummary(declaredContext.verificationAnchors, 2)}.`;
|
|
175
|
-
}
|
|
176
|
-
return "After approved kb_upsert calls, run kb_check to validate the resulting graph.";
|
|
177
|
-
}
|
|
178
|
-
function buildRecommendedActions(workspaceRoot, activationMode, activationReason, handoffMessage, applyBlocked, declaredContext, candidateRecords, sourceOnlySignals) {
|
|
179
|
-
const actions = [];
|
|
180
|
-
let order = 1;
|
|
181
|
-
const reviewTargets = Array.from(new Set([
|
|
182
|
-
...declaredContext.sourceOfTruthPaths,
|
|
183
|
-
...declaredContext.priorityRoots,
|
|
184
|
-
...sourceOnlySignals.map((signal) => toWorkspaceRelativePath(workspaceRoot, signal.sourcePath)),
|
|
185
|
-
]));
|
|
186
|
-
const candidateIds = candidateRecords
|
|
187
|
-
.map((candidate) => String(candidate.candidateId ?? ""))
|
|
188
|
-
.filter(Boolean);
|
|
189
|
-
const isActiveRepo = activationMode === "attached_thin_handoff" ||
|
|
190
|
-
activationMode === "attached_seeded_handoff";
|
|
191
|
-
actions.push({
|
|
192
|
-
order: order++,
|
|
193
|
-
kind: "query",
|
|
194
|
-
description: reviewTargets.length > 0
|
|
195
|
-
? `Review ${listSummary(reviewTargets, 3)} before authoring or applying bootstrap output.`
|
|
196
|
-
: "Review the workspace evidence and any existing KB records before authoring or applying bootstrap output.",
|
|
197
|
-
});
|
|
198
|
-
if (isActiveRepo) {
|
|
199
|
-
actions.push({
|
|
200
|
-
order: order++,
|
|
201
|
-
kind: "handoff",
|
|
202
|
-
description: "Use kb_search to explore existing KB entities and understand current coverage.",
|
|
203
|
-
});
|
|
204
|
-
actions.push({
|
|
205
|
-
order: order++,
|
|
206
|
-
kind: "handoff",
|
|
207
|
-
description: "Use kb_query or kb_graph with task-relevant IDs to inspect cited KB context.",
|
|
208
|
-
});
|
|
209
|
-
actions.push({
|
|
210
|
-
order: order++,
|
|
211
|
-
kind: "handoff",
|
|
212
|
-
description: activationMode === "attached_thin_handoff"
|
|
213
|
-
? "Use kb_find_gaps to identify coverage holes and guide incremental KB growth."
|
|
214
|
-
: "Use kb_coverage to review traceability and identify areas needing attention.",
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
if (applyBlocked) {
|
|
218
|
-
actions.push({
|
|
219
|
-
order: order++,
|
|
220
|
-
kind: "handoff",
|
|
221
|
-
description: handoffMessage ??
|
|
222
|
-
blockedActivationMessage(activationMode, activationReason),
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
else if (candidateIds.length > 0) {
|
|
226
|
-
actions.push({
|
|
227
|
-
order: order++,
|
|
228
|
-
kind: "upsert",
|
|
229
|
-
description: `Review and optionally upsert ${candidateIds.length} safe candidate(s) from typed or deterministic evidence.`,
|
|
230
|
-
candidateIds,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
if (sourceOnlySignals.length > 0) {
|
|
234
|
-
actions.push({
|
|
235
|
-
order: order++,
|
|
236
|
-
kind: "handoff",
|
|
237
|
-
description: buildSourceOnlyActionDescription(workspaceRoot, sourceOnlySignals),
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
actions.push({
|
|
241
|
-
order: order++,
|
|
242
|
-
kind: "check",
|
|
243
|
-
description: buildCheckDescription(declaredContext),
|
|
244
|
-
});
|
|
245
|
-
return actions;
|
|
246
|
-
}
|
|
247
|
-
function buildConfidence(activationMode, applyBlocked, declaredContext, candidateRecords, sourceOnlySignals, promptBlock) {
|
|
248
|
-
const reasons = [];
|
|
249
|
-
let score = candidateRecords.length > 0 ? 0.68 : 0.44;
|
|
250
|
-
if (applyBlocked) {
|
|
251
|
-
score -= 0.24;
|
|
252
|
-
reasons.push("Current workspace posture blocks direct application.");
|
|
253
|
-
}
|
|
254
|
-
else {
|
|
255
|
-
score += 0.12;
|
|
256
|
-
reasons.push("Workspace posture allows read-only bootstrap synthesis.");
|
|
257
|
-
}
|
|
258
|
-
switch (activationMode) {
|
|
259
|
-
case "cold_start_bootstrap":
|
|
260
|
-
score += 0.1;
|
|
261
|
-
reasons.push("Cold-start mode is a strong fit for bootstrap synthesis.");
|
|
262
|
-
break;
|
|
263
|
-
case "repair_bootstrap":
|
|
264
|
-
score -= 0.05;
|
|
265
|
-
reasons.push("Repair mode favors staged recovery before apply.");
|
|
266
|
-
break;
|
|
267
|
-
case "attached_thin_handoff":
|
|
268
|
-
score -= 0.12;
|
|
269
|
-
reasons.push("Thin attached KB favors handoff/query guidance.");
|
|
270
|
-
break;
|
|
271
|
-
case "attached_seeded_handoff":
|
|
272
|
-
score -= 0.18;
|
|
273
|
-
reasons.push("Seeded attached KB already has enough history to prefer handoff guidance.");
|
|
274
|
-
break;
|
|
275
|
-
case "vendored_blocked":
|
|
276
|
-
score -= 0.25;
|
|
277
|
-
reasons.push("Vendored-only posture blocks bootstrap output from becoming actionable.");
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
if (declaredContext.projectSummary ||
|
|
281
|
-
declaredContext.sourceOfTruthPaths.length > 0 ||
|
|
282
|
-
declaredContext.sourceOfTruthNotes.length > 0 ||
|
|
283
|
-
declaredContext.priorityRoots.length > 0 ||
|
|
284
|
-
declaredContext.verificationAnchors.length > 0) {
|
|
285
|
-
score += 0.08;
|
|
286
|
-
reasons.push("Declared bootstrap context grounds the output.");
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
reasons.push("No declared bootstrap context was supplied.");
|
|
290
|
-
}
|
|
291
|
-
if (sourceOnlySignals.length > 0) {
|
|
292
|
-
score += 0.04;
|
|
293
|
-
reasons.push("Source-only evidence was routed into authoring guidance instead of speculative REQ/SCEN/TEST candidates.");
|
|
294
|
-
}
|
|
295
|
-
if (candidateRecords.length === 0) {
|
|
296
|
-
score -= 0.08;
|
|
297
|
-
reasons.push("No safe candidates were synthesized from current evidence.");
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
reasons.push(`${candidateRecords.length} safe candidate(s) are ready for review.`);
|
|
301
|
-
}
|
|
302
|
-
if (!promptBlock) {
|
|
303
|
-
score -= 0.05;
|
|
304
|
-
reasons.push("Prompt block could not be assembled within the handoff budget.");
|
|
305
|
-
}
|
|
306
|
-
const rounded = roundScore(score);
|
|
307
|
-
const level = rounded > 0.7 ? "high" : rounded >= 0.4 ? "medium" : "low";
|
|
308
|
-
const policy = level === "high"
|
|
309
|
-
? "full_actions"
|
|
310
|
-
: level === "medium"
|
|
311
|
-
? "review_required"
|
|
312
|
-
: "handoff_only";
|
|
313
|
-
if (policy === "review_required") {
|
|
314
|
-
reasons.push("Medium confidence: review recommended before applying.");
|
|
315
|
-
}
|
|
316
|
-
else if (policy === "handoff_only") {
|
|
317
|
-
reasons.push("Low confidence: handoff-only output with diagnostic guidance.");
|
|
318
|
-
}
|
|
319
|
-
return {
|
|
320
|
-
score: rounded,
|
|
321
|
-
level,
|
|
322
|
-
reasons,
|
|
323
|
-
policy,
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function buildTldr(activationMode, applyBlocked, candidateRecords, sourceOnlySignals, activationReason, handoffMessage) {
|
|
327
|
-
if (applyBlocked) {
|
|
328
|
-
if (candidateRecords.length > 0 || sourceOnlySignals.length > 0) {
|
|
329
|
-
return `Bootstrap guidance is ready in ${activationMode}: ${candidateRecords.length} safe candidate(s), ${sourceOnlySignals.length} source-only authoring follow-up(s), and apply remains blocked.`;
|
|
330
|
-
}
|
|
331
|
-
return (handoffMessage ??
|
|
332
|
-
blockedActivationMessage(activationMode, activationReason));
|
|
333
|
-
}
|
|
334
|
-
if (candidateRecords.length > 0 || sourceOnlySignals.length > 0) {
|
|
335
|
-
return `Bootstrap output is ready with ${candidateRecords.length} safe candidate(s) and ${sourceOnlySignals.length} source-only authoring follow-up(s).`;
|
|
336
|
-
}
|
|
337
|
-
return "Bootstrap output found no safe candidates; follow the recommended actions to continue.";
|
|
338
|
-
}
|
|
339
|
-
function extractTextRefFromApplyPlan(applyPlan) {
|
|
340
|
-
if (!Array.isArray(applyPlan) || applyPlan.length === 0)
|
|
341
|
-
return "";
|
|
342
|
-
const first = applyPlan[0];
|
|
343
|
-
if (!first || typeof first !== "object")
|
|
344
|
-
return "";
|
|
345
|
-
const firstRecord = first;
|
|
346
|
-
const properties = firstRecord.properties;
|
|
347
|
-
if (!properties || typeof properties !== "object")
|
|
348
|
-
return "";
|
|
349
|
-
const propsRecord = properties;
|
|
350
|
-
const textRef = propsRecord.text_ref;
|
|
351
|
-
return typeof textRef === "string" ? textRef : "";
|
|
352
|
-
}
|
|
353
|
-
function toSuppressedCandidate(reason, candidate) {
|
|
354
|
-
return {
|
|
355
|
-
candidateId: String(candidate.candidateId ?? ""),
|
|
356
|
-
reason,
|
|
357
|
-
sourcePath: String(candidate.sourcePath ?? ""),
|
|
358
|
-
entityType: String(candidate.entityType ?? ""),
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
function blockedActivationMessage(activationMode, activationReason, handoffMessage) {
|
|
362
|
-
switch (activationMode) {
|
|
363
|
-
case "vendored_blocked":
|
|
364
|
-
return `Autopilot bootstrap blocked: ${activationReason}`;
|
|
365
|
-
case "attached_thin_handoff":
|
|
366
|
-
case "attached_seeded_handoff":
|
|
367
|
-
return handoffMessage
|
|
368
|
-
? `Autopilot handoff: ${handoffMessage}`
|
|
369
|
-
: `Autopilot handoff: ${activationReason}`;
|
|
370
|
-
default:
|
|
371
|
-
return `Autopilot bootstrap blocked: ${activationReason}`;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
function splitDiscoveredSources(workspaceRoot, candidates) {
|
|
375
|
-
const markdownFiles = [];
|
|
376
|
-
const manifestFiles = [];
|
|
377
|
-
for (const relativePath of candidates) {
|
|
378
|
-
const absolutePath = path.resolve(workspaceRoot, relativePath);
|
|
379
|
-
if (/symbols\.ya?ml$/i.test(relativePath)) {
|
|
380
|
-
manifestFiles.push(absolutePath);
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (/\.md$/i.test(relativePath)) {
|
|
384
|
-
markdownFiles.push(absolutePath);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
return { markdownFiles, manifestFiles };
|
|
388
|
-
}
|
|
389
|
-
export async function handleKbAutopilotGenerate(
|
|
390
|
-
// implements REQ-mcp-init-kibi-autopilot-v1
|
|
391
|
-
_prolog, args) {
|
|
392
|
-
const { includeGenericMarkdown = true, minConfidence = 0.8, maxCandidates = 50, entityTypes, bootstrapContext, } = args;
|
|
393
|
-
const normalizedMinConfidence = clamp(minConfidence, 0.6, 0.95);
|
|
394
|
-
const normalizedMaxCandidates = clamp(maxCandidates, 1, 200);
|
|
395
|
-
const prolog = _prolog;
|
|
396
|
-
// Gather existing entity ids to suppress duplicates
|
|
397
|
-
let existingIds = new Set();
|
|
398
|
-
try {
|
|
399
|
-
const entities = await autopilotGenerateDeps.loadEntities(prolog, {});
|
|
400
|
-
for (const e of entities) {
|
|
401
|
-
const id = String(e.id ?? "");
|
|
402
|
-
if (id)
|
|
403
|
-
existingIds.add(id);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
catch (error) {
|
|
407
|
-
// If we can't list entities, proceed with empty set
|
|
408
|
-
existingIds = new Set();
|
|
409
|
-
}
|
|
410
|
-
const workspaceRoot = resolveWorkspaceRoot();
|
|
411
|
-
const activation = await autopilotGenerateDeps.resolveActivationPolicy(workspaceRoot, prolog);
|
|
412
|
-
const activationState = activation.activationState;
|
|
413
|
-
const activationDiscovery = autopilotGenerateDeps.discoverProviderEvidence(workspaceRoot, activation);
|
|
414
|
-
const migrationWarning = await autopilotGenerateDeps.getWorkspaceMigrationWarning(workspaceRoot);
|
|
415
|
-
const declaredContext = normalizeBootstrapContext(bootstrapContext);
|
|
416
|
-
const discoveredCandidatePaths = activationDiscovery.evidence.reduce((acc, item) => {
|
|
417
|
-
const relativePath = item.relativePath;
|
|
418
|
-
if (typeof relativePath === "string" &&
|
|
419
|
-
(relativePath.endsWith(".md") || /symbols\.ya?ml$/i.test(relativePath))) {
|
|
420
|
-
acc.push(relativePath);
|
|
421
|
-
}
|
|
422
|
-
return acc;
|
|
423
|
-
}, []);
|
|
424
|
-
const discovery = splitDiscoveredSources(workspaceRoot, discoveredCandidatePaths);
|
|
425
|
-
const candidateDiscovery = {
|
|
426
|
-
...discovery,
|
|
427
|
-
evidence: activationDiscovery.evidence,
|
|
428
|
-
};
|
|
429
|
-
const guidanceDiscovery = includeGenericMarkdown
|
|
430
|
-
? candidateDiscovery
|
|
431
|
-
: {
|
|
432
|
-
...candidateDiscovery,
|
|
433
|
-
markdownFiles: [],
|
|
434
|
-
evidence: candidateDiscovery.evidence.filter((item) => item.kind !== "generic_markdown"),
|
|
435
|
-
};
|
|
436
|
-
let sourceOnlySignals = autopilotGenerateDeps.collectSourceOnlyAuthoringSignals(guidanceDiscovery, {
|
|
437
|
-
ids: existingIds,
|
|
438
|
-
workspaceRoot,
|
|
439
|
-
}, normalizedMinConfidence);
|
|
440
|
-
if (entityTypes && entityTypes.length > 0) {
|
|
441
|
-
const allowedSignals = new Set(entityTypes);
|
|
442
|
-
sourceOnlySignals = sourceOnlySignals.filter((signal) => allowedSignals.has(signal.kind));
|
|
443
|
-
}
|
|
444
|
-
let typedMarkdownCandidates = [];
|
|
445
|
-
let manifestCandidates = [];
|
|
446
|
-
let genericCandidates = [];
|
|
447
|
-
let normativeRequirementCandidates = [];
|
|
448
|
-
let providerEvidenceCandidates = [];
|
|
449
|
-
let allCandidates = [];
|
|
450
|
-
const seenByKey = new Map();
|
|
451
|
-
const suppressed = [];
|
|
452
|
-
// Helpers
|
|
453
|
-
function normalizeTitle(entityType, title) {
|
|
454
|
-
return `${entityType}::${String(title).trim().toLowerCase().replace(/\s+/g, " ")}`;
|
|
455
|
-
}
|
|
456
|
-
if (activation.allowCandidateGeneration) {
|
|
457
|
-
typedMarkdownCandidates =
|
|
458
|
-
autopilotGenerateDeps.buildTypedMarkdownCandidates(candidateDiscovery, {
|
|
459
|
-
ids: existingIds,
|
|
460
|
-
workspaceRoot,
|
|
461
|
-
});
|
|
462
|
-
manifestCandidates = autopilotGenerateDeps.buildSymbolManifestCandidates(candidateDiscovery, {
|
|
463
|
-
ids: existingIds,
|
|
464
|
-
workspaceRoot,
|
|
465
|
-
});
|
|
466
|
-
if (includeGenericMarkdown) {
|
|
467
|
-
genericCandidates = autopilotGenerateDeps.buildGenericMarkdownCandidates(candidateDiscovery, {
|
|
468
|
-
ids: existingIds,
|
|
469
|
-
workspaceRoot,
|
|
470
|
-
}, normalizedMinConfidence);
|
|
471
|
-
normativeRequirementCandidates =
|
|
472
|
-
autopilotGenerateDeps.buildNormativeRequirementCandidates(candidateDiscovery, {
|
|
473
|
-
ids: existingIds,
|
|
474
|
-
workspaceRoot,
|
|
475
|
-
}, normalizedMinConfidence);
|
|
476
|
-
}
|
|
477
|
-
providerEvidenceCandidates =
|
|
478
|
-
autopilotGenerateDeps.buildProviderEvidenceCandidates(candidateDiscovery, {
|
|
479
|
-
ids: existingIds,
|
|
480
|
-
workspaceRoot,
|
|
481
|
-
}, normalizedMinConfidence);
|
|
482
|
-
allCandidates = [
|
|
483
|
-
...typedMarkdownCandidates,
|
|
484
|
-
...manifestCandidates,
|
|
485
|
-
...genericCandidates,
|
|
486
|
-
...normativeRequirementCandidates,
|
|
487
|
-
...providerEvidenceCandidates,
|
|
488
|
-
];
|
|
489
|
-
if (entityTypes && entityTypes.length > 0) {
|
|
490
|
-
const allowed = new Set(entityTypes);
|
|
491
|
-
allCandidates = allCandidates.filter((candidate) => allowed.has(candidate.entityType));
|
|
492
|
-
}
|
|
493
|
-
allCandidates = allCandidates.filter((candidate) => candidate.confidence >= normalizedMinConfidence);
|
|
494
|
-
allCandidates.sort((left, right) => {
|
|
495
|
-
if (right.confidence !== left.confidence) {
|
|
496
|
-
return right.confidence - left.confidence;
|
|
497
|
-
}
|
|
498
|
-
if (left.sourcePath < right.sourcePath)
|
|
499
|
-
return -1;
|
|
500
|
-
if (left.sourcePath > right.sourcePath)
|
|
501
|
-
return 1;
|
|
502
|
-
return 0;
|
|
503
|
-
});
|
|
504
|
-
allCandidates = allCandidates.slice(0, normalizedMaxCandidates);
|
|
505
|
-
const typedTitleKeys = new Set(typedMarkdownCandidates.map((candidate) => normalizeTitle(String(candidate.entityType || ""), String(candidate.title || ""))));
|
|
506
|
-
for (const candidate of allCandidates) {
|
|
507
|
-
const record = { ...candidate };
|
|
508
|
-
const entityType = String(candidate.entityType || "");
|
|
509
|
-
const title = String(candidate.title || "");
|
|
510
|
-
const sourceKind = String(candidate.sourceKind || "");
|
|
511
|
-
const sourcePath = String(candidate.sourcePath || "");
|
|
512
|
-
const textRef = extractTextRefFromApplyPlan(candidate.applyPlan);
|
|
513
|
-
const titleKey = normalizeTitle(entityType, title);
|
|
514
|
-
const upsert = Array.isArray(candidate.applyPlan)
|
|
515
|
-
? candidate.applyPlan[0]
|
|
516
|
-
: null;
|
|
517
|
-
let upsertId = "";
|
|
518
|
-
if (upsert && typeof upsert === "object") {
|
|
519
|
-
const upsertRecord = upsert;
|
|
520
|
-
const directId = upsertRecord.id;
|
|
521
|
-
if (typeof directId === "string" && directId.length > 0) {
|
|
522
|
-
upsertId = directId;
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
const properties = upsertRecord.properties;
|
|
526
|
-
if (properties && typeof properties === "object") {
|
|
527
|
-
const nestedId = properties.id;
|
|
528
|
-
if (typeof nestedId === "string" && nestedId.length > 0) {
|
|
529
|
-
upsertId = nestedId;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
if (existingIds.has(upsertId)) {
|
|
535
|
-
suppressed.push(toSuppressedCandidate("entity_exists", record));
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
if (sourceKind === "generic_markdown" && typedTitleKeys.has(titleKey)) {
|
|
539
|
-
suppressed.push(toSuppressedCandidate("shadowed_by_typed_source", record));
|
|
540
|
-
continue;
|
|
541
|
-
}
|
|
542
|
-
const existing = seenByKey.get(titleKey);
|
|
543
|
-
if (existing) {
|
|
544
|
-
const existingConf = Number(existing.confidence ?? 0);
|
|
545
|
-
const thisConf = Number(candidate.confidence ?? 0);
|
|
546
|
-
if (thisConf > existingConf) {
|
|
547
|
-
suppressed.push(toSuppressedCandidate("duplicate_title", existing));
|
|
548
|
-
seenByKey.set(titleKey, record);
|
|
549
|
-
}
|
|
550
|
-
else if (thisConf < existingConf) {
|
|
551
|
-
suppressed.push(toSuppressedCandidate("duplicate_title", record));
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
const existingRef = `${String(existing.sourcePath ?? "")}::${extractTextRefFromApplyPlan(existing.applyPlan)}`;
|
|
555
|
-
const thisRef = `${sourcePath}::${textRef}`;
|
|
556
|
-
if (thisRef < existingRef) {
|
|
557
|
-
suppressed.push(toSuppressedCandidate("duplicate_title", existing));
|
|
558
|
-
seenByKey.set(titleKey, record);
|
|
559
|
-
}
|
|
560
|
-
else {
|
|
561
|
-
suppressed.push(toSuppressedCandidate("duplicate_title", record));
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
|
-
seenByKey.set(titleKey, record);
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
// Detect repository files that would be candidate inputs but are ignored by
|
|
570
|
-
// the repo ignore policy (e.g. .sisyphus drafts, .gitignore entries). Add
|
|
571
|
-
// them to suppressedCandidates with reason `ignored_source` so callers see
|
|
572
|
-
// why those files were omitted from candidate output.
|
|
573
|
-
try {
|
|
574
|
-
const repoIgnore = createRepoIgnorePolicy(workspaceRoot);
|
|
575
|
-
const potentialFiles = fg.sync(["**/*.md", "**/symbols.{yml,yaml}"], {
|
|
576
|
-
cwd: workspaceRoot,
|
|
577
|
-
absolute: true,
|
|
578
|
-
onlyFiles: true,
|
|
579
|
-
unique: true,
|
|
580
|
-
dot: true,
|
|
581
|
-
suppressErrors: true,
|
|
582
|
-
});
|
|
583
|
-
for (const absPath of potentialFiles) {
|
|
584
|
-
const rel = toWorkspaceRelativePath(workspaceRoot, absPath);
|
|
585
|
-
const explain = repoIgnore.explain(rel);
|
|
586
|
-
if (explain.ignored) {
|
|
587
|
-
// avoid duplicating existing suppressed entries for the same source
|
|
588
|
-
if (!suppressed.some((s) => String(s.sourcePath ?? "") === rel &&
|
|
589
|
-
s.reason === "ignored_source")) {
|
|
590
|
-
suppressed.push({
|
|
591
|
-
candidateId: String("") /* no candidate id for ignored source */,
|
|
592
|
-
reason: "ignored_source",
|
|
593
|
-
sourcePath: rel,
|
|
594
|
-
entityType: String("") /* unknown at this stage */,
|
|
595
|
-
detail: explain.reason,
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
catch {
|
|
602
|
-
// best-effort only; ignore failures here so generation can continue
|
|
603
|
-
}
|
|
604
|
-
const candidateRecords = Array.from(seenByKey.values());
|
|
605
|
-
const applyPlan = buildApplyPlan(candidateRecords);
|
|
606
|
-
const payoffSummary = buildPayoffSummary(candidateRecords);
|
|
607
|
-
const promptBlock = buildPromptBlock(workspaceRoot, activationState, activation.activationMode, activation.reason, activation.applyBlocked, declaredContext, candidateRecords, sourceOnlySignals, activationDiscovery.summary.scanWarnings);
|
|
608
|
-
const confidence = buildConfidence(activation.activationMode, activation.applyBlocked, declaredContext, candidateRecords, sourceOnlySignals, promptBlock);
|
|
609
|
-
const recommendedActions = buildRecommendedActions(workspaceRoot, activation.activationMode, activation.reason, activation.handoffMessage, activation.applyBlocked, declaredContext, candidateRecords, sourceOnlySignals);
|
|
610
|
-
const tldr = buildTldr(activation.activationMode, activation.applyBlocked, candidateRecords, sourceOnlySignals, activation.reason, activation.handoffMessage);
|
|
611
|
-
// Apply confidence policy: medium and low confidence force applyBlocked
|
|
612
|
-
const effectiveApplyBlocked = activation.applyBlocked ||
|
|
613
|
-
confidence.level === "medium" ||
|
|
614
|
-
confidence.level === "low";
|
|
615
|
-
const effectiveTldr = confidence.level === "low" && !activation.applyBlocked
|
|
616
|
-
? `Low-confidence bootstrap (${confidence.score}): review diagnostics before proceeding. ${tldr}`
|
|
617
|
-
: tldr;
|
|
618
|
-
const effectiveText = applyPlan.length > 0
|
|
619
|
-
? `${effectiveTldr} Review structuredContent.applyPlan for exact sequential kb_upsert payloads before requesting approval.`
|
|
620
|
-
: effectiveTldr;
|
|
621
|
-
const structuredContent = {
|
|
622
|
-
activationState,
|
|
623
|
-
activationMode: activation.activationMode,
|
|
624
|
-
bootstrapMode: activation.activationMode,
|
|
625
|
-
activationReason: activation.reason,
|
|
626
|
-
applyBlocked: effectiveApplyBlocked,
|
|
627
|
-
migrationWarning,
|
|
628
|
-
...(activation.handoffMessage
|
|
629
|
-
? { handoffMessage: activation.handoffMessage }
|
|
630
|
-
: {}),
|
|
631
|
-
confidence,
|
|
632
|
-
tldr,
|
|
633
|
-
promptBlock,
|
|
634
|
-
recommendedActions,
|
|
635
|
-
declaredContext,
|
|
636
|
-
discoverySummary: activationDiscovery.summary,
|
|
637
|
-
candidates: candidateRecords,
|
|
638
|
-
applyPlan,
|
|
639
|
-
suppressedCandidates: suppressed,
|
|
640
|
-
payoffSummary,
|
|
641
|
-
};
|
|
642
|
-
return {
|
|
643
|
-
content: [
|
|
644
|
-
{
|
|
645
|
-
type: "text",
|
|
646
|
-
text: effectiveText,
|
|
647
|
-
},
|
|
648
|
-
],
|
|
649
|
-
structuredContent,
|
|
650
|
-
migrationWarning,
|
|
651
|
-
candidates: candidateRecords,
|
|
652
|
-
applyPlan,
|
|
653
|
-
suppressedCandidates: suppressed,
|
|
654
|
-
payoffSummary,
|
|
25
|
+
// implements REQ-mcp-init-kibi-autopilot-v1, REQ-kibi-operation-interface-parity
|
|
26
|
+
export async function handleKbAutopilotGenerate(first, second) {
|
|
27
|
+
if (isOperationContext(second) && !(first instanceof PrologProcess))
|
|
28
|
+
return executeAutopilotGenerate(first, {
|
|
29
|
+
...second,
|
|
30
|
+
fs: second.fs ?? nodeFilesystem,
|
|
31
|
+
git: second.git ?? nodeGit,
|
|
32
|
+
});
|
|
33
|
+
if (!(first instanceof PrologProcess) || isOperationContext(second))
|
|
34
|
+
throw new TypeError("Invalid autopilot adapter invocation");
|
|
35
|
+
const testResult = await executeTestDependencies(first, second);
|
|
36
|
+
if (testResult)
|
|
37
|
+
return testResult;
|
|
38
|
+
const context = {
|
|
39
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
40
|
+
signal: new AbortController().signal,
|
|
41
|
+
clock: () => new Date(),
|
|
42
|
+
fs: nodeFilesystem,
|
|
43
|
+
git: nodeGit,
|
|
44
|
+
prolog: adaptProlog(first),
|
|
655
45
|
};
|
|
46
|
+
return executeAutopilotGenerate(second, context);
|
|
656
47
|
}
|