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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { presentAutopilot, selectAutopilotCandidates, } from "kibi-cli/operations";
|
|
2
|
+
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
3
|
+
import { activation, candidate, record, signal, } from "./autopilot-test-parsers.js";
|
|
4
|
+
let testDependencies;
|
|
5
|
+
export function setTestDependencies(dependencies) {
|
|
6
|
+
testDependencies = dependencies;
|
|
7
|
+
}
|
|
8
|
+
export function resetTestDependencies() {
|
|
9
|
+
testDependencies = undefined;
|
|
10
|
+
}
|
|
11
|
+
async function invoke(dependencies, name, values) {
|
|
12
|
+
const callable = dependencies[name];
|
|
13
|
+
return typeof callable === "function"
|
|
14
|
+
? Reflect.apply(callable, undefined, [...values])
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
17
|
+
export async function executeTestDependencies(prolog, input) {
|
|
18
|
+
const dependencies = testDependencies;
|
|
19
|
+
if (!dependencies)
|
|
20
|
+
return null;
|
|
21
|
+
const root = resolveWorkspaceRoot();
|
|
22
|
+
const policy = activation(await invoke(dependencies, "resolveActivationPolicy", [root, prolog]));
|
|
23
|
+
if (!policy)
|
|
24
|
+
return null;
|
|
25
|
+
const discovered = record(await invoke(dependencies, "discoverProviderEvidence", [root, policy]));
|
|
26
|
+
const rawSummary = record(discovered?.summary);
|
|
27
|
+
const summary = {
|
|
28
|
+
activationState: policy.activationState,
|
|
29
|
+
activationMode: policy.activationMode,
|
|
30
|
+
applyBlocked: policy.applyBlocked,
|
|
31
|
+
reason: policy.reason,
|
|
32
|
+
providersRun: [],
|
|
33
|
+
providerCounts: {},
|
|
34
|
+
detectedLanguages: [],
|
|
35
|
+
detectedTestFrameworks: [],
|
|
36
|
+
excludedRoots: [],
|
|
37
|
+
truncated: false,
|
|
38
|
+
scanWarnings: [],
|
|
39
|
+
...(rawSummary ? Object.fromEntries(Object.entries(rawSummary)) : {}),
|
|
40
|
+
};
|
|
41
|
+
const rawEntities = await invoke(dependencies, "loadEntities", [prolog, {}]);
|
|
42
|
+
const existingIds = new Set(Array.isArray(rawEntities)
|
|
43
|
+
? rawEntities
|
|
44
|
+
.map((entry) => record(entry)?.id)
|
|
45
|
+
.filter((id) => typeof id === "string")
|
|
46
|
+
: []);
|
|
47
|
+
const discoveryInput = {
|
|
48
|
+
evidence: Array.isArray(discovered?.evidence) ? discovered.evidence : [],
|
|
49
|
+
};
|
|
50
|
+
const context = { ids: existingIds, workspaceRoot: root };
|
|
51
|
+
const minConfidence = Math.max(0.6, Math.min(0.95, input.minConfidence ?? 0.8));
|
|
52
|
+
const all = [];
|
|
53
|
+
for (const name of [
|
|
54
|
+
"buildTypedMarkdownCandidates",
|
|
55
|
+
"buildSymbolManifestCandidates",
|
|
56
|
+
"buildGenericMarkdownCandidates",
|
|
57
|
+
"buildNormativeRequirementCandidates",
|
|
58
|
+
"buildProviderEvidenceCandidates",
|
|
59
|
+
]) {
|
|
60
|
+
const result = await invoke(dependencies, name, [
|
|
61
|
+
discoveryInput,
|
|
62
|
+
context,
|
|
63
|
+
minConfidence,
|
|
64
|
+
]);
|
|
65
|
+
if (Array.isArray(result))
|
|
66
|
+
all.push(...result.flatMap((entry) => candidate(entry) ?? []));
|
|
67
|
+
}
|
|
68
|
+
const rawSignals = await invoke(dependencies, "collectSourceOnlyAuthoringSignals", [discoveryInput, context, minConfidence]);
|
|
69
|
+
const signals = Array.isArray(rawSignals)
|
|
70
|
+
? rawSignals.flatMap((entry) => signal(entry) ?? [])
|
|
71
|
+
: [];
|
|
72
|
+
const selected = selectAutopilotCandidates(all.filter((entry) => entry.confidence >= minConfidence), existingIds, input.entityTypes, Math.max(1, Math.min(200, Math.trunc(input.maxCandidates ?? 50))));
|
|
73
|
+
const warning = await invoke(dependencies, "getWorkspaceMigrationWarning", [
|
|
74
|
+
root,
|
|
75
|
+
]);
|
|
76
|
+
return presentAutopilot({
|
|
77
|
+
root,
|
|
78
|
+
activation: policy,
|
|
79
|
+
discoverySummary: summary,
|
|
80
|
+
migrationWarning: typeof warning === "string" ? warning : null,
|
|
81
|
+
...(input.bootstrapContext
|
|
82
|
+
? { bootstrapContext: input.bootstrapContext }
|
|
83
|
+
: {}),
|
|
84
|
+
candidates: selected.candidates,
|
|
85
|
+
sourceOnlySignals: signals,
|
|
86
|
+
suppressedCandidates: selected.suppressed,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export function record(value) {
|
|
2
|
+
return value !== null && typeof value === "object" && !Array.isArray(value)
|
|
3
|
+
? Object.fromEntries(Object.entries(value))
|
|
4
|
+
: null;
|
|
5
|
+
}
|
|
6
|
+
export function candidate(value) {
|
|
7
|
+
const item = record(value);
|
|
8
|
+
if (!item ||
|
|
9
|
+
typeof item.candidateId !== "string" ||
|
|
10
|
+
typeof item.entityType !== "string" ||
|
|
11
|
+
typeof item.title !== "string" ||
|
|
12
|
+
typeof item.sourceKind !== "string" ||
|
|
13
|
+
typeof item.sourcePath !== "string" ||
|
|
14
|
+
typeof item.confidence !== "number" ||
|
|
15
|
+
!Array.isArray(item.applyPlan))
|
|
16
|
+
return null;
|
|
17
|
+
const relationships = Array.isArray(item.relationships)
|
|
18
|
+
? item.relationships.flatMap((entry) => {
|
|
19
|
+
const relation = record(entry);
|
|
20
|
+
return relation &&
|
|
21
|
+
typeof relation.type === "string" &&
|
|
22
|
+
typeof relation.from === "string" &&
|
|
23
|
+
typeof relation.to === "string"
|
|
24
|
+
? [{ type: relation.type, from: relation.from, to: relation.to }]
|
|
25
|
+
: [];
|
|
26
|
+
})
|
|
27
|
+
: [];
|
|
28
|
+
const applyPlan = item.applyPlan
|
|
29
|
+
.map(record)
|
|
30
|
+
.filter((entry) => entry !== null);
|
|
31
|
+
return {
|
|
32
|
+
candidateId: item.candidateId,
|
|
33
|
+
entityType: item.entityType,
|
|
34
|
+
title: item.title,
|
|
35
|
+
sourceKind: item.sourceKind,
|
|
36
|
+
sourcePath: item.sourcePath,
|
|
37
|
+
confidence: item.confidence,
|
|
38
|
+
confidenceBand: typeof item.confidenceBand === "string" ? item.confidenceBand : "medium",
|
|
39
|
+
evidence: Array.isArray(item.evidence)
|
|
40
|
+
? item.evidence.filter((entry) => typeof entry === "string")
|
|
41
|
+
: [],
|
|
42
|
+
relationships,
|
|
43
|
+
applyPlan,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function signal(value) {
|
|
47
|
+
const item = record(value);
|
|
48
|
+
if (!item ||
|
|
49
|
+
!["req", "scenario", "test"].includes(String(item.kind)) ||
|
|
50
|
+
typeof item.title !== "string" ||
|
|
51
|
+
typeof item.sourcePath !== "string" ||
|
|
52
|
+
typeof item.confidence !== "number")
|
|
53
|
+
return null;
|
|
54
|
+
const kind = item.kind === "req"
|
|
55
|
+
? "req"
|
|
56
|
+
: item.kind === "scenario"
|
|
57
|
+
? "scenario"
|
|
58
|
+
: "test";
|
|
59
|
+
return {
|
|
60
|
+
kind,
|
|
61
|
+
title: item.title,
|
|
62
|
+
sourcePath: item.sourcePath,
|
|
63
|
+
confidence: item.confidence,
|
|
64
|
+
evidence: Array.isArray(item.evidence)
|
|
65
|
+
? item.evidence.filter((entry) => typeof entry === "string")
|
|
66
|
+
: [],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function activation(value) {
|
|
70
|
+
const item = record(value);
|
|
71
|
+
if (!item ||
|
|
72
|
+
typeof item.activationState !== "string" ||
|
|
73
|
+
typeof item.activationMode !== "string" ||
|
|
74
|
+
typeof item.applyBlocked !== "boolean" ||
|
|
75
|
+
typeof item.allowCandidateGeneration !== "boolean" ||
|
|
76
|
+
typeof item.reason !== "string")
|
|
77
|
+
return null;
|
|
78
|
+
const states = new Set([
|
|
79
|
+
"root_uninitialized",
|
|
80
|
+
"root_partial",
|
|
81
|
+
"vendored_only",
|
|
82
|
+
"root_active_thin",
|
|
83
|
+
"root_active_seeded",
|
|
84
|
+
]);
|
|
85
|
+
const modes = new Set([
|
|
86
|
+
"cold_start_bootstrap",
|
|
87
|
+
"repair_bootstrap",
|
|
88
|
+
"attached_thin_handoff",
|
|
89
|
+
"attached_seeded_handoff",
|
|
90
|
+
"vendored_blocked",
|
|
91
|
+
]);
|
|
92
|
+
if (!states.has(item.activationState) || !modes.has(item.activationMode))
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
activationState: item.activationState === "root_partial"
|
|
96
|
+
? "root_partial"
|
|
97
|
+
: item.activationState === "vendored_only"
|
|
98
|
+
? "vendored_only"
|
|
99
|
+
: item.activationState === "root_active_thin"
|
|
100
|
+
? "root_active_thin"
|
|
101
|
+
: item.activationState === "root_active_seeded"
|
|
102
|
+
? "root_active_seeded"
|
|
103
|
+
: "root_uninitialized",
|
|
104
|
+
activationMode: item.activationMode === "repair_bootstrap"
|
|
105
|
+
? "repair_bootstrap"
|
|
106
|
+
: item.activationMode === "attached_thin_handoff"
|
|
107
|
+
? "attached_thin_handoff"
|
|
108
|
+
: item.activationMode === "attached_seeded_handoff"
|
|
109
|
+
? "attached_seeded_handoff"
|
|
110
|
+
: item.activationMode === "vendored_blocked"
|
|
111
|
+
? "vendored_blocked"
|
|
112
|
+
: "cold_start_bootstrap",
|
|
113
|
+
applyBlocked: item.applyBlocked,
|
|
114
|
+
allowCandidateGeneration: item.allowCandidateGeneration,
|
|
115
|
+
reason: item.reason,
|
|
116
|
+
...(typeof item.handoffMessage === "string"
|
|
117
|
+
? { handoffMessage: item.handoffMessage }
|
|
118
|
+
: {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
package/dist/tools/check.js
CHANGED
|
@@ -1,114 +1,21 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { checkSpec } from "kibi-cli/operations";
|
|
2
2
|
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
3
|
-
import { getEffectiveRules, loadChecksConfig } from "./check-config.js";
|
|
4
|
-
import { buildStructuredContent, formatImpactText, formatQualityDiagnosticsText, formatViolationText, } from "./check-format.js";
|
|
5
|
-
import { analyzeKbCheckImpact } from "./check-impact.js";
|
|
6
|
-
import { runAggregatedChecks } from "./check-prolog.js";
|
|
7
|
-
function qualityDiagnosticsFromImpact(impactResult) {
|
|
8
|
-
if (impactResult === undefined) {
|
|
9
|
-
return [];
|
|
10
|
-
}
|
|
11
|
-
return impactResult.impactDiagnostics.filter((diagnostic) => !diagnostic.blocking && diagnostic.severity !== "error");
|
|
12
|
-
}
|
|
13
|
-
function buildSummary(input) {
|
|
14
|
-
const sections = [formatViolationText(input.violations)];
|
|
15
|
-
if (input.impactResult !== undefined) {
|
|
16
|
-
sections.push(formatImpactText(input.impactResult));
|
|
17
|
-
}
|
|
18
|
-
if (input.qualityDiagnostics.length > 0) {
|
|
19
|
-
sections.push(formatQualityDiagnosticsText(input.qualityDiagnostics));
|
|
20
|
-
}
|
|
21
|
-
return sections.join("\n");
|
|
22
|
-
}
|
|
23
3
|
/**
|
|
24
4
|
* Handle kb_check tool calls - run validation rules on the KB
|
|
25
5
|
* Reuses validation logic from CLI check command
|
|
26
6
|
*/
|
|
27
7
|
// implements REQ-002
|
|
28
8
|
export async function handleKbCheck(prolog, args) {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
? impactQualityDiagnostics
|
|
42
|
-
: await collectFullKbQualityDiagnostics({
|
|
43
|
-
prolog,
|
|
44
|
-
...(args.maxDiagnostics !== undefined
|
|
45
|
-
? { maxDiagnostics: args.maxDiagnostics }
|
|
46
|
-
: {}),
|
|
47
|
-
});
|
|
48
|
-
return {
|
|
49
|
-
content: [
|
|
50
|
-
{
|
|
51
|
-
type: "text",
|
|
52
|
-
text: buildSummary({
|
|
53
|
-
violations: [],
|
|
54
|
-
impactResult,
|
|
55
|
-
qualityDiagnostics,
|
|
56
|
-
}),
|
|
57
|
-
},
|
|
58
|
-
],
|
|
59
|
-
structuredContent: buildStructuredContent({
|
|
60
|
-
violations: [],
|
|
61
|
-
diagnostics: [],
|
|
62
|
-
qualityDiagnostics,
|
|
63
|
-
impactResult,
|
|
64
|
-
}),
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
// Ensure we read the latest KB state, not a cached snapshot.
|
|
68
|
-
prolog.invalidateCache();
|
|
69
|
-
// Run aggregated checks using same approach as CLI
|
|
70
|
-
// This now runs ALL rules including symbol-traceability
|
|
71
|
-
const aggregatedViolations = await runAggregatedChecks(prolog, rulesAllowlist, checksConfig.symbolTraceability.requireAdr);
|
|
72
|
-
const diagnostics = aggregatedViolations.map((v) => ({
|
|
73
|
-
category: "SYNC_ERROR",
|
|
74
|
-
severity: "error",
|
|
75
|
-
message: v.description,
|
|
76
|
-
...(v.source !== undefined ? { file: v.source } : {}),
|
|
77
|
-
...(v.suggestion !== undefined ? { suggestion: v.suggestion } : {}),
|
|
78
|
-
}));
|
|
79
|
-
const qualityDiagnostics = hasExplicitRules
|
|
80
|
-
? impactQualityDiagnostics
|
|
81
|
-
: impactResult
|
|
82
|
-
? impactQualityDiagnostics
|
|
83
|
-
: await collectFullKbQualityDiagnostics({
|
|
84
|
-
prolog,
|
|
85
|
-
hardViolationEntityIds: new Set(aggregatedViolations.map((violation) => violation.entityId)),
|
|
86
|
-
...(args.maxDiagnostics !== undefined
|
|
87
|
-
? { maxDiagnostics: args.maxDiagnostics }
|
|
88
|
-
: {}),
|
|
89
|
-
});
|
|
90
|
-
const summary = buildSummary({
|
|
91
|
-
violations: aggregatedViolations,
|
|
92
|
-
impactResult,
|
|
93
|
-
qualityDiagnostics,
|
|
94
|
-
});
|
|
95
|
-
return {
|
|
96
|
-
content: [
|
|
97
|
-
{
|
|
98
|
-
type: "text",
|
|
99
|
-
text: summary,
|
|
100
|
-
},
|
|
101
|
-
],
|
|
102
|
-
structuredContent: buildStructuredContent({
|
|
103
|
-
violations: aggregatedViolations,
|
|
104
|
-
diagnostics,
|
|
105
|
-
qualityDiagnostics,
|
|
106
|
-
impactResult,
|
|
107
|
-
}),
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
112
|
-
throw new Error(`Check execution failed: ${message}`);
|
|
113
|
-
}
|
|
9
|
+
const workspaceRoot = args.workspaceRoot ?? resolveWorkspaceRoot();
|
|
10
|
+
return checkSpec.execute({ ...args }, {
|
|
11
|
+
workspaceRoot,
|
|
12
|
+
signal: new AbortController().signal,
|
|
13
|
+
clock: () => new Date(),
|
|
14
|
+
prolog: {
|
|
15
|
+
query: (goal) => prolog.query(goal),
|
|
16
|
+
nextSolution: async () => null,
|
|
17
|
+
invalidateCache: () => prolog.invalidateCache(),
|
|
18
|
+
save: () => prolog.query("kb_save"),
|
|
19
|
+
},
|
|
20
|
+
});
|
|
114
21
|
}
|
package/dist/tools/coverage.js
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { executeCoverage } from "kibi-cli/operations";
|
|
2
2
|
// implements REQ-002, REQ-013
|
|
3
3
|
export async function handleKbCoverage(prolog, args) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return {
|
|
15
|
-
content: [
|
|
16
|
-
{
|
|
17
|
-
type: "text",
|
|
18
|
-
text: `Coverage summary: ${fullyCovered} fully covered out of ${total}.`,
|
|
19
|
-
},
|
|
20
|
-
],
|
|
21
|
-
...(payload !== undefined ? { structuredContent: payload } : {}),
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
26
|
-
throw new Error(`Coverage execution failed: ${message}`);
|
|
27
|
-
}
|
|
4
|
+
return executeCoverage({ ...args }, {
|
|
5
|
+
workspaceRoot: process.cwd(),
|
|
6
|
+
signal: new AbortController().signal,
|
|
7
|
+
clock: () => new Date(),
|
|
8
|
+
prolog: {
|
|
9
|
+
query: (goal) => prolog.query(goal),
|
|
10
|
+
nextSolution: async () => null,
|
|
11
|
+
save: () => prolog.query("kb_save"),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
28
14
|
}
|
package/dist/tools/delete.js
CHANGED
|
@@ -1,113 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export async function handleKbDelete(
|
|
7
|
-
// implements REQ-002, REQ-011
|
|
8
|
-
prolog, args) {
|
|
9
|
-
const { ids } = args;
|
|
10
|
-
if (!ids || ids.length === 0) {
|
|
11
|
-
throw new Error("At least one ID required for delete");
|
|
12
|
-
}
|
|
13
|
-
let deleted = 0;
|
|
14
|
-
let skipped = 0;
|
|
15
|
-
const errors = [];
|
|
16
|
-
try {
|
|
17
|
-
for (const id of ids) {
|
|
18
|
-
const safeId = escapeAtom(id);
|
|
19
|
-
// Check if entity exists
|
|
20
|
-
const checkGoal = `once(kb_entity('${safeId}', _, _))`;
|
|
21
|
-
const checkResult = await prolog.query(checkGoal);
|
|
22
|
-
if (!checkResult.success) {
|
|
23
|
-
errors.push(`Entity ${id} does not exist`);
|
|
24
|
-
skipped++;
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
// Check for dependents (entities that reference this one)
|
|
28
|
-
const dependentsGoal = `findall([RelType,From], (member(RelType, [depends_on, verified_by, validates, specified_by, relates_to, guards, publishes, consumes]), kb_relationship(RelType, From, '${safeId}')), Dependents)`;
|
|
29
|
-
const dependentsResult = await prolog.query(dependentsGoal);
|
|
30
|
-
if (!dependentsResult.success) {
|
|
31
|
-
errors.push(`Failed to inspect dependents for entity ${id}: ${dependentsResult.error ?? "Query failed"}`);
|
|
32
|
-
skipped++;
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const hasDependents = dependentsResult.bindings.Dependents !== undefined &&
|
|
36
|
-
dependentsResult.bindings.Dependents !== "[]";
|
|
37
|
-
if (hasDependents) {
|
|
38
|
-
errors.push(`Cannot delete entity ${id}: has dependents (other entities reference it)`);
|
|
39
|
-
skipped++;
|
|
40
|
-
}
|
|
41
|
-
if (hasDependents) {
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
// No dependents, safe to delete
|
|
45
|
-
const entityMetadata = await loadEntityMetadataForDelete(prolog, id, safeId);
|
|
46
|
-
const deleteGoal = buildDeleteGoal(safeId, entityMetadata);
|
|
47
|
-
const deleteResult = await prolog.query(deleteGoal);
|
|
48
|
-
if (!deleteResult.success) {
|
|
49
|
-
errors.push(`Failed to delete entity ${id}: ${deleteResult.error || "Unknown error"}`);
|
|
50
|
-
skipped++;
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
deleted++;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
// Save KB to disk
|
|
57
|
-
const saveResult = await prolog.query("kb_save");
|
|
58
|
-
if (!saveResult.success) {
|
|
59
|
-
throw new Error(`Failed to save KB after delete: ${saveResult.error || "Unknown error"}`);
|
|
60
|
-
}
|
|
61
|
-
prolog.invalidateCache();
|
|
62
|
-
return {
|
|
63
|
-
content: [
|
|
64
|
-
{
|
|
65
|
-
type: "text",
|
|
66
|
-
text: `Deleted ${deleted} entities. Skipped ${skipped}. ${errors.length > 0 ? `Errors: ${errors.join("; ")}` : ""}`,
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
structuredContent: {
|
|
70
|
-
deleted,
|
|
71
|
-
skipped,
|
|
72
|
-
errors,
|
|
73
|
-
},
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
78
|
-
throw new Error(`Delete execution failed: ${message}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
async function loadEntityMetadataForDelete(prolog, id, safeId) {
|
|
82
|
-
const result = await prolog.query(`findall(['${safeId}',Type,Props], kb_entity('${safeId}', Type, Props), Results)`);
|
|
83
|
-
if (!result.success) {
|
|
84
|
-
throw new Error(`Failed to load metadata for entity ${id}: ${result.error || "Unknown error"}`);
|
|
85
|
-
}
|
|
86
|
-
const rows = result.bindings.Results
|
|
87
|
-
? parseListOfLists(result.bindings.Results)
|
|
88
|
-
: [];
|
|
89
|
-
if (rows.length === 0) {
|
|
90
|
-
throw new Error(`Failed to load metadata for entity ${id}: Entity not found`);
|
|
91
|
-
}
|
|
92
|
-
const entity = parseEntityFromList(rows[0] ?? []);
|
|
93
|
-
const type = String(entity.type ?? "unknown");
|
|
94
|
-
const { id: _entityId, type: _entityType, ...props } = entity;
|
|
95
|
-
return { type, props };
|
|
96
|
-
}
|
|
97
|
-
function buildDeleteGoal(safeId, metadata) {
|
|
98
|
-
const auditProps = [
|
|
99
|
-
`id='${safeId}'`,
|
|
100
|
-
...serializeDeleteProps(metadata.props),
|
|
101
|
-
];
|
|
102
|
-
return `kb_retract_entity('${safeId}', ${metadata.type}, [${auditProps.join(", ")}])`;
|
|
103
|
-
}
|
|
104
|
-
function serializeDeleteProps(props) {
|
|
105
|
-
const orderedKeys = ["title", "source", "text_ref"];
|
|
106
|
-
return orderedKeys.flatMap((key) => {
|
|
107
|
-
const value = props[key];
|
|
108
|
-
if (typeof value !== "string") {
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
return `${key}=${JSON.stringify(value)}`;
|
|
112
|
-
});
|
|
1
|
+
import { deleteSpec } from "kibi-cli/operations";
|
|
2
|
+
import { createMutationContext } from "./mutation-context.js";
|
|
3
|
+
// implements REQ-kibi-operation-interface-parity
|
|
4
|
+
export async function handleKbDelete(prolog, args) {
|
|
5
|
+
return deleteSpec.execute(args, createMutationContext(prolog));
|
|
113
6
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { resolveWorkspaceRoot } from "../workspace.js";
|
|
2
|
+
export function createDiscoveryContext(prolog) {
|
|
3
|
+
// implements REQ-kibi-operation-interface-parity
|
|
4
|
+
let lastResult = null;
|
|
5
|
+
const port = {
|
|
6
|
+
query: async (goal) => {
|
|
7
|
+
lastResult = await prolog.query(goal);
|
|
8
|
+
return lastResult;
|
|
9
|
+
},
|
|
10
|
+
nextSolution: async () => {
|
|
11
|
+
const result = lastResult;
|
|
12
|
+
lastResult = null;
|
|
13
|
+
return result;
|
|
14
|
+
},
|
|
15
|
+
save: () => prolog.query("kb_save"),
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
workspaceRoot: resolveWorkspaceRoot(),
|
|
19
|
+
signal: new AbortController().signal,
|
|
20
|
+
clock: () => new Date(),
|
|
21
|
+
prolog: port,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,111 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export const VALID_ENTITY_TYPES = [
|
|
3
|
-
// implements REQ-002
|
|
4
|
-
"req",
|
|
5
|
-
"scenario",
|
|
6
|
-
"test",
|
|
7
|
-
"adr",
|
|
8
|
-
"flag",
|
|
9
|
-
"event",
|
|
10
|
-
"symbol",
|
|
11
|
-
"fact",
|
|
12
|
-
];
|
|
13
|
-
// implements REQ-002, REQ-013
|
|
14
|
-
export function validateEntityType(type) {
|
|
15
|
-
if (type && !VALID_ENTITY_TYPES.includes(type)) {
|
|
16
|
-
throw new Error(`Invalid type '${type}'. Valid types: ${VALID_ENTITY_TYPES.join(", ")}. Use a single type value, or omit this parameter to query all entities.`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
// implements REQ-002, REQ-013
|
|
20
|
-
export function buildEntityGoal(args) {
|
|
21
|
-
const { type, id, tags, sourceFile } = args;
|
|
22
|
-
if (sourceFile) {
|
|
23
|
-
const safeSource = escapeAtomContent(sourceFile);
|
|
24
|
-
if (type) {
|
|
25
|
-
const safeType = escapeAtomContent(type);
|
|
26
|
-
return `findall([Id,'${safeType}',Props], (kb_entities_by_source('${safeSource}', SourceIds), member(Id, SourceIds), kb_entity(Id, '${safeType}', Props)), Results)`;
|
|
27
|
-
}
|
|
28
|
-
return `findall([Id,Type,Props], (kb_entities_by_source('${safeSource}', SourceIds), member(Id, SourceIds), kb_entity(Id, Type, Props)), Results)`;
|
|
29
|
-
}
|
|
30
|
-
if (id && type) {
|
|
31
|
-
const safeId = escapeAtomContent(id);
|
|
32
|
-
const safeType = escapeAtomContent(type);
|
|
33
|
-
return `findall(['${safeId}','${safeType}',Props], kb_entity('${safeId}', '${safeType}', Props), Results)`;
|
|
34
|
-
}
|
|
35
|
-
if (id) {
|
|
36
|
-
const safeId = escapeAtomContent(id);
|
|
37
|
-
return `findall(['${safeId}',Type,Props], kb_entity('${safeId}', Type, Props), Results)`;
|
|
38
|
-
}
|
|
39
|
-
if (tags && tags.length > 0) {
|
|
40
|
-
if (type) {
|
|
41
|
-
const safeType = escapeAtomContent(type);
|
|
42
|
-
return `findall([Id,'${safeType}',Props], kb_entity(Id, '${safeType}', Props), Results)`;
|
|
43
|
-
}
|
|
44
|
-
return "findall([Id,Type,Props], kb_entity(Id, Type, Props), Results)";
|
|
45
|
-
}
|
|
46
|
-
if (type) {
|
|
47
|
-
const safeType = escapeAtomContent(type);
|
|
48
|
-
return `findall([Id,'${safeType}',Props], kb_entity(Id, '${safeType}', Props), Results)`;
|
|
49
|
-
}
|
|
50
|
-
return "findall([Id,Type,Props], kb_entity(Id, Type, Props), Results)";
|
|
51
|
-
}
|
|
52
|
-
// implements REQ-002, REQ-013
|
|
53
|
-
export async function loadEntities(prolog, args) {
|
|
54
|
-
validateEntityType(args.type);
|
|
55
|
-
const goal = buildEntityGoal(args);
|
|
56
|
-
const queryResult = await prolog.query(goal);
|
|
57
|
-
let results = [];
|
|
58
|
-
if (!queryResult.success) {
|
|
59
|
-
throw new Error(queryResult.error || "Query failed with unknown error");
|
|
60
|
-
}
|
|
61
|
-
if (queryResult.bindings.Results) {
|
|
62
|
-
const entitiesData = parseListOfLists(queryResult.bindings.Results);
|
|
63
|
-
for (const data of entitiesData) {
|
|
64
|
-
results.push(parseEntityFromList(data));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
else if (queryResult.bindings.Result) {
|
|
68
|
-
results = [parseEntityFromBinding(queryResult.bindings.Result)];
|
|
69
|
-
}
|
|
70
|
-
if (args.tags && args.tags.length > 0) {
|
|
71
|
-
const requestedTags = args.tags;
|
|
72
|
-
results = dedupeEntities(results.filter((entity) => hasAnyTag(entity, requestedTags)));
|
|
73
|
-
}
|
|
74
|
-
return dedupeEntities(results);
|
|
75
|
-
}
|
|
76
|
-
// implements REQ-002
|
|
77
|
-
export function paginateResults(results, limit = 100, offset = 0) {
|
|
78
|
-
return results.slice(offset, offset + limit);
|
|
79
|
-
}
|
|
80
|
-
function hasAnyTag(entity, requestedTags) {
|
|
81
|
-
const expected = new Set(requestedTags.map(normalizeTagValue));
|
|
82
|
-
const rawTags = entity.tags;
|
|
83
|
-
if (!Array.isArray(rawTags) || rawTags.length === 0) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
for (const tag of rawTags) {
|
|
87
|
-
if (expected.has(normalizeTagValue(tag))) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
function normalizeTagValue(tag) {
|
|
94
|
-
return String(tag).trim();
|
|
95
|
-
}
|
|
96
|
-
// implements REQ-002
|
|
97
|
-
export function dedupeEntities(entities) {
|
|
98
|
-
const seen = new Set();
|
|
99
|
-
const deduped = [];
|
|
100
|
-
for (const entity of entities) {
|
|
101
|
-
const id = String(entity.id ?? "");
|
|
102
|
-
const type = String(entity.type ?? "");
|
|
103
|
-
const key = `${type}::${id}`;
|
|
104
|
-
if (seen.has(key)) {
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
107
|
-
seen.add(key);
|
|
108
|
-
deduped.push(entity);
|
|
109
|
-
}
|
|
110
|
-
return deduped;
|
|
111
|
-
}
|
|
1
|
+
export { VALID_ENTITY_TYPES, buildEntityGoal, dedupeEntities, loadEntities, paginateResults, validateEntityType, } from "kibi-cli/operations";
|
package/dist/tools/find-gaps.js
CHANGED
|
@@ -1,29 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { validateEntityType } from "./entity-query.js";
|
|
1
|
+
import { executeFindGaps } from "kibi-cli/operations";
|
|
3
2
|
// implements REQ-002, REQ-013
|
|
4
3
|
export async function handleKbFindGaps(prolog, args) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
text: rows.length === 0
|
|
16
|
-
? "No matching gaps found."
|
|
17
|
-
: `Found ${payload?.count ?? rows.length} gap rows. Showing ${rows.length}: ${rows
|
|
18
|
-
.map((row) => row.id)
|
|
19
|
-
.join(", ")}`,
|
|
20
|
-
},
|
|
21
|
-
],
|
|
22
|
-
...(payload !== undefined ? { structuredContent: payload } : {}),
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
27
|
-
throw new Error(`Find-gaps execution failed: ${message}`);
|
|
28
|
-
}
|
|
4
|
+
return executeFindGaps({ ...args }, {
|
|
5
|
+
workspaceRoot: process.cwd(),
|
|
6
|
+
signal: new AbortController().signal,
|
|
7
|
+
clock: () => new Date(),
|
|
8
|
+
prolog: {
|
|
9
|
+
query: (goal) => prolog.query(goal),
|
|
10
|
+
nextSolution: async () => null,
|
|
11
|
+
save: () => prolog.query("kb_save"),
|
|
12
|
+
},
|
|
13
|
+
});
|
|
29
14
|
}
|