kibi-cli 0.2.8 → 0.4.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/cli.d.ts +4 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +91 -14
- package/dist/commands/check.d.ts +5 -8
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +41 -62
- package/dist/commands/coverage.d.ts +12 -0
- package/dist/commands/coverage.d.ts.map +1 -0
- package/dist/commands/coverage.js +24 -0
- package/dist/commands/discovery-shared.d.ts +11 -0
- package/dist/commands/discovery-shared.d.ts.map +1 -0
- package/dist/commands/discovery-shared.js +281 -0
- package/dist/commands/doctor.d.ts +3 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +12 -13
- package/dist/commands/gaps.d.ts +12 -0
- package/dist/commands/gaps.d.ts.map +1 -0
- package/dist/commands/gaps.js +28 -0
- package/dist/commands/graph.d.ts +13 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/commands/graph.js +35 -0
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +4 -3
- package/dist/commands/query.d.ts +3 -1
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +9 -20
- package/dist/commands/search.d.ts +9 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +38 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +9 -0
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +79 -12
- package/dist/commands/sync.d.ts +4 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +79 -31
- package/dist/extractors/markdown.d.ts +17 -0
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +104 -14
- package/dist/prolog/codec.d.ts +32 -5
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +95 -58
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +12 -2
- package/dist/public/check-types.d.ts +7 -0
- package/dist/public/check-types.d.ts.map +1 -0
- package/dist/public/check-types.js +18 -0
- package/dist/public/schemas/entity.d.ts +68 -0
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +52 -0
- package/dist/relationships/shards.d.ts.map +1 -1
- package/dist/relationships/shards.js +6 -3
- package/dist/schemas/entity.schema.json +120 -0
- package/dist/search-ranking.d.ts +9 -0
- package/dist/search-ranking.d.ts.map +1 -0
- package/dist/search-ranking.js +149 -0
- package/dist/traceability/symbol-extract.d.ts.map +1 -1
- package/dist/traceability/symbol-extract.js +16 -8
- package/dist/types/entities.d.ts +19 -1
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/utils/prolog-cleanup.d.ts +10 -0
- package/dist/utils/prolog-cleanup.d.ts.map +1 -0
- package/dist/utils/prolog-cleanup.js +39 -0
- package/dist/utils/rule-registry.d.ts +8 -0
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/dist/utils/rule-registry.js +14 -12
- package/package.json +10 -2
- package/schema/config.json +7 -1
- package/schema/entities.pl +18 -0
- package/schema/validation.pl +115 -4
- package/src/public/check-types.ts +37 -0
- package/src/public/schemas/entity.ts +58 -0
- package/src/schemas/entity.schema.json +56 -1
- package/dist/kb/target-resolver.d.ts +0 -80
- package/dist/kb/target-resolver.d.ts.map +0 -1
- package/dist/kb/target-resolver.js +0 -313
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
// implements REQ-mcp-search-discovery, REQ-002, REQ-003
|
|
5
|
+
export async function rankEntities(entities, query, workspaceRoot) {
|
|
6
|
+
const matches = [];
|
|
7
|
+
for (const entity of entities) {
|
|
8
|
+
const match = await rankEntity(entity, query, workspaceRoot);
|
|
9
|
+
if (match) {
|
|
10
|
+
matches.push(match);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
matches.sort((left, right) => {
|
|
14
|
+
if (right.score !== left.score) {
|
|
15
|
+
return right.score - left.score;
|
|
16
|
+
}
|
|
17
|
+
const leftType = String(left.entity.type ?? "");
|
|
18
|
+
const rightType = String(right.entity.type ?? "");
|
|
19
|
+
if (leftType !== rightType) {
|
|
20
|
+
return leftType.localeCompare(rightType);
|
|
21
|
+
}
|
|
22
|
+
return String(left.entity.id ?? "").localeCompare(String(right.entity.id ?? ""));
|
|
23
|
+
});
|
|
24
|
+
return matches;
|
|
25
|
+
}
|
|
26
|
+
async function rankEntity(entity, query, workspaceRoot) {
|
|
27
|
+
const normalizedQuery = normalize(query);
|
|
28
|
+
const tokens = normalizedQuery.split(/\s+/).filter(Boolean);
|
|
29
|
+
const reasons = [];
|
|
30
|
+
let score = 0;
|
|
31
|
+
const id = String(entity.id ?? "");
|
|
32
|
+
const title = String(entity.title ?? "");
|
|
33
|
+
const type = String(entity.type ?? "");
|
|
34
|
+
const source = String(entity.source ?? "");
|
|
35
|
+
const owner = String(entity.owner ?? "");
|
|
36
|
+
const priority = String(entity.priority ?? "");
|
|
37
|
+
const severity = String(entity.severity ?? "");
|
|
38
|
+
const tags = Array.isArray(entity.tags)
|
|
39
|
+
? entity.tags.map((tag) => String(tag))
|
|
40
|
+
: [];
|
|
41
|
+
const normalizedTitle = normalize(title);
|
|
42
|
+
const normalizedId = normalize(id);
|
|
43
|
+
if (normalizedTitle === normalizedQuery) {
|
|
44
|
+
score += 100;
|
|
45
|
+
reasons.push("exact title match");
|
|
46
|
+
}
|
|
47
|
+
else if (normalizedTitle.includes(normalizedQuery)) {
|
|
48
|
+
score += 60;
|
|
49
|
+
reasons.push("title phrase match");
|
|
50
|
+
}
|
|
51
|
+
if (normalizedId === normalizedQuery) {
|
|
52
|
+
score += 90;
|
|
53
|
+
reasons.push("exact ID match");
|
|
54
|
+
}
|
|
55
|
+
else if (normalizedId.includes(normalizedQuery)) {
|
|
56
|
+
score += 55;
|
|
57
|
+
reasons.push("ID match");
|
|
58
|
+
}
|
|
59
|
+
const metadataFields = [type, source, owner, priority, severity];
|
|
60
|
+
const metadataMatched = metadataFields.some((field) => normalize(field).includes(normalizedQuery));
|
|
61
|
+
if (metadataMatched) {
|
|
62
|
+
score += 20;
|
|
63
|
+
reasons.push("metadata match");
|
|
64
|
+
}
|
|
65
|
+
const matchingTags = tags.filter((tag) => normalize(tag).includes(normalizedQuery));
|
|
66
|
+
if (matchingTags.length > 0) {
|
|
67
|
+
score += 30;
|
|
68
|
+
reasons.push("tag match");
|
|
69
|
+
}
|
|
70
|
+
const titleTokenMatches = countTokenMatches(normalizedTitle, tokens);
|
|
71
|
+
if (titleTokenMatches > 0) {
|
|
72
|
+
score += titleTokenMatches * 8;
|
|
73
|
+
reasons.push("title token coverage");
|
|
74
|
+
}
|
|
75
|
+
const bodyText = await loadMarkdownBody(source, workspaceRoot);
|
|
76
|
+
let snippet;
|
|
77
|
+
if (bodyText) {
|
|
78
|
+
const normalizedBody = normalize(bodyText);
|
|
79
|
+
if (normalizedBody.includes(normalizedQuery)) {
|
|
80
|
+
score += 15;
|
|
81
|
+
reasons.push("markdown body match");
|
|
82
|
+
snippet = buildSnippet(bodyText, query);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
const bodyTokenMatches = countTokenMatches(normalizedBody, tokens);
|
|
86
|
+
if (bodyTokenMatches > 0) {
|
|
87
|
+
score += bodyTokenMatches * 3;
|
|
88
|
+
reasons.push("markdown body token coverage");
|
|
89
|
+
snippet = buildSnippet(bodyText, query);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (score === 0) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
entity,
|
|
98
|
+
score,
|
|
99
|
+
reasons: Array.from(new Set(reasons)),
|
|
100
|
+
snippet,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export async function loadMarkdownBody(
|
|
104
|
+
// implements REQ-007, REQ-mcp-search-discovery
|
|
105
|
+
source, workspaceRoot) {
|
|
106
|
+
if (!source) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
const normalizedSource = source.split("#", 1)[0]?.trim() ?? "";
|
|
110
|
+
if (!normalizedSource.endsWith(".md")) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
// Resolve to absolute path; relative paths are resolved against workspaceRoot.
|
|
114
|
+
const resolved = path.resolve(path.isAbsolute(normalizedSource)
|
|
115
|
+
? normalizedSource
|
|
116
|
+
: path.join(workspaceRoot, normalizedSource));
|
|
117
|
+
// Reject paths that escape the workspace root to prevent path traversal.
|
|
118
|
+
const normalizedRoot = path.resolve(workspaceRoot);
|
|
119
|
+
if (!resolved.startsWith(normalizedRoot + path.sep)) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const fileContent = await fs.readFile(resolved, "utf8");
|
|
124
|
+
return matter(fileContent).content;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function normalize(value) {
|
|
131
|
+
return value.trim().toLowerCase();
|
|
132
|
+
}
|
|
133
|
+
function countTokenMatches(haystack, tokens) {
|
|
134
|
+
return tokens.filter((token) => haystack.includes(token)).length;
|
|
135
|
+
}
|
|
136
|
+
function buildSnippet(bodyText, query) {
|
|
137
|
+
const lines = bodyText
|
|
138
|
+
.split(/\r?\n/)
|
|
139
|
+
.map((line) => line.trim())
|
|
140
|
+
.filter(Boolean);
|
|
141
|
+
const normalizedQuery = normalize(query);
|
|
142
|
+
const matchedLine = lines.find((line) => normalize(line).includes(normalizedQuery)) ?? lines[0];
|
|
143
|
+
if (!matchedLine) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
return matchedLine.length > 160
|
|
147
|
+
? `${matchedLine.slice(0, 157)}...`
|
|
148
|
+
: matchedLine;
|
|
149
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IAC7D,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AA4D9D,wBAAgB,4BAA4B,
|
|
1
|
+
{"version":3,"file":"symbol-extract.d.ts","sourceRoot":"","sources":["../../src/traceability/symbol-extract.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7D,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;IAC7D,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,MAAM,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AA4D9D,wBAAgB,4BAA4B,CAE1C,UAAU,EAAE,UAAU,EACtB,cAAc,CAAC,EAAE,cAAc,GAC9B,eAAe,EAAE,CAuNnB"}
|
|
@@ -45,7 +45,9 @@ function parseReqDirectives(text) {
|
|
|
45
45
|
function rangesIntersect(aStart, aEnd, bStart, bEnd) {
|
|
46
46
|
return aStart <= bEnd && bStart <= aEnd;
|
|
47
47
|
}
|
|
48
|
-
export function extractSymbolsFromStagedFile(
|
|
48
|
+
export function extractSymbolsFromStagedFile(
|
|
49
|
+
// implements REQ-008
|
|
50
|
+
stagedFile, manifestLookup) {
|
|
49
51
|
const content = stagedFile.content ?? "";
|
|
50
52
|
const sha = computeContentSha(`${content}|${stagedFile.path}`);
|
|
51
53
|
// TTL cache lookup
|
|
@@ -108,7 +110,9 @@ export function extractSymbolsFromStagedFile(stagedFile, manifestLookup) {
|
|
|
108
110
|
reqLinks: mergedReqLinks,
|
|
109
111
|
});
|
|
110
112
|
}
|
|
111
|
-
catch {
|
|
113
|
+
catch {
|
|
114
|
+
// skip: individual declaration extraction may fail on malformed AST nodes
|
|
115
|
+
}
|
|
112
116
|
}
|
|
113
117
|
// Classes
|
|
114
118
|
for (const cls of sf.getClasses()) {
|
|
@@ -139,7 +143,9 @@ export function extractSymbolsFromStagedFile(stagedFile, manifestLookup) {
|
|
|
139
143
|
reqLinks: mergedReqLinks,
|
|
140
144
|
});
|
|
141
145
|
}
|
|
142
|
-
catch {
|
|
146
|
+
catch {
|
|
147
|
+
// skip: individual declaration extraction may fail on malformed AST nodes
|
|
148
|
+
}
|
|
143
149
|
}
|
|
144
150
|
// Enums
|
|
145
151
|
for (const en of sf.getEnums()) {
|
|
@@ -167,7 +173,9 @@ export function extractSymbolsFromStagedFile(stagedFile, manifestLookup) {
|
|
|
167
173
|
reqLinks: mergedReqLinks,
|
|
168
174
|
});
|
|
169
175
|
}
|
|
170
|
-
catch {
|
|
176
|
+
catch {
|
|
177
|
+
// skip: individual declaration extraction may fail on malformed AST nodes
|
|
178
|
+
}
|
|
171
179
|
}
|
|
172
180
|
// Variable statements (exported)
|
|
173
181
|
for (const vs of sf.getVariableStatements()) {
|
|
@@ -196,7 +204,9 @@ export function extractSymbolsFromStagedFile(stagedFile, manifestLookup) {
|
|
|
196
204
|
reqLinks: mergedReqLinks,
|
|
197
205
|
});
|
|
198
206
|
}
|
|
199
|
-
catch {
|
|
207
|
+
catch {
|
|
208
|
+
// skip: individual declaration extraction may fail on malformed AST nodes
|
|
209
|
+
}
|
|
200
210
|
}
|
|
201
211
|
}
|
|
202
212
|
// Filter to only include symbols that intersect with at least one hunk
|
|
@@ -219,9 +229,7 @@ function resolveSymbolId(filePath, name, manifestLookup) {
|
|
|
219
229
|
// First, check the provided manifest lookup if available
|
|
220
230
|
if (manifestLookup) {
|
|
221
231
|
// Normalize the source file path for consistent lookup
|
|
222
|
-
const normalizedSource = filePath
|
|
223
|
-
? filePath
|
|
224
|
-
: `${filePath}`;
|
|
232
|
+
const normalizedSource = filePath;
|
|
225
233
|
const lookupKey = `${normalizedSource}:${name}`;
|
|
226
234
|
const entry = manifestLookup.get(lookupKey);
|
|
227
235
|
if (entry) {
|
package/dist/types/entities.d.ts
CHANGED
|
@@ -12,6 +12,24 @@ export interface BaseEntity {
|
|
|
12
12
|
links?: string[];
|
|
13
13
|
text_ref?: string;
|
|
14
14
|
}
|
|
15
|
+
export interface FactFields {
|
|
16
|
+
fact_kind?: "subject" | "property_value" | "observation" | "meta";
|
|
17
|
+
subject_key?: string;
|
|
18
|
+
property_key?: string;
|
|
19
|
+
operator?: "eq" | "neq" | "lt" | "lte" | "gt" | "gte";
|
|
20
|
+
value_type?: "string" | "int" | "number" | "bool";
|
|
21
|
+
value_string?: string;
|
|
22
|
+
value_int?: number;
|
|
23
|
+
value_number?: number;
|
|
24
|
+
value_bool?: boolean;
|
|
25
|
+
unit?: string;
|
|
26
|
+
scope?: string;
|
|
27
|
+
polarity?: "require" | "forbid";
|
|
28
|
+
closed_world?: boolean;
|
|
29
|
+
valid_from?: string;
|
|
30
|
+
valid_to?: string;
|
|
31
|
+
canonical_key?: string;
|
|
32
|
+
}
|
|
15
33
|
export type Requirement = BaseEntity & {
|
|
16
34
|
type: "req";
|
|
17
35
|
};
|
|
@@ -33,7 +51,7 @@ export type Event = BaseEntity & {
|
|
|
33
51
|
export type Symbol = BaseEntity & {
|
|
34
52
|
type: "symbol";
|
|
35
53
|
};
|
|
36
|
-
export type Fact = BaseEntity & {
|
|
54
|
+
export type Fact = BaseEntity & FactFields & {
|
|
37
55
|
type: "fact";
|
|
38
56
|
};
|
|
39
57
|
export type Entity = Requirement | Scenario | TestEntity | ADR | Flag | Event | Symbol | Fact;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/types/entities.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACvD,MAAM,MAAM,GAAG,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAC/C,MAAM,MAAM,IAAI,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjD,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AACnD,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AACrD,MAAM,MAAM,IAAI,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"entities.d.ts","sourceRoot":"","sources":["../../src/types/entities.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,UAAU;IACzB,SAAS,CAAC,EAAE,SAAS,GAAG,gBAAgB,GAAG,aAAa,GAAG,MAAM,CAAC;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,CAAC;IACtD,UAAU,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAClD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAChC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AACvD,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AACzD,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACvD,MAAM,MAAM,GAAG,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC;AAC/C,MAAM,MAAM,IAAI,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AACjD,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AACnD,MAAM,MAAM,MAAM,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AACrD,MAAM,MAAM,IAAI,GAAG,UAAU,GAAG,UAAU,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,MAAM,GACd,WAAW,GACX,QAAQ,GACR,UAAU,GACV,GAAG,GACH,IAAI,GACJ,KAAK,GACL,MAAM,GACN,IAAI,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort Prolog process cleanup.
|
|
3
|
+
* Detaches the KB and terminates the process, swallowing any errors.
|
|
4
|
+
* Safe to call in finally blocks where the process may already be dead.
|
|
5
|
+
*/
|
|
6
|
+
export declare function safeCleanupProlog(prolog: {
|
|
7
|
+
query: (q: string) => Promise<unknown>;
|
|
8
|
+
terminate: () => Promise<void>;
|
|
9
|
+
} | null | undefined): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=prolog-cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prolog-cleanup.d.ts","sourceRoot":"","sources":["../../src/utils/prolog-cleanup.ts"],"names":[],"mappings":"AAkBA;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EACF;IAAE,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAC1E,IAAI,GACJ,SAAS,GACZ,OAAO,CAAC,IAAI,CAAC,CAaf"}
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
/**
|
|
19
|
+
* Best-effort Prolog process cleanup.
|
|
20
|
+
* Detaches the KB and terminates the process, swallowing any errors.
|
|
21
|
+
* Safe to call in finally blocks where the process may already be dead.
|
|
22
|
+
*/
|
|
23
|
+
export async function safeCleanupProlog(prolog) {
|
|
24
|
+
// implements REQ-003
|
|
25
|
+
if (!prolog)
|
|
26
|
+
return;
|
|
27
|
+
try {
|
|
28
|
+
await prolog.query("kb_detach");
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// best-effort: process may already be dead or KB was never attached
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
await prolog.terminate();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// best-effort: process may already be dead
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -11,6 +11,14 @@ export interface RuleDefinition {
|
|
|
11
11
|
export interface SymbolTraceabilityOptions {
|
|
12
12
|
requireAdr: boolean;
|
|
13
13
|
}
|
|
14
|
+
/** A single KB check violation. */
|
|
15
|
+
export interface Violation {
|
|
16
|
+
rule: string;
|
|
17
|
+
entityId: string;
|
|
18
|
+
description: string;
|
|
19
|
+
suggestion?: string;
|
|
20
|
+
source?: string;
|
|
21
|
+
}
|
|
14
22
|
export interface ChecksConfig {
|
|
15
23
|
rules: Record<string, boolean>;
|
|
16
24
|
symbolTraceability: SymbolTraceabilityOptions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,
|
|
1
|
+
{"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,EA8DjC,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,UAAU,aAAoC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF;;;;;GAKG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,GAChB,GAAG,CAAC,MAAM,CAAC,CAsBb;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,YAAY,CAWd"}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
* When adding a new rule, add it here.
|
|
21
21
|
*/
|
|
22
22
|
export const RULES = [
|
|
23
|
+
// implements REQ-006
|
|
23
24
|
{
|
|
24
25
|
name: "must-priority-coverage",
|
|
25
26
|
description: "Every must-priority requirement must have a scenario and a test",
|
|
@@ -68,6 +69,12 @@ export const RULES = [
|
|
|
68
69
|
defaultEnabled: true,
|
|
69
70
|
category: "integrity",
|
|
70
71
|
},
|
|
72
|
+
{
|
|
73
|
+
name: "strict-fact-shape",
|
|
74
|
+
description: "Detect malformed strict facts (facts with fact_kind that are missing required fields)",
|
|
75
|
+
defaultEnabled: false,
|
|
76
|
+
category: "integrity",
|
|
77
|
+
},
|
|
71
78
|
];
|
|
72
79
|
/**
|
|
73
80
|
* Set of all rule names for quick lookups.
|
|
@@ -88,7 +95,14 @@ export const DEFAULT_CHECKS_CONFIG = {
|
|
|
88
95
|
* @param cliRules Optional comma-separated list from --rules CLI flag
|
|
89
96
|
* @returns Set of rule names that should run
|
|
90
97
|
*/
|
|
98
|
+
// implements REQ-006
|
|
91
99
|
export function getEffectiveRules(configRules, cliRules) {
|
|
100
|
+
if (cliRules) {
|
|
101
|
+
return new Set(cliRules
|
|
102
|
+
.split(",")
|
|
103
|
+
.map((s) => s.trim())
|
|
104
|
+
.filter((s) => RULE_NAMES.has(s)));
|
|
105
|
+
}
|
|
92
106
|
// Start with all known rules
|
|
93
107
|
const effective = new Set();
|
|
94
108
|
for (const rule of RULES) {
|
|
@@ -98,18 +112,6 @@ export function getEffectiveRules(configRules, cliRules) {
|
|
|
98
112
|
effective.add(rule.name);
|
|
99
113
|
}
|
|
100
114
|
}
|
|
101
|
-
// CLI --rules is an intersection filter (narrows to specified rules)
|
|
102
|
-
if (cliRules) {
|
|
103
|
-
const allowed = new Set(cliRules
|
|
104
|
-
.split(",")
|
|
105
|
-
.map((s) => s.trim())
|
|
106
|
-
.filter((s) => RULE_NAMES.has(s)));
|
|
107
|
-
for (const rule of effective) {
|
|
108
|
-
if (!allowed.has(rule)) {
|
|
109
|
-
effective.delete(rule);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
115
|
return effective;
|
|
114
116
|
}
|
|
115
117
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Kibi CLI for knowledge base management",
|
|
6
6
|
"engines": {
|
|
@@ -58,6 +58,14 @@
|
|
|
58
58
|
"./diagnostics": {
|
|
59
59
|
"types": "./dist/diagnostics.d.ts",
|
|
60
60
|
"default": "./dist/diagnostics.js"
|
|
61
|
+
},
|
|
62
|
+
"./search-ranking": {
|
|
63
|
+
"types": "./dist/search-ranking.d.ts",
|
|
64
|
+
"default": "./dist/search-ranking.js"
|
|
65
|
+
},
|
|
66
|
+
"./public/check-types": {
|
|
67
|
+
"types": "./dist/public/check-types.d.ts",
|
|
68
|
+
"default": "./dist/public/check-types.js"
|
|
61
69
|
}
|
|
62
70
|
},
|
|
63
71
|
"types": "./dist/cli.d.ts",
|
|
@@ -68,7 +76,7 @@
|
|
|
68
76
|
"fast-glob": "^3.2.12",
|
|
69
77
|
"gray-matter": "^4.0.3",
|
|
70
78
|
"js-yaml": "^4.1.0",
|
|
71
|
-
"kibi-core": "^0.
|
|
79
|
+
"kibi-core": "^0.3.0",
|
|
72
80
|
"ts-morph": "^23.0.0"
|
|
73
81
|
},
|
|
74
82
|
"devDependencies": {
|
package/schema/config.json
CHANGED
|
@@ -108,6 +108,11 @@
|
|
|
108
108
|
"type": "boolean",
|
|
109
109
|
"description": "Detect contradictions between requirements constraining the same fact",
|
|
110
110
|
"default": true
|
|
111
|
+
},
|
|
112
|
+
"strict-fact-shape": {
|
|
113
|
+
"type": "boolean",
|
|
114
|
+
"description": "Detect malformed strict facts (facts with fact_kind that are missing required fields)",
|
|
115
|
+
"default": false
|
|
111
116
|
}
|
|
112
117
|
},
|
|
113
118
|
"additionalProperties": false
|
|
@@ -150,7 +155,8 @@
|
|
|
150
155
|
"no-cycles": true,
|
|
151
156
|
"required-fields": true,
|
|
152
157
|
"deprecated-adr-no-successor": true,
|
|
153
|
-
"domain-contradictions": true
|
|
158
|
+
"domain-contradictions": true,
|
|
159
|
+
"strict-fact-shape": false
|
|
154
160
|
},
|
|
155
161
|
"symbolTraceability": {
|
|
156
162
|
"requireAdr": false
|
package/schema/entities.pl
CHANGED
|
@@ -29,6 +29,24 @@ entity_property(_, severity, atom).
|
|
|
29
29
|
entity_property(_, links, list).
|
|
30
30
|
entity_property(_, text_ref, uri).
|
|
31
31
|
|
|
32
|
+
% Typed fact fields - only valid for fact entities
|
|
33
|
+
entity_property(fact, fact_kind, atom).
|
|
34
|
+
entity_property(fact, subject_key, string).
|
|
35
|
+
entity_property(fact, property_key, string).
|
|
36
|
+
entity_property(fact, operator, atom).
|
|
37
|
+
entity_property(fact, value_type, atom).
|
|
38
|
+
entity_property(fact, value_string, string).
|
|
39
|
+
entity_property(fact, value_int, integer).
|
|
40
|
+
entity_property(fact, value_number, number).
|
|
41
|
+
entity_property(fact, value_bool, boolean).
|
|
42
|
+
entity_property(fact, unit, string).
|
|
43
|
+
entity_property(fact, scope, string).
|
|
44
|
+
entity_property(fact, polarity, atom).
|
|
45
|
+
entity_property(fact, closed_world, boolean).
|
|
46
|
+
entity_property(fact, valid_from, datetime).
|
|
47
|
+
entity_property(fact, valid_to, datetime).
|
|
48
|
+
entity_property(fact, canonical_key, string).
|
|
49
|
+
|
|
32
50
|
% Required properties for all entity types
|
|
33
51
|
required_property(Type, id) :- entity_type(Type).
|
|
34
52
|
required_property(Type, title) :- entity_type(Type).
|
package/schema/validation.pl
CHANGED
|
@@ -17,7 +17,9 @@ validate_entity(Type, Props) :-
|
|
|
17
17
|
% required properties present
|
|
18
18
|
forall(required_property(Type, P), memberchk(P=_Val, Props)),
|
|
19
19
|
% all properties have correct types
|
|
20
|
-
forall(member(Key=Val, Props), validate_property_type(Type, Key, Val))
|
|
20
|
+
forall(member(Key=Val, Props), validate_property_type(Type, Key, Val)),
|
|
21
|
+
% validate entity-specific shape constraints
|
|
22
|
+
validate_entity_shape(Type, Props).
|
|
21
23
|
|
|
22
24
|
% validate_relationship(+RelType, +From, +To)
|
|
23
25
|
% From and To are pairs Type=Id or structures type(Type) - allow Type or Type=Id
|
|
@@ -33,9 +35,8 @@ type_of(Type, Type) :- atom(Type), entity_type(Type), !.
|
|
|
33
35
|
type_of(Type=_Id, Type) :- atom(Type), entity_type(Type), !.
|
|
34
36
|
|
|
35
37
|
% validate_property_type(+EntityType, +Prop, +Value)
|
|
36
|
-
validate_property_type(
|
|
37
|
-
|
|
38
|
-
( entity_property(_Any, Prop, Kind) -> true ; Kind = atom ),
|
|
38
|
+
validate_property_type(Type, Prop, Value) :-
|
|
39
|
+
entity_property(Type, Prop, Kind),
|
|
39
40
|
check_kind(Kind, Value), !.
|
|
40
41
|
|
|
41
42
|
% check_kind(Kind, Value) succeeds if Value matches Kind
|
|
@@ -44,6 +45,116 @@ check_kind(string, V) :- string(V).
|
|
|
44
45
|
check_kind(datetime, V) :- string(V). % accept ISO strings for now
|
|
45
46
|
check_kind(list, V) :- is_list(V).
|
|
46
47
|
check_kind(uri, V) :- string(V).
|
|
48
|
+
check_kind(integer, V) :- integer(V).
|
|
49
|
+
check_kind(number, V) :- number(V).
|
|
50
|
+
check_kind(boolean, true).
|
|
51
|
+
check_kind(boolean, false).
|
|
47
52
|
|
|
48
53
|
% Fallback false
|
|
49
54
|
check_kind(_, _) :- fail.
|
|
55
|
+
|
|
56
|
+
% validate_entity_shape(+Type, +Props)
|
|
57
|
+
% Validates entity-specific shape constraints (e.g., strict fact shapes)
|
|
58
|
+
validate_entity_shape(fact, Props) :-
|
|
59
|
+
!,
|
|
60
|
+
valid_optional_fact_enums(Props),
|
|
61
|
+
valid_polarity_in_props(Props),
|
|
62
|
+
( memberchk(fact_kind=RawKind, Props) -> validate_fact_shape(RawKind, Props) ; true ).
|
|
63
|
+
validate_entity_shape(Type, Props) :-
|
|
64
|
+
Type \= fact,
|
|
65
|
+
% Non-fact entities cannot have fact-only fields
|
|
66
|
+
forall(member(Key=_Val, Props), \+ is_fact_only_field(Key)).
|
|
67
|
+
|
|
68
|
+
% is_fact_only_field(+Key) - true if Key is a fact-specific field
|
|
69
|
+
is_fact_only_field(fact_kind).
|
|
70
|
+
is_fact_only_field(subject_key).
|
|
71
|
+
is_fact_only_field(property_key).
|
|
72
|
+
is_fact_only_field(operator).
|
|
73
|
+
is_fact_only_field(value_type).
|
|
74
|
+
is_fact_only_field(value_string).
|
|
75
|
+
is_fact_only_field(value_int).
|
|
76
|
+
is_fact_only_field(value_number).
|
|
77
|
+
is_fact_only_field(value_bool).
|
|
78
|
+
is_fact_only_field(unit).
|
|
79
|
+
is_fact_only_field(scope).
|
|
80
|
+
is_fact_only_field(polarity).
|
|
81
|
+
is_fact_only_field(closed_world).
|
|
82
|
+
is_fact_only_field(valid_from).
|
|
83
|
+
is_fact_only_field(valid_to).
|
|
84
|
+
is_fact_only_field(canonical_key).
|
|
85
|
+
|
|
86
|
+
% validate_fact_shape(+Kind, +Props)
|
|
87
|
+
validate_fact_shape(subject, Props) :-
|
|
88
|
+
memberchk(subject_key=_Val, Props),
|
|
89
|
+
valid_optional_fact_enums(Props),
|
|
90
|
+
valid_polarity_in_props(Props).
|
|
91
|
+
validate_fact_shape(property_value, Props) :-
|
|
92
|
+
memberchk(subject_key=_Subject, Props),
|
|
93
|
+
memberchk(property_key=_Property, Props),
|
|
94
|
+
memberchk(operator=Op, Props),
|
|
95
|
+
valid_operator(Op),
|
|
96
|
+
memberchk(value_type=VT, Props),
|
|
97
|
+
valid_value_type(VT),
|
|
98
|
+
exactly_one_value_field(Props),
|
|
99
|
+
value_type_matches_field(VT, Props),
|
|
100
|
+
valid_optional_fact_enums(Props),
|
|
101
|
+
valid_polarity_in_props(Props).
|
|
102
|
+
validate_fact_shape(observation, Props) :-
|
|
103
|
+
% Observation facts are allowed but don't require full strict property tuple yet
|
|
104
|
+
valid_optional_fact_enums(Props),
|
|
105
|
+
valid_polarity_in_props(Props).
|
|
106
|
+
validate_fact_shape(meta, Props) :-
|
|
107
|
+
% Meta facts are allowed but don't require full strict property tuple yet
|
|
108
|
+
valid_optional_fact_enums(Props),
|
|
109
|
+
valid_polarity_in_props(Props).
|
|
110
|
+
validate_fact_shape(Kind, _Props) :-
|
|
111
|
+
% Unknown fact_kind values fail validation
|
|
112
|
+
\+ memberchk(Kind, [subject, property_value, observation, meta]),
|
|
113
|
+
fail.
|
|
114
|
+
|
|
115
|
+
% valid_operator(+Op)
|
|
116
|
+
valid_operator(eq).
|
|
117
|
+
valid_operator(neq).
|
|
118
|
+
valid_operator(lt).
|
|
119
|
+
valid_operator(lte).
|
|
120
|
+
valid_operator(gt).
|
|
121
|
+
valid_operator(gte).
|
|
122
|
+
|
|
123
|
+
% valid_value_type(+VT)
|
|
124
|
+
valid_value_type(string).
|
|
125
|
+
valid_value_type(int).
|
|
126
|
+
valid_value_type(number).
|
|
127
|
+
valid_value_type(bool).
|
|
128
|
+
|
|
129
|
+
% valid_polarity_in_props(+Props)
|
|
130
|
+
% Validates polarity if present; succeeds if no polarity in props
|
|
131
|
+
valid_polarity_in_props(Props) :-
|
|
132
|
+
( memberchk(polarity=P, Props) -> valid_polarity(P) ; true ).
|
|
133
|
+
|
|
134
|
+
% valid_optional_fact_enums(+Props)
|
|
135
|
+
% Validates enum-typed fact fields whenever they are present
|
|
136
|
+
valid_optional_fact_enums(Props) :-
|
|
137
|
+
( memberchk(operator=Op, Props) -> valid_operator(Op) ; true ),
|
|
138
|
+
( memberchk(value_type=VT, Props) -> valid_value_type(VT) ; true ).
|
|
139
|
+
|
|
140
|
+
% valid_polarity(+P)
|
|
141
|
+
valid_polarity(require).
|
|
142
|
+
valid_polarity(forbid).
|
|
143
|
+
|
|
144
|
+
% exactly_one_value_field(+Props)
|
|
145
|
+
exactly_one_value_field(Props) :-
|
|
146
|
+
findall(F, (member(F=_, Props), is_value_field(F)), Fields),
|
|
147
|
+
length(Fields, 1).
|
|
148
|
+
|
|
149
|
+
% is_value_field(+Field)
|
|
150
|
+
is_value_field(value_string).
|
|
151
|
+
is_value_field(value_int).
|
|
152
|
+
is_value_field(value_number).
|
|
153
|
+
is_value_field(value_bool).
|
|
154
|
+
|
|
155
|
+
% value_type_matches_field(+ValueType, +Props)
|
|
156
|
+
% Ensures that value_type matches the actual value field present
|
|
157
|
+
value_type_matches_field(string, Props) :- memberchk(value_string=_, Props), !.
|
|
158
|
+
value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
|
|
159
|
+
value_type_matches_field(number, Props) :- memberchk(value_number=_, Props), !.
|
|
160
|
+
value_type_matches_field(bool, Props) :- memberchk(value_bool=_, Props), !.
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Public re-export barrel for shared check types.
|
|
21
|
+
* Import from "kibi-cli/public/check-types" in MCP or external consumers.
|
|
22
|
+
*/
|
|
23
|
+
export type {
|
|
24
|
+
ChecksConfig,
|
|
25
|
+
RuleDefinition,
|
|
26
|
+
SymbolTraceabilityOptions,
|
|
27
|
+
Violation,
|
|
28
|
+
} from "../utils/rule-registry.js";
|
|
29
|
+
|
|
30
|
+
export {
|
|
31
|
+
DEFAULT_CHECKS_CONFIG,
|
|
32
|
+
RULE_NAMES,
|
|
33
|
+
RULES,
|
|
34
|
+
getEffectiveRules,
|
|
35
|
+
mergeChecksConfig,
|
|
36
|
+
validateRuleName,
|
|
37
|
+
} from "../utils/rule-registry.js";
|