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.
Files changed (36) hide show
  1. package/bin/kibi-mcp +1 -1
  2. package/dist/runtime/mcp-runtime.js +43 -0
  3. package/dist/server/docs.js +4 -4
  4. package/dist/server/json-schema-to-zod.js +73 -2
  5. package/dist/server/session.js +33 -15
  6. package/dist/server/tool-registration.js +35 -62
  7. package/dist/server/tools-runtime.js +52 -2
  8. package/dist/server/tools.js +19 -5
  9. package/dist/server/transport.js +9 -5
  10. package/dist/tools/autopilot-generate.js +37 -646
  11. package/dist/tools/autopilot-test-compat.js +88 -0
  12. package/dist/tools/autopilot-test-parsers.js +120 -0
  13. package/dist/tools/check.js +13 -106
  14. package/dist/tools/coverage.js +11 -25
  15. package/dist/tools/delete.js +5 -112
  16. package/dist/tools/discovery-adapter.js +23 -0
  17. package/dist/tools/entity-query.js +1 -111
  18. package/dist/tools/find-gaps.js +11 -26
  19. package/dist/tools/graph.js +11 -24
  20. package/dist/tools/model-requirement.js +8 -347
  21. package/dist/tools/mutation-context.js +28 -0
  22. package/dist/tools/query.js +5 -50
  23. package/dist/tools/relationship-validation.js +9 -116
  24. package/dist/tools/search.js +4 -37
  25. package/dist/tools/semantic-advisor.js +2 -40
  26. package/dist/tools/skills.js +17 -57
  27. package/dist/tools/sparql.js +12 -93
  28. package/dist/tools/status.js +5 -19
  29. package/dist/tools/suggest-predicates.js +11 -2448
  30. package/dist/tools/upsert.js +21 -816
  31. package/dist/tools/validate-upsert.js +12 -48
  32. package/dist/tools-config.js +75 -802
  33. package/package.json +4 -3
  34. package/dist/semantic-advisor/analyze-prose.js +0 -1392
  35. package/dist/semantic-advisor/prose-coverage-evaluator.js +0 -66
  36. package/dist/semantic-advisor/types.js +0 -1
@@ -1,656 +1,47 @@
1
- import path from "node:path";
2
- import fg from "fast-glob";
3
- import { createRepoIgnorePolicy } from "kibi-cli/ignore-policy";
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 { buildGenericMarkdownCandidates, buildNormativeRequirementCandidates, buildProviderEvidenceCandidates, buildSymbolManifestCandidates, buildTypedMarkdownCandidates, collectSourceOnlyAuthoringSignals, } from "./autopilot-candidates.js";
6
- import { discoverProviderEvidence, resolveActivationPolicy, } from "./autopilot-discovery.js";
7
- import { loadEntities } from "./entity-query.js";
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
- autopilotGenerateDeps = defaultAutopilotGenerateDeps;
10
+ resetTestDependencies();
27
11
  }
28
- function clamp(value, min, max) {
29
- return Math.max(min, Math.min(max, value));
12
+ function isOperationContext(value) {
13
+ return (value !== null &&
14
+ typeof value === "object" &&
15
+ "workspaceRoot" in value &&
16
+ "signal" in value);
30
17
  }
31
- function normalizeOptionalString(value) {
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
- ...(projectSummary ? { projectSummary } : {}),
51
- sourceOfTruthPaths: normalizeStringArray(bootstrapContext?.sourceOfTruthPaths),
52
- sourceOfTruthNotes: normalizeStringArray(bootstrapContext?.sourceOfTruthNotes),
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
- function roundScore(score) {
58
- return Math.round(clamp(score, 0, 1) * 100) / 100;
59
- }
60
- function toWorkspaceRelativePath(workspaceRoot, targetPath) {
61
- const relative = path.relative(workspaceRoot, targetPath);
62
- if (!relative.startsWith("..") && !path.isAbsolute(relative)) {
63
- return relative.split(path.sep).join("/");
64
- }
65
- return targetPath.split(path.sep).join("/");
66
- }
67
- function listSummary(values, limit = 3) {
68
- if (values.length === 0)
69
- return "workspace evidence";
70
- if (values.length <= limit)
71
- return values.join(", ");
72
- return `${values.slice(0, limit).join(", ")} +${values.length - limit} more`;
73
- }
74
- function countCandidatesByType(candidateRecords) {
75
- const counts = {};
76
- for (const candidate of candidateRecords) {
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
  }