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.
@@ -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 { execSync } from "node:child_process";
46
19
  import * as fs from "node:fs";
47
20
  import * as path from "node:path";
@@ -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 { existsSync } from "node:fs";
46
19
  import { createRequire } from "node:module";
47
20
  import * as path from "node:path";
@@ -90,6 +63,9 @@ export async function handleKbCheck(prolog, args) {
90
63
  "no-cycles",
91
64
  "required-fields",
92
65
  "symbol-coverage",
66
+ "symbol-traceability",
67
+ "deprecated-adr-no-successor",
68
+ "domain-contradictions",
93
69
  ];
94
70
  const rulesToRun = rules && rules.length > 0 ? rules : allRules;
95
71
  const rulesAllowlist = new Set(rulesToRun);
@@ -137,6 +113,9 @@ export async function handleKbCheck(prolog, args) {
137
113
  if (rulesToRun.includes("symbol-coverage")) {
138
114
  violations.push(...(await checkSymbolCoverage(prolog)));
139
115
  }
116
+ if (rulesToRun.includes("symbol-traceability")) {
117
+ violations.push(...(await checkSymbolTraceability(prolog, false)));
118
+ }
140
119
  const diagnostics = violations.map((v) => ({
141
120
  category: "SYNC_ERROR",
142
121
  severity: "error",
@@ -232,6 +211,32 @@ async function runAggregatedChecks(prolog, rulesAllowlist) {
232
211
  }
233
212
  return violations;
234
213
  }
214
+ async function checkSymbolTraceability(prolog, requireAdr) {
215
+ const violations = [];
216
+ const requireAdrStr = requireAdr ? "true" : "false";
217
+ const result = await prolog.query(`findall(violation(Rule, EntityId, Desc, Sugg, Src), checks:symbol_traceability_violation(${requireAdrStr}, violation(Rule, EntityId, Desc, Sugg, Src)), Violations)`);
218
+ if (!result.success || !result.bindings.Violations) {
219
+ return violations;
220
+ }
221
+ const violationsStr = result.bindings.Violations;
222
+ if (violationsStr && violationsStr !== "[]") {
223
+ const violationRegex = /violation\(([^,]+),'?([^',]+)'?,([^,]+),([^,]+),'?([^']*)'?\)/g;
224
+ let match;
225
+ do {
226
+ match = violationRegex.exec(violationsStr);
227
+ if (match) {
228
+ violations.push({
229
+ rule: match[1].trim().replace(/^'|'$/g, ""),
230
+ entityId: match[2].trim(),
231
+ description: match[3].trim().replace(/^"|"$/g, ""),
232
+ suggestion: match[4].trim().replace(/^"|"$/g, ""),
233
+ source: match[5].trim() || undefined,
234
+ });
235
+ }
236
+ } while (match);
237
+ }
238
+ return violations;
239
+ }
235
240
  async function checkMustPriorityCoverage(prolog) {
236
241
  const violations = [];
237
242
  const gapsResult = await prolog.query("setof([Req,Reason], coverage_gap(Req, Reason), Rows)");
@@ -1,20 +1,4 @@
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
- */
1
+ import { parseEntityFromList, parseListOfLists, } from "kibi-cli/prolog/codec";
18
2
  export async function handleKbContext(prolog, args) {
19
3
  const { sourceFile } = args;
20
4
  try {
@@ -78,193 +62,3 @@ export async function handleKbContext(prolog, args) {
78
62
  throw new Error(`Context query failed: ${message}`);
79
63
  }
80
64
  }
81
- function parseListOfLists(listStr) {
82
- const cleaned = listStr.trim().replace(/^\[/, "").replace(/\]$/, "");
83
- if (cleaned === "") {
84
- return [];
85
- }
86
- const results = [];
87
- let depth = 0;
88
- let current = "";
89
- let currentList = [];
90
- for (let i = 0; i < cleaned.length; i++) {
91
- const char = cleaned[i];
92
- if (char === "[") {
93
- depth++;
94
- if (depth > 1)
95
- current += char;
96
- }
97
- else if (char === "]") {
98
- depth--;
99
- if (depth === 0) {
100
- if (current) {
101
- currentList.push(current.trim());
102
- current = "";
103
- }
104
- if (currentList.length > 0) {
105
- results.push(currentList);
106
- currentList = [];
107
- }
108
- }
109
- else {
110
- current += char;
111
- }
112
- }
113
- else if (char === "," && depth === 1) {
114
- if (current) {
115
- currentList.push(current.trim());
116
- current = "";
117
- }
118
- }
119
- else if (char === "," && depth === 0) {
120
- }
121
- else {
122
- current += char;
123
- }
124
- }
125
- return results;
126
- }
127
- function parseEntityFromList(data) {
128
- if (data.length < 3) {
129
- return {};
130
- }
131
- const id = data[0].trim();
132
- const type = data[1].trim();
133
- const propsStr = data[2].trim();
134
- const props = parsePropertyList(propsStr);
135
- return { ...props, id: normalizeEntityId(stripOuterQuotes(id)), type };
136
- }
137
- function parsePropertyList(propsStr) {
138
- const props = {};
139
- let cleaned = propsStr.trim();
140
- if (cleaned.startsWith("[")) {
141
- cleaned = cleaned.substring(1);
142
- }
143
- if (cleaned.endsWith("]")) {
144
- cleaned = cleaned.substring(0, cleaned.length - 1);
145
- }
146
- const pairs = splitTopLevel(cleaned, ",");
147
- for (const pair of pairs) {
148
- const eqIndex = pair.indexOf("=");
149
- if (eqIndex === -1)
150
- continue;
151
- const key = pair.substring(0, eqIndex).trim();
152
- const value = pair.substring(eqIndex + 1).trim();
153
- if (key === "..." || value === "..." || value === "...|...") {
154
- continue;
155
- }
156
- const parsed = parsePrologValue(value);
157
- props[key] = parsed;
158
- }
159
- return props;
160
- }
161
- function parsePrologValue(valueInput) {
162
- const value = valueInput.trim();
163
- if (value.startsWith("^^(")) {
164
- const innerStart = value.indexOf("(") + 1;
165
- let depth = 1;
166
- let innerEnd = innerStart;
167
- for (let i = innerStart; i < value.length; i++) {
168
- if (value[i] === "(")
169
- depth++;
170
- if (value[i] === ")") {
171
- depth--;
172
- if (depth === 0) {
173
- innerEnd = i;
174
- break;
175
- }
176
- }
177
- }
178
- const innerContent = value.substring(innerStart, innerEnd);
179
- const parts = splitTopLevel(innerContent, ",");
180
- if (parts.length >= 2) {
181
- let literalValue = parts[0].trim();
182
- if (literalValue.startsWith('"') && literalValue.endsWith('"')) {
183
- literalValue = literalValue.substring(1, literalValue.length - 1);
184
- }
185
- if (literalValue.startsWith("[") && literalValue.endsWith("]")) {
186
- const listContent = literalValue.substring(1, literalValue.length - 1);
187
- if (listContent === "") {
188
- return [];
189
- }
190
- return listContent.split(",").map((item) => item.trim());
191
- }
192
- return literalValue;
193
- }
194
- }
195
- if (value.startsWith("file:///")) {
196
- const lastSlash = value.lastIndexOf("/");
197
- if (lastSlash !== -1) {
198
- return value.substring(lastSlash + 1);
199
- }
200
- return value;
201
- }
202
- if (value.startsWith('"') && value.endsWith('"')) {
203
- return value.substring(1, value.length - 1);
204
- }
205
- if (value.startsWith("'") && value.endsWith("'")) {
206
- return value.substring(1, value.length - 1);
207
- }
208
- if (value.startsWith("[") && value.endsWith("]")) {
209
- const listContent = value.substring(1, value.length - 1);
210
- if (listContent === "") {
211
- return [];
212
- }
213
- const items = listContent.split(",").map((item) => {
214
- return parsePrologValue(item.trim());
215
- });
216
- return items;
217
- }
218
- return value;
219
- }
220
- function splitTopLevel(str, delimiter) {
221
- const results = [];
222
- let current = "";
223
- let depth = 0;
224
- let inQuotes = false;
225
- for (let i = 0; i < str.length; i++) {
226
- const char = str[i];
227
- const prevChar = i > 0 ? str[i - 1] : "";
228
- if (char === '"' && prevChar !== "\\") {
229
- inQuotes = !inQuotes;
230
- current += char;
231
- }
232
- else if (!inQuotes && (char === "[" || char === "(")) {
233
- depth++;
234
- current += char;
235
- }
236
- else if (!inQuotes && (char === "]" || char === ")")) {
237
- depth--;
238
- current += char;
239
- }
240
- else if (!inQuotes && depth === 0 && char === delimiter) {
241
- if (current) {
242
- results.push(current);
243
- current = "";
244
- }
245
- }
246
- else {
247
- current += char;
248
- }
249
- }
250
- if (current) {
251
- results.push(current);
252
- }
253
- return results;
254
- }
255
- function stripOuterQuotes(value) {
256
- if (value.startsWith("'") && value.endsWith("'")) {
257
- return value.slice(1, -1);
258
- }
259
- if (value.startsWith('"') && value.endsWith('"')) {
260
- return value.slice(1, -1);
261
- }
262
- return value;
263
- }
264
- function normalizeEntityId(value) {
265
- if (!value.startsWith("file:///")) {
266
- return value;
267
- }
268
- const idx = value.lastIndexOf("/");
269
- return idx === -1 ? value : value.slice(idx + 1);
270
- }
@@ -1,20 +1,3 @@
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
1
  import { parseAtomList, parsePairList } from "./prolog-list.js";
19
2
  export async function handleKbCoverageReport(prolog, args) {
20
3
  const requested = args.type ?? "all";
@@ -1,23 +1,4 @@
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
- function escapeAtom(value) {
19
- return value.replace(/'/g, "''");
20
- }
1
+ import { escapeAtom } from "kibi-cli/prolog/codec";
21
2
  /**
22
3
  * Handle kb.delete tool calls
23
4
  * Prevents deletion of entities with dependents (referential integrity)
@@ -1,20 +1,4 @@
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
- */
1
+ import { escapeAtom } from "kibi-cli/prolog/codec";
18
2
  import { parseAtomList, parsePairList } from "./prolog-list.js";
19
3
  const RULES = [
20
4
  "transitively_implements",
@@ -234,9 +218,6 @@ function makeConjunction(parts) {
234
218
  }
235
219
  return `, ${filtered.join(", ")}`;
236
220
  }
237
- function escapeAtom(value) {
238
- return value.replace(/'/g, "\\'");
239
- }
240
221
  async function deriveCurrentAdr(prolog) {
241
222
  // Query for all current ADRs and their titles
242
223
  const result = await prolog.query("setof([Id,TitleAtom], (kb_entity(Id, adr, Props), memberchk(title=Title, Props), normalize_term_atom(Title, TitleAtom), current_adr(Id)), Rows)");
@@ -1,20 +1,4 @@
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
- */
1
+ import { escapeAtom } from "kibi-cli/prolog/codec";
18
2
  import { parseAtomList } from "./prolog-list.js";
19
3
  export async function handleKbImpact(prolog, args) {
20
4
  if (!args.entity || typeof args.entity !== "string") {
@@ -65,6 +49,3 @@ async function getEntityType(prolog, id) {
65
49
  }
66
50
  return result.bindings.Type;
67
51
  }
68
- function escapeAtom(value) {
69
- return value.replace(/'/g, "\\'");
70
- }
@@ -1,20 +1,3 @@
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
1
  /**
19
2
  * Handle kb_list_entity_types tool calls
20
3
  * Returns the static list of supported KB entity type names (req, scenario, test, adr, flag, event, symbol, fact).
@@ -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
  export function parseAtomList(raw) {
46
19
  const trimmed = raw.trim();
47
20
  if (trimmed === "[]" || trimmed.length === 0) {
@@ -1,20 +1,3 @@
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
1
  const VALID_REL_TYPES = [
19
2
  "depends_on",
20
3
  "specified_by",