kibi-mcp 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/env.js +0 -27
- package/dist/server/docs.js +260 -0
- package/dist/server/session.js +218 -0
- package/dist/server/tools.js +199 -0
- package/dist/server/transport.js +71 -0
- package/dist/server.js +13 -846
- package/dist/tools/branch.js +0 -27
- package/dist/tools/check.js +32 -27
- package/dist/tools/context.js +1 -207
- package/dist/tools/coverage-report.js +0 -17
- package/dist/tools/delete.js +1 -20
- package/dist/tools/derive.js +1 -20
- package/dist/tools/impact.js +1 -20
- package/dist/tools/list-types.js +0 -17
- package/dist/tools/prolog-list.js +0 -27
- package/dist/tools/query-relationships.js +0 -17
- package/dist/tools/query.js +31 -249
- package/dist/tools/suggest-shared-facts.js +2 -19
- package/dist/tools/symbols.js +0 -27
- package/dist/tools/upsert.js +9 -11
- package/dist/tools-config.js +8 -32
- package/dist/workspace.js +0 -27
- package/package.json +3 -3
package/dist/env.js
CHANGED
|
@@ -15,33 +15,6 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
-
/*
|
|
19
|
-
How to apply this header to source files (examples)
|
|
20
|
-
|
|
21
|
-
1) Prepend header to a single file (POSIX shells):
|
|
22
|
-
|
|
23
|
-
cat LICENSE_HEADER.txt "$FILE" > "$FILE".with-header && mv "$FILE".with-header "$FILE"
|
|
24
|
-
|
|
25
|
-
2) Apply to multiple files (example: the project's main entry files):
|
|
26
|
-
|
|
27
|
-
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp packages/cli/src/*.ts packages/mcp/src/*.ts; do
|
|
28
|
-
if [ -f "$f" ]; then
|
|
29
|
-
cp "$f" "$f".bak
|
|
30
|
-
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
31
|
-
fi
|
|
32
|
-
done
|
|
33
|
-
|
|
34
|
-
3) Avoid duplicating the header: run a quick guard to only add if missing
|
|
35
|
-
|
|
36
|
-
for f in packages/cli/bin/kibi packages/mcp/bin/kibi-mcp; do
|
|
37
|
-
if [ -f "$f" ]; then
|
|
38
|
-
if ! head -n 5 "$f" | grep -q "Copyright (C) 2026 Piotr Franczyk"; then
|
|
39
|
-
cp "$f" "$f".bak
|
|
40
|
-
(cat LICENSE_HEADER.txt; echo; cat "$f" ) > "$f".new && mv "$f".new "$f"
|
|
41
|
-
fi
|
|
42
|
-
fi
|
|
43
|
-
done
|
|
44
|
-
*/
|
|
45
18
|
import fs from "node:fs";
|
|
46
19
|
import { resolveEnvFilePath, resolveWorkspaceRoot } from "./workspace.js";
|
|
47
20
|
const DEFAULT_ENV_FILE = ".env";
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import { TOOLS } from "../tools-config.js";
|
|
19
|
+
const ACTIVE_TOOLS = TOOLS;
|
|
20
|
+
function renderToolsDoc() {
|
|
21
|
+
const lines = [
|
|
22
|
+
"# kibi-mcp Tools",
|
|
23
|
+
"",
|
|
24
|
+
"Use this reference to choose the correct tool before calling it.",
|
|
25
|
+
"",
|
|
26
|
+
"| Tool | Summary | Required Parameters |",
|
|
27
|
+
"| --- | --- | --- |",
|
|
28
|
+
];
|
|
29
|
+
for (const tool of ACTIVE_TOOLS) {
|
|
30
|
+
const required = Array.isArray(tool.inputSchema?.required)
|
|
31
|
+
? tool.inputSchema.required.join(", ")
|
|
32
|
+
: "none";
|
|
33
|
+
lines.push(`| ${tool.name} | ${tool.description} | ${required} |`);
|
|
34
|
+
}
|
|
35
|
+
return lines.join("\n");
|
|
36
|
+
}
|
|
37
|
+
export const PROMPTS = [
|
|
38
|
+
{
|
|
39
|
+
name: "init-kibi",
|
|
40
|
+
description: "Bootstrap Kibi on an existing repository with zero entities.",
|
|
41
|
+
text: [
|
|
42
|
+
"# Kibi Initialization Workflow",
|
|
43
|
+
"",
|
|
44
|
+
"Use this workflow to retroactively bootstrap Kibi on an existing repository with zero entities.",
|
|
45
|
+
"",
|
|
46
|
+
"## Phase 1: Discovery",
|
|
47
|
+
"1. Scan project structure to identify:",
|
|
48
|
+
" - Requirements (docs/requirements/, README, specs)",
|
|
49
|
+
" - Tests (unit, integration, e2e)",
|
|
50
|
+
" - Architecture decisions (docs/adr/, ARCHITECTURE.md)",
|
|
51
|
+
" - Feature flags (environment files, config)",
|
|
52
|
+
" - Events (domain events, pub/sub topics)",
|
|
53
|
+
" - Core symbols (key functions, classes, modules)",
|
|
54
|
+
"",
|
|
55
|
+
"## Phase 2: Fact Extraction",
|
|
56
|
+
"1. Identify atomic domain facts (invariants, constraints, properties)",
|
|
57
|
+
"2. Create reusable fact entities with `kb_upsert` using type 'fact'",
|
|
58
|
+
"3. Use consistent IDs: FACT-XXX with descriptive titles",
|
|
59
|
+
"",
|
|
60
|
+
"## Phase 3: Requirement Encoding",
|
|
61
|
+
"1. Extract requirements from documentation",
|
|
62
|
+
"2. For each requirement, determine which facts it constrains or requires",
|
|
63
|
+
"3. Create req entities with `kb_upsert` using type 'req'",
|
|
64
|
+
"4. Link reqs to facts using `constrains` and `requires_property` relationships",
|
|
65
|
+
"5. Reuse fact IDs across related requirements for contradiction detection",
|
|
66
|
+
"",
|
|
67
|
+
"## Phase 4: Test Linking",
|
|
68
|
+
"1. Map existing tests to requirements they verify",
|
|
69
|
+
"2. Create test entities with `kb_upsert` using type 'test'",
|
|
70
|
+
"3. Link tests to requirements using `verified_by` relationship",
|
|
71
|
+
"",
|
|
72
|
+
"## Phase 5: Architecture Documentation",
|
|
73
|
+
"1. Extract ADRs from docs/adr/ or decision records",
|
|
74
|
+
"2. Create adr entities with `kb_upsert` using type 'adr'",
|
|
75
|
+
"3. Link ADRs to symbols they constrain using `constrained_by`",
|
|
76
|
+
"",
|
|
77
|
+
"## Phase 6: Event Catalog",
|
|
78
|
+
"1. Identify domain/system events from code",
|
|
79
|
+
"2. Create event entities with `kb_upsert` using type 'event'",
|
|
80
|
+
"3. Link symbols that publish/consume events using `publishes`/`consumes`",
|
|
81
|
+
"",
|
|
82
|
+
"## Phase 7: Symbol Mapping",
|
|
83
|
+
"1. Map key code symbols to requirements",
|
|
84
|
+
"2. Create symbol entities with `kb_upsert` using type 'symbol'",
|
|
85
|
+
"3. Link symbols to requirements using `implements`",
|
|
86
|
+
"",
|
|
87
|
+
"## Phase 8: Validation",
|
|
88
|
+
"1. Run `kb_check` with all rules to verify integrity",
|
|
89
|
+
"2. Fix any dangling references or constraint violations",
|
|
90
|
+
"3. Re-run validation until clean",
|
|
91
|
+
"",
|
|
92
|
+
"## Best Practices",
|
|
93
|
+
"- Start with high-value entities first (critical requirements, security constraints)",
|
|
94
|
+
"- Use incremental batches to avoid overwhelming the KB",
|
|
95
|
+
"- Always call `kb_query` before creating to avoid duplicate entities",
|
|
96
|
+
"- Run `kb_check` after each batch of changes",
|
|
97
|
+
].join("\n"),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "kibi_overview",
|
|
101
|
+
description: "High-level model for using kibi-mcp safely and effectively.",
|
|
102
|
+
text: [
|
|
103
|
+
"# kibi-mcp Overview",
|
|
104
|
+
"",
|
|
105
|
+
"Treat this server as a branch-aware knowledge graph interface for software traceability.",
|
|
106
|
+
"",
|
|
107
|
+
"The server exposes 4 public tools for KB operations:",
|
|
108
|
+
"- `kb_query`: Read-only lookup of entities by type, ID, tags, or source file",
|
|
109
|
+
"- `kb_upsert`: Create or update entities and their relationships",
|
|
110
|
+
"- `kb_delete`: Remove entities by ID (with dependency safety checks)",
|
|
111
|
+
"- `kb_check`: Validate KB integrity against configurable rules",
|
|
112
|
+
"",
|
|
113
|
+
"Core modeling principles:",
|
|
114
|
+
"- Encode requirements as linked facts: `req --constrains--> fact` plus `req --requires_property--> fact`.",
|
|
115
|
+
"- Reuse canonical fact IDs across requirements; shared constrained facts make contradictions detectable.",
|
|
116
|
+
"- Use `kb_query` first to confirm current state before any mutation.",
|
|
117
|
+
"- Use `kb_upsert` and `kb_delete` only for intentional, traceable KB changes.",
|
|
118
|
+
"- Run `kb_check` after meaningful mutations to catch integrity issues early.",
|
|
119
|
+
"- Prefer explicit IDs and enum values to avoid invalid parameters.",
|
|
120
|
+
"- Assume every write can affect downstream traceability queries.",
|
|
121
|
+
].join("\n"),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: "kibi_workflow",
|
|
125
|
+
description: "Step-by-step call order for discovery, mutation, and verification.",
|
|
126
|
+
text: [
|
|
127
|
+
"# kibi-mcp Workflow",
|
|
128
|
+
"",
|
|
129
|
+
"Follow this sequence for reliable operation:",
|
|
130
|
+
"",
|
|
131
|
+
"1. **Query-first**: Always call `kb_query` to confirm current state before any mutation.",
|
|
132
|
+
"2. **Create-before-link**: Create endpoint entities with `kb_upsert` before linking them.",
|
|
133
|
+
"3. **Validate intent**: If creating links, call `kb_query` for both endpoint IDs first to ensure they exist.",
|
|
134
|
+
"4. **Model requirements as facts**: For new/updated reqs, create/reuse fact entities first, then express req semantics with `constrains` + `requires_property`.",
|
|
135
|
+
"5. **Mutate**: Call `kb_upsert` for create/update, or `kb_delete` for explicit removals.",
|
|
136
|
+
"6. **Targeted checks**: Run `kb_check` after meaningful mutations; specify only the rules you need.",
|
|
137
|
+
"",
|
|
138
|
+
"If a tool returns empty results, do not assume failure. Re-check filters (type, id, tags, sourceFile, limit, or offset).",
|
|
139
|
+
].join("\n"),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "kibi_constraints",
|
|
143
|
+
description: "Operational limits, validation rules, and mutation gotchas.",
|
|
144
|
+
text: [
|
|
145
|
+
"# kibi-mcp Constraints",
|
|
146
|
+
"",
|
|
147
|
+
"Apply these rules before calling write operations:",
|
|
148
|
+
"",
|
|
149
|
+
"## Validation Rules",
|
|
150
|
+
"- `kb_upsert` validates entity and relationship payloads against JSON Schema.",
|
|
151
|
+
"- `kb_delete` blocks deletion when dependents still reference the entity.",
|
|
152
|
+
"- Relationship and rule names are strict enums; unknown values fail validation.",
|
|
153
|
+
"- Branch KB setup is automatic at server startup; lifecycle maintenance stays outside the public MCP tool surface.",
|
|
154
|
+
"",
|
|
155
|
+
"## Telemetry-Driven Guardrails",
|
|
156
|
+
"- The server does not send external telemetry or analytics.",
|
|
157
|
+
"- All operations are local and branch-scoped.",
|
|
158
|
+
"- No network requests are made to external services.",
|
|
159
|
+
"- KB state persists only within the repository's `.kb/` directory.",
|
|
160
|
+
].join("\n"),
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
function registerDocResources() {
|
|
164
|
+
const overview = [
|
|
165
|
+
"# kibi-mcp Server Overview",
|
|
166
|
+
"",
|
|
167
|
+
"kibi-mcp is a stdio MCP server for querying and mutating the Kibi knowledge base.",
|
|
168
|
+
"",
|
|
169
|
+
"Scope:",
|
|
170
|
+
"- Entity CRUD-like operations for KB records",
|
|
171
|
+
"- Validation of KB integrity after changes",
|
|
172
|
+
"- Automatic branch-local attachment for the active workspace",
|
|
173
|
+
"",
|
|
174
|
+
"Use this server when you need branch-local, machine-readable project memory.",
|
|
175
|
+
].join("\n");
|
|
176
|
+
const errors = [
|
|
177
|
+
"# kibi-mcp Error Guide",
|
|
178
|
+
"",
|
|
179
|
+
"Common failure modes and recoveries:",
|
|
180
|
+
"",
|
|
181
|
+
"- `-32602 INVALID_PARAMS`: Tool arguments are missing/invalid. Recover by checking enum values and required fields.",
|
|
182
|
+
"- `-32601 METHOD_NOT_FOUND`: Unknown MCP method. Recover by using supported methods (`tools/*`, `prompts/*`, `resources/*`).",
|
|
183
|
+
"- `-32000 PROLOG_QUERY_FAILED`: Prolog query failed. Recover by validating IDs, rule names, and branch KB availability.",
|
|
184
|
+
"- `VALIDATION_ERROR` message: `kb_upsert` payload failed schema checks. Recover by fixing required fields and enum values.",
|
|
185
|
+
"- Delete blocked by dependents: `kb_delete` detected incoming references. Recover by removing/rewiring relationships first.",
|
|
186
|
+
"- Empty results: filters may be too strict. Recover by loosening type/id/tags/source filters and retrying.",
|
|
187
|
+
].join("\n");
|
|
188
|
+
const examples = [
|
|
189
|
+
"# kibi-mcp Examples",
|
|
190
|
+
"",
|
|
191
|
+
"## Model requirements as reusable facts",
|
|
192
|
+
'1. `kb_query` with `{ "type": "fact" }` to find existing fact IDs before creating new ones',
|
|
193
|
+
"2. `kb_upsert` for the fact entity first (create-before-link)",
|
|
194
|
+
"3. `kb_upsert` for the req entity and include `relationships` with `constrains` and `requires_property`",
|
|
195
|
+
"4. Reuse the same constrained fact ID across related requirements; vary property facts only when semantics differ",
|
|
196
|
+
'5. `kb_check` with `{ "rules": ["required-fields","no-dangling-refs"] }` for targeted validation',
|
|
197
|
+
"",
|
|
198
|
+
"## Add a requirement and link it to a test",
|
|
199
|
+
'1. `kb_query` with `{ "type": "test" }` to check for existing test IDs',
|
|
200
|
+
'2. `kb_query` with `{ "id": "REQ-XXX" }` to verify the requirement exists',
|
|
201
|
+
"3. `kb_upsert` with entity payload and `relationships` containing `verified_by`",
|
|
202
|
+
'4. `kb_check` with `{ "rules": ["required-fields","no-dangling-refs"] }` for targeted validation',
|
|
203
|
+
"",
|
|
204
|
+
"Note: Always use query-first pattern. Specify only needed rules in kb_check for faster validation.",
|
|
205
|
+
].join("\n");
|
|
206
|
+
return [
|
|
207
|
+
{
|
|
208
|
+
uri: "kibi://docs/overview",
|
|
209
|
+
name: "kibi docs overview",
|
|
210
|
+
description: "Full server description, purpose, and scope.",
|
|
211
|
+
mimeType: "text/markdown",
|
|
212
|
+
text: overview,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
uri: "kibi://docs/tools",
|
|
216
|
+
name: "kibi docs tools",
|
|
217
|
+
description: "Available tools with summaries and required parameters.",
|
|
218
|
+
mimeType: "text/markdown",
|
|
219
|
+
text: renderToolsDoc(),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
uri: "kibi://docs/errors",
|
|
223
|
+
name: "kibi docs errors",
|
|
224
|
+
description: "Common error modes and suggested recovery actions.",
|
|
225
|
+
mimeType: "text/markdown",
|
|
226
|
+
text: errors,
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
uri: "kibi://docs/examples",
|
|
230
|
+
name: "kibi docs examples",
|
|
231
|
+
description: "Concrete tool call sequences for common tasks.",
|
|
232
|
+
mimeType: "text/markdown",
|
|
233
|
+
text: examples,
|
|
234
|
+
},
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
export const DOC_RESOURCES = registerDocResources();
|
|
238
|
+
export function setupDocsAndPrompts(server) {
|
|
239
|
+
for (const prompt of PROMPTS) {
|
|
240
|
+
server.prompt(prompt.name, prompt.description, async () => ({
|
|
241
|
+
messages: [
|
|
242
|
+
{
|
|
243
|
+
role: "user",
|
|
244
|
+
content: { type: "text", text: prompt.text },
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
}));
|
|
248
|
+
}
|
|
249
|
+
for (const resource of DOC_RESOURCES) {
|
|
250
|
+
server.resource(resource.name, resource.uri, { description: resource.description, mimeType: resource.mimeType }, async () => ({
|
|
251
|
+
contents: [
|
|
252
|
+
{
|
|
253
|
+
uri: resource.uri,
|
|
254
|
+
mimeType: resource.mimeType,
|
|
255
|
+
text: resource.text,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
}));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Kibi — repo-local, per-branch, queryable long-term memory for software projects
|
|
3
|
+
Copyright (C) 2026 Piotr Franczyk
|
|
4
|
+
|
|
5
|
+
This program is free software: you can redistribute it and/or modify
|
|
6
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
7
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
8
|
+
(at your option) any later version.
|
|
9
|
+
|
|
10
|
+
This program is distributed in the hope that it will be useful,
|
|
11
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
GNU Affero General Public License for more details.
|
|
14
|
+
|
|
15
|
+
You should have received a copy of the GNU Affero General Public License
|
|
16
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
+
*/
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import process from "node:process";
|
|
21
|
+
import { PrologProcess } from "kibi-cli/prolog";
|
|
22
|
+
import { copyCleanSnapshot, getBranchDiagnostic, isValidBranchName, resolveActiveBranch, } from "kibi-cli/public/branch-resolver";
|
|
23
|
+
import { resolveKbPath, resolveWorkspaceRoot } from "../workspace.js";
|
|
24
|
+
export let prologProcess = null;
|
|
25
|
+
let isInitialized = false;
|
|
26
|
+
export let activeBranchName = "develop";
|
|
27
|
+
let ensurePrologTail = Promise.resolve();
|
|
28
|
+
export let isShuttingDown = false;
|
|
29
|
+
let shutdownTimeout = null;
|
|
30
|
+
export const inFlightRequests = new Map();
|
|
31
|
+
function debugLog(...args) {
|
|
32
|
+
if (process.env.KIBI_MCP_DEBUG) {
|
|
33
|
+
console.error(...args);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function ensureBranchKbExists(workspaceRoot, branch) {
|
|
37
|
+
if (!isValidBranchName(branch)) {
|
|
38
|
+
throw new Error(`Invalid branch name: ${branch}`);
|
|
39
|
+
}
|
|
40
|
+
const branchPath = resolveKbPath(workspaceRoot, branch);
|
|
41
|
+
if (fs.existsSync(branchPath)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Try to copy from the previously active branch if available
|
|
45
|
+
const previousBranch = activeBranchName;
|
|
46
|
+
const previousBranchPath = resolveKbPath(workspaceRoot, previousBranch);
|
|
47
|
+
if (previousBranch !== branch &&
|
|
48
|
+
previousBranch !== "develop" &&
|
|
49
|
+
fs.existsSync(previousBranchPath)) {
|
|
50
|
+
// Copy from previous branch for continuity
|
|
51
|
+
copyCleanSnapshot(previousBranchPath, branchPath);
|
|
52
|
+
debugLog(`[KIBI-MCP] Created branch KB for '${branch}' from '${previousBranch}'`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// No previous branch available - create empty KB
|
|
56
|
+
fs.mkdirSync(branchPath, { recursive: true });
|
|
57
|
+
debugLog(`[KIBI-MCP] Created empty branch KB for '${branch}'`);
|
|
58
|
+
}
|
|
59
|
+
export async function initiateGracefulShutdown(exitCode = 0) {
|
|
60
|
+
if (isShuttingDown) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
isShuttingDown = true;
|
|
64
|
+
debugLog(`[KIBI-MCP] Initiating graceful shutdown (exit code: ${exitCode})`);
|
|
65
|
+
// Wait for in-flight requests
|
|
66
|
+
if (inFlightRequests.size > 0) {
|
|
67
|
+
debugLog(`[KIBI-MCP] Waiting for ${inFlightRequests.size} in-flight requests to complete...`);
|
|
68
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
69
|
+
shutdownTimeout = setTimeout(() => {
|
|
70
|
+
reject(new Error("Shutdown timeout"));
|
|
71
|
+
}, 10000); // 10 second timeout
|
|
72
|
+
});
|
|
73
|
+
try {
|
|
74
|
+
await Promise.race([
|
|
75
|
+
Promise.allSettled(Array.from(inFlightRequests.values())),
|
|
76
|
+
timeoutPromise,
|
|
77
|
+
]);
|
|
78
|
+
debugLog("[KIBI-MCP] All in-flight requests completed");
|
|
79
|
+
}
|
|
80
|
+
catch (_error) {
|
|
81
|
+
console.error("[KIBI-MCP] Shutdown timeout reached, forcing exit");
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
if (shutdownTimeout) {
|
|
85
|
+
clearTimeout(shutdownTimeout);
|
|
86
|
+
shutdownTimeout = null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Cleanup Prolog process
|
|
91
|
+
if (prologProcess?.isRunning()) {
|
|
92
|
+
debugLog("[KIBI-MCP] Terminating Prolog process...");
|
|
93
|
+
try {
|
|
94
|
+
await prologProcess.terminate();
|
|
95
|
+
debugLog("[KIBI-MCP] Prolog process terminated");
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error("[KIBI-MCP] Error terminating Prolog:", error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Exit
|
|
102
|
+
process.exit(exitCode);
|
|
103
|
+
}
|
|
104
|
+
async function ensurePrologUnsafe() {
|
|
105
|
+
const workspaceRoot = resolveWorkspaceRoot();
|
|
106
|
+
// Determine target branch: respect KIBI_BRANCH override or resolve from git
|
|
107
|
+
const envBranch = process.env.KIBI_BRANCH?.trim();
|
|
108
|
+
let targetBranch;
|
|
109
|
+
if (envBranch) {
|
|
110
|
+
// KIBI_BRANCH override is set - use it without re-resolving from git
|
|
111
|
+
if (!isValidBranchName(envBranch)) {
|
|
112
|
+
throw new Error(`Invalid branch name from KIBI_BRANCH: '${envBranch}'`);
|
|
113
|
+
}
|
|
114
|
+
targetBranch = envBranch;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
// No override - resolve active branch from git (may change between requests)
|
|
118
|
+
const branchResult = resolveActiveBranch(workspaceRoot);
|
|
119
|
+
if ("error" in branchResult) {
|
|
120
|
+
const diagnostic = getBranchDiagnostic(undefined, branchResult.error);
|
|
121
|
+
console.error(`[KIBI-MCP] ${diagnostic}`);
|
|
122
|
+
throw new Error(`Failed to resolve active branch: ${branchResult.error}`);
|
|
123
|
+
}
|
|
124
|
+
targetBranch = branchResult.branch;
|
|
125
|
+
}
|
|
126
|
+
// Check if we need to switch branches
|
|
127
|
+
if (isInitialized && prologProcess?.isRunning()) {
|
|
128
|
+
if (targetBranch === activeBranchName) {
|
|
129
|
+
// Still on the same branch - return existing connection
|
|
130
|
+
return prologProcess;
|
|
131
|
+
}
|
|
132
|
+
// Branch changed - need to detach and re-attach
|
|
133
|
+
debugLog(`[KIBI-MCP] Branch changed: ${activeBranchName} -> ${targetBranch}`);
|
|
134
|
+
// Detach from old KB
|
|
135
|
+
const detachResult = await prologProcess.query("kb_detach");
|
|
136
|
+
if (!detachResult.success) {
|
|
137
|
+
debugLog(`[KIBI-MCP] Warning: failed to detach from old KB: ${detachResult.error || "Unknown error"}`);
|
|
138
|
+
// Continue anyway - we'll try to attach to the new KB
|
|
139
|
+
}
|
|
140
|
+
// Ensure new branch KB exists
|
|
141
|
+
ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
142
|
+
const newKbPath = resolveKbPath(workspaceRoot, targetBranch);
|
|
143
|
+
// Attach to new branch KB
|
|
144
|
+
const attachResult = await prologProcess.query(`kb_attach('${newKbPath}')`);
|
|
145
|
+
if (!attachResult.success) {
|
|
146
|
+
throw new Error(`Failed to attach to new branch KB: ${attachResult.error || "Unknown error"}`);
|
|
147
|
+
}
|
|
148
|
+
activeBranchName = targetBranch;
|
|
149
|
+
debugLog(`[KIBI-MCP] Re-attached to branch: ${targetBranch}`);
|
|
150
|
+
debugLog(`[KIBI-MCP] KB path: ${newKbPath}`);
|
|
151
|
+
return prologProcess;
|
|
152
|
+
}
|
|
153
|
+
// First initialization
|
|
154
|
+
debugLog("[KIBI-MCP] Initializing Prolog process...");
|
|
155
|
+
prologProcess = new PrologProcess({ timeout: 120000 });
|
|
156
|
+
await prologProcess.start();
|
|
157
|
+
// Startup debug: resolve which kibi-cli is being used and its version (best-effort).
|
|
158
|
+
// Gate all output under KIBI_MCP_DEBUG and write only to stderr via debugLog.
|
|
159
|
+
if (process.env.KIBI_MCP_DEBUG) {
|
|
160
|
+
try {
|
|
161
|
+
const req = createRequire(import.meta.url);
|
|
162
|
+
try {
|
|
163
|
+
const resolved = req.resolve("kibi-cli/prolog");
|
|
164
|
+
debugLog(`[KIBI-MCP] require.resolve('kibi-cli/prolog') -> ${resolved}`);
|
|
165
|
+
}
|
|
166
|
+
catch (resolveErr) {
|
|
167
|
+
debugLog("[KIBI-MCP] require.resolve('kibi-cli/prolog') failed:", resolveErr.message);
|
|
168
|
+
}
|
|
169
|
+
// Try to read package.json for kibi-cli to get version. This may fail if
|
|
170
|
+
// the package uses exports blocking package.json access — log explicit failure.
|
|
171
|
+
try {
|
|
172
|
+
// prefer direct package.json require; createRequire makes this ESM-friendly
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
174
|
+
const pkg = req("kibi-cli/package.json");
|
|
175
|
+
if (pkg && typeof pkg.version === "string") {
|
|
176
|
+
debugLog(`[KIBI-MCP] kibi-cli version: ${pkg.version}`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
debugLog("[KIBI-MCP] kibi-cli package.json read but no version field");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (pkgErr) {
|
|
183
|
+
debugLog("[KIBI-MCP] Failed to read kibi-cli package.json (exports may restrict access):", pkgErr.message);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
debugLog("[KIBI-MCP] Failed to create require() for debug lookup:", err.message);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
debugLog("[KIBI-MCP] Branch selection:");
|
|
191
|
+
debugLog(`[KIBI-MCP] KIBI_BRANCH env: ${process.env.KIBI_BRANCH || "not set"}`);
|
|
192
|
+
debugLog(`[KIBI-MCP] Resolved branch: ${targetBranch}`);
|
|
193
|
+
activeBranchName = targetBranch;
|
|
194
|
+
ensureBranchKbExists(workspaceRoot, targetBranch);
|
|
195
|
+
const kbPath = resolveKbPath(workspaceRoot, targetBranch);
|
|
196
|
+
const attachResult = await prologProcess.query(`kb_attach('${kbPath}')`);
|
|
197
|
+
if (!attachResult.success) {
|
|
198
|
+
throw new Error(`Failed to attach KB: ${attachResult.error || "Unknown error"}`);
|
|
199
|
+
}
|
|
200
|
+
isInitialized = true;
|
|
201
|
+
debugLog(`[KIBI-MCP] Prolog process started (PID: ${prologProcess.getPid()})`);
|
|
202
|
+
debugLog(`[KIBI-MCP] KB attached: ${kbPath}`);
|
|
203
|
+
return prologProcess;
|
|
204
|
+
}
|
|
205
|
+
export async function ensureProlog() {
|
|
206
|
+
const previous = ensurePrologTail;
|
|
207
|
+
let release;
|
|
208
|
+
ensurePrologTail = new Promise((resolve) => {
|
|
209
|
+
release = resolve;
|
|
210
|
+
});
|
|
211
|
+
await previous;
|
|
212
|
+
try {
|
|
213
|
+
return await ensurePrologUnsafe();
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
release();
|
|
217
|
+
}
|
|
218
|
+
}
|