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.
Files changed (78) hide show
  1. package/dist/cli.d.ts +4 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +91 -14
  4. package/dist/commands/check.d.ts +5 -8
  5. package/dist/commands/check.d.ts.map +1 -1
  6. package/dist/commands/check.js +41 -62
  7. package/dist/commands/coverage.d.ts +12 -0
  8. package/dist/commands/coverage.d.ts.map +1 -0
  9. package/dist/commands/coverage.js +24 -0
  10. package/dist/commands/discovery-shared.d.ts +11 -0
  11. package/dist/commands/discovery-shared.d.ts.map +1 -0
  12. package/dist/commands/discovery-shared.js +281 -0
  13. package/dist/commands/doctor.d.ts +3 -1
  14. package/dist/commands/doctor.d.ts.map +1 -1
  15. package/dist/commands/doctor.js +12 -13
  16. package/dist/commands/gaps.d.ts +12 -0
  17. package/dist/commands/gaps.d.ts.map +1 -0
  18. package/dist/commands/gaps.js +28 -0
  19. package/dist/commands/graph.d.ts +13 -0
  20. package/dist/commands/graph.d.ts.map +1 -0
  21. package/dist/commands/graph.js +35 -0
  22. package/dist/commands/init.d.ts +3 -1
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +4 -3
  25. package/dist/commands/query.d.ts +3 -1
  26. package/dist/commands/query.d.ts.map +1 -1
  27. package/dist/commands/query.js +9 -20
  28. package/dist/commands/search.d.ts +9 -0
  29. package/dist/commands/search.d.ts.map +1 -0
  30. package/dist/commands/search.js +38 -0
  31. package/dist/commands/status.d.ts +6 -0
  32. package/dist/commands/status.d.ts.map +1 -0
  33. package/dist/commands/status.js +9 -0
  34. package/dist/commands/sync/persistence.d.ts.map +1 -1
  35. package/dist/commands/sync/persistence.js +79 -12
  36. package/dist/commands/sync.d.ts +4 -1
  37. package/dist/commands/sync.d.ts.map +1 -1
  38. package/dist/commands/sync.js +79 -31
  39. package/dist/extractors/markdown.d.ts +17 -0
  40. package/dist/extractors/markdown.d.ts.map +1 -1
  41. package/dist/extractors/markdown.js +104 -14
  42. package/dist/prolog/codec.d.ts +32 -5
  43. package/dist/prolog/codec.d.ts.map +1 -1
  44. package/dist/prolog/codec.js +95 -58
  45. package/dist/prolog.d.ts.map +1 -1
  46. package/dist/prolog.js +12 -2
  47. package/dist/public/check-types.d.ts +7 -0
  48. package/dist/public/check-types.d.ts.map +1 -0
  49. package/dist/public/check-types.js +18 -0
  50. package/dist/public/schemas/entity.d.ts +68 -0
  51. package/dist/public/schemas/entity.d.ts.map +1 -1
  52. package/dist/public/schemas/entity.js +52 -0
  53. package/dist/relationships/shards.d.ts.map +1 -1
  54. package/dist/relationships/shards.js +6 -3
  55. package/dist/schemas/entity.schema.json +120 -0
  56. package/dist/search-ranking.d.ts +9 -0
  57. package/dist/search-ranking.d.ts.map +1 -0
  58. package/dist/search-ranking.js +149 -0
  59. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  60. package/dist/traceability/symbol-extract.js +16 -8
  61. package/dist/types/entities.d.ts +19 -1
  62. package/dist/types/entities.d.ts.map +1 -1
  63. package/dist/utils/prolog-cleanup.d.ts +10 -0
  64. package/dist/utils/prolog-cleanup.d.ts.map +1 -0
  65. package/dist/utils/prolog-cleanup.js +39 -0
  66. package/dist/utils/rule-registry.d.ts +8 -0
  67. package/dist/utils/rule-registry.d.ts.map +1 -1
  68. package/dist/utils/rule-registry.js +14 -12
  69. package/package.json +10 -2
  70. package/schema/config.json +7 -1
  71. package/schema/entities.pl +18 -0
  72. package/schema/validation.pl +115 -4
  73. package/src/public/check-types.ts +37 -0
  74. package/src/public/schemas/entity.ts +58 -0
  75. package/src/schemas/entity.schema.json +56 -1
  76. package/dist/kb/target-resolver.d.ts +0 -80
  77. package/dist/kb/target-resolver.d.ts.map +0 -1
  78. package/dist/kb/target-resolver.js +0 -313
@@ -28,6 +28,21 @@ export function toPrologAtom(value) {
28
28
  ? value
29
29
  : `'${value.replace(/'/g, "''")}'`;
30
30
  }
31
+ /**
32
+ * Escape a string value for use inside a Prolog double-quoted string literal.
33
+ * Escapes: backslash, double-quote, newline, carriage-return, tab.
34
+ * Returns the full quoted literal including surrounding double-quotes.
35
+ */
36
+ export function toPrologString(value) {
37
+ // implements REQ-009
38
+ const escaped = value
39
+ .replace(/\\/g, "\\\\")
40
+ .replace(/"/g, '\\"')
41
+ .replace(/\n/g, "\\n")
42
+ .replace(/\r/g, "\\r")
43
+ .replace(/\t/g, "\\t");
44
+ return `"${escaped}"`;
45
+ }
31
46
  /**
32
47
  * Escape a string for embedding inside a single-quoted Prolog atom.
33
48
  * Alias for escapeAtom for semantic clarity.
@@ -35,56 +50,6 @@ export function toPrologAtom(value) {
35
50
  export function escapeAtomContent(value) {
36
51
  return value.replace(/'/g, "''");
37
52
  }
38
- /**
39
- * Split a string by delimiter at the top level (not inside brackets or quotes).
40
- * This is the general-purpose version that splits at depth 0.
41
- */
42
- export function splitTopLevel(str, delimiter) {
43
- const results = [];
44
- let current = "";
45
- let depth = 0;
46
- let inDoubleQuotes = false;
47
- let inSingleQuotes = false;
48
- for (let i = 0; i < str.length; i++) {
49
- const char = str[i];
50
- const prev = i > 0 ? str[i - 1] : "";
51
- if (char === '"' && !inSingleQuotes && prev !== "\\") {
52
- inDoubleQuotes = !inDoubleQuotes;
53
- current += char;
54
- continue;
55
- }
56
- if (char === "'" && !inDoubleQuotes && prev !== "\\") {
57
- inSingleQuotes = !inSingleQuotes;
58
- current += char;
59
- continue;
60
- }
61
- if (!inSingleQuotes && !inDoubleQuotes && (char === "[" || char === "(")) {
62
- depth++;
63
- current += char;
64
- continue;
65
- }
66
- if (!inSingleQuotes && !inDoubleQuotes && (char === "]" || char === ")")) {
67
- depth--;
68
- current += char;
69
- continue;
70
- }
71
- if (!inSingleQuotes &&
72
- !inDoubleQuotes &&
73
- depth === 0 &&
74
- char === delimiter) {
75
- if (current.length > 0) {
76
- results.push(current);
77
- }
78
- current = "";
79
- continue;
80
- }
81
- current += char;
82
- }
83
- if (current.length > 0) {
84
- results.push(current);
85
- }
86
- return results;
87
- }
88
53
  /**
89
54
  * Parse a Prolog list of lists into a JavaScript array.
90
55
  * Input: "[[a,b,c],[d,e,f]]"
@@ -199,6 +164,7 @@ export function parsePropertyList(propsStr) {
199
164
  * Parse a single Prolog value, handling typed literals and URIs.
200
165
  */
201
166
  export function parsePrologValue(valueInput) {
167
+ // implements REQ-009
202
168
  const value = valueInput.trim();
203
169
  // Handle typed literal: ^^("value", type)
204
170
  if (value.startsWith("^^(")) {
@@ -220,10 +186,21 @@ export function parsePrologValue(valueInput) {
220
186
  const parts = splitTopLevelGeneral(innerContent, ",");
221
187
  if (parts.length >= 2) {
222
188
  let literalValue = parts[0].trim();
189
+ const datatype = parts[1].trim();
223
190
  if (literalValue.startsWith('"') && literalValue.endsWith('"')) {
224
191
  literalValue = literalValue.substring(1, literalValue.length - 1);
225
192
  }
226
- // Handle array notation
193
+ // Parse typed literals based on datatype
194
+ if (datatype.includes("#integer")) {
195
+ return Number.parseInt(literalValue, 10);
196
+ }
197
+ if (datatype.includes("#decimal") || datatype.includes("#double")) {
198
+ return Number.parseFloat(literalValue);
199
+ }
200
+ if (datatype.includes("#boolean")) {
201
+ return literalValue === "true";
202
+ }
203
+ // Handle array notation for string values
227
204
  if (literalValue.startsWith("[") && literalValue.endsWith("]")) {
228
205
  const listContent = literalValue.substring(1, literalValue.length - 1);
229
206
  if (listContent === "") {
@@ -267,27 +244,40 @@ export function parsePrologValue(valueInput) {
267
244
  * General-purpose split at top level (not inside brackets or quotes).
268
245
  * More robust version used by property parsing.
269
246
  */
270
- function splitTopLevelGeneral(str, delimiter) {
247
+ export function splitTopLevelGeneral(str, delimiter) {
248
+ // implements REQ-009
271
249
  const results = [];
272
250
  let current = "";
273
251
  let depth = 0;
274
- let inQuotes = false;
252
+ let inDoubleQuotes = false;
253
+ let inSingleQuotes = false;
275
254
  for (let i = 0; i < str.length; i++) {
276
255
  const char = str[i];
277
256
  const prevChar = i > 0 ? str[i - 1] : "";
278
- if (char === '"' && prevChar !== "\\") {
279
- inQuotes = !inQuotes;
257
+ if (char === '"' && !inSingleQuotes && prevChar !== "\\") {
258
+ inDoubleQuotes = !inDoubleQuotes;
280
259
  current += char;
281
260
  }
282
- else if (!inQuotes && (char === "[" || char === "(")) {
261
+ else if (char === "'" && !inDoubleQuotes && prevChar !== "\\") {
262
+ inSingleQuotes = !inSingleQuotes;
263
+ current += char;
264
+ }
265
+ else if (!inDoubleQuotes &&
266
+ !inSingleQuotes &&
267
+ (char === "[" || char === "(")) {
283
268
  depth++;
284
269
  current += char;
285
270
  }
286
- else if (!inQuotes && (char === "]" || char === ")")) {
271
+ else if (!inDoubleQuotes &&
272
+ !inSingleQuotes &&
273
+ (char === "]" || char === ")")) {
287
274
  depth--;
288
275
  current += char;
289
276
  }
290
- else if (!inQuotes && depth === 0 && char === delimiter) {
277
+ else if (!inDoubleQuotes &&
278
+ !inSingleQuotes &&
279
+ depth === 0 &&
280
+ char === delimiter) {
291
281
  if (current) {
292
282
  results.push(current);
293
283
  current = "";
@@ -302,6 +292,14 @@ function splitTopLevelGeneral(str, delimiter) {
302
292
  }
303
293
  return results;
304
294
  }
295
+ /**
296
+ * Split a string by delimiter at the top level (not inside brackets or quotes).
297
+ * @see splitTopLevelGeneral
298
+ */
299
+ export function splitTopLevel(str, delimiter) {
300
+ // implements REQ-009
301
+ return splitTopLevelGeneral(str, delimiter);
302
+ }
305
303
  /**
306
304
  * Strip outer quotes from a string value.
307
305
  */
@@ -420,6 +418,45 @@ function unwrapList(value) {
420
418
  }
421
419
  return value;
422
420
  }
421
+ /**
422
+ * Parse a Prolog list of violation/5 terms into JavaScript objects.
423
+ * Handles descriptions and suggestions that contain commas or nested parens.
424
+ * Input: "[violation(rule,'EntityId',\"Desc\",\"Sugg\",'src')]"
425
+ */
426
+ export function parseViolationRows(raw) {
427
+ // implements REQ-006
428
+ const trimmed = raw.trim();
429
+ if (trimmed === "[]" || trimmed.length === 0) {
430
+ return [];
431
+ }
432
+ const violations = [];
433
+ // Unwrap outer list
434
+ const content = trimmed.startsWith("[") && trimmed.endsWith("]")
435
+ ? trimmed.slice(1, -1)
436
+ : trimmed;
437
+ // Split at top-level commas to get individual violation(...) terms
438
+ const terms = splitTopLevelGeneral(content, ",");
439
+ for (const term of terms) {
440
+ const t = term.trim();
441
+ if (!t.startsWith("violation(") || !t.endsWith(")"))
442
+ continue;
443
+ // Strip "violation(" prefix and trailing ")"
444
+ const inner = t.slice("violation(".length, -1);
445
+ // Split the 5 arguments at top-level commas
446
+ const parts = splitTopLevelGeneral(inner, ",");
447
+ if (parts.length < 4)
448
+ continue;
449
+ const rule = parts[0].trim().replace(/^'|'$/g, "");
450
+ const entityId = parts[1].trim().replace(/^'|'$/g, "");
451
+ const description = parts[2].trim().replace(/^"|"$/g, "");
452
+ const suggestion = parts[3].trim().replace(/^"|"$/g, "");
453
+ const source = parts.length >= 5
454
+ ? parts[4].trim().replace(/^'|'$/g, "") || undefined
455
+ : undefined;
456
+ violations.push({ rule, entityId, description, suggestion, source });
457
+ }
458
+ return violations;
459
+ }
423
460
  /**
424
461
  * Strip quotes from a value (single or double).
425
462
  */
@@ -1 +1 @@
1
- {"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"AA4BA,wBAAgB,eAAe,IAAI,MAAM,CAsCxC;AACD,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,oBAAoB,CAAoC;IAChE,OAAO,CAAC,cAAc,CACyC;IAC/D,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,aAAa,CAA6B;gBAEtC,OAAO,GAAE,aAAkB;IAKjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAoCd,YAAY;IAyCpB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAuJ1D,eAAe,IAAI,IAAI;IAIvB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,kBAAkB;YAIZ,YAAY;IAkC1B,OAAO,CAAC,WAAW;IA8FnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,cAAc;IA8BtB,SAAS,IAAI,OAAO;IAIpB,MAAM,IAAI,MAAM;IAIV,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBjC"}
1
+ {"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"AA4BA,wBAAgB,eAAe,IAAI,MAAM,CAyCxC;AACD,MAAM,WAAW,aAAa;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,oBAAoB,CAAoC;IAChE,OAAO,CAAC,cAAc,CACyC;IAC/D,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,aAAa,CAA6B;gBAEtC,OAAO,GAAE,aAAkB;IAKjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAoCd,YAAY;IAyCpB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAuJ1D,eAAe,IAAI,IAAI;IAIvB,OAAO,CAAC,eAAe;IAavB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,kBAAkB;YAIZ,YAAY;IAkC1B,OAAO,CAAC,WAAW;IAsGnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,cAAc;IA8BtB,SAAS,IAAI,OAAO;IAIpB,MAAM,IAAI,MAAM;IAIV,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBjC"}
package/dist/prolog.js CHANGED
@@ -23,6 +23,7 @@ import { fileURLToPath } from "node:url";
23
23
  const importMetaDir = path.dirname(fileURLToPath(import.meta.url));
24
24
  const require = createRequire(import.meta.url);
25
25
  export function resolveKbPlPath() {
26
+ // implements REQ-009
26
27
  const overrideKbPath = process.env.KIBI_KB_PL_PATH;
27
28
  if (overrideKbPath && existsSync(overrideKbPath)) {
28
29
  return overrideKbPath;
@@ -32,7 +33,9 @@ export function resolveKbPlPath() {
32
33
  if (existsSync(installedKbPl))
33
34
  return installedKbPl;
34
35
  }
35
- catch { }
36
+ catch {
37
+ // require.resolve not available or package not installed
38
+ }
36
39
  const startDirs = [importMetaDir, process.cwd()];
37
40
  for (const startDir of startDirs) {
38
41
  let currentDir = path.resolve(startDir);
@@ -363,10 +366,17 @@ export class PrologProcess {
363
366
  bindings: this.extractBindings(clean),
364
367
  };
365
368
  }
369
+ if (stdout.includes("__KIBI_FALSE__")) {
370
+ return {
371
+ success: false,
372
+ bindings: {},
373
+ error: "Query returned false",
374
+ };
375
+ }
366
376
  return {
367
377
  success: false,
368
378
  bindings: {},
369
- error: "Query failed",
379
+ error: `Query failed - stdout: ${stdout.substring(0, 200)}, stderr: ${stderr.substring(0, 200)}`,
370
380
  };
371
381
  }
372
382
  normalizeGoal(goal) {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Public re-export barrel for shared check types.
3
+ * Import from "kibi-cli/public/check-types" in MCP or external consumers.
4
+ */
5
+ export type { ChecksConfig, RuleDefinition, SymbolTraceabilityOptions, Violation, } from "../utils/rule-registry.js";
6
+ export { DEFAULT_CHECKS_CONFIG, RULE_NAMES, RULES, getEffectiveRules, mergeChecksConfig, validateRuleName, } from "../utils/rule-registry.js";
7
+ //# sourceMappingURL=check-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-types.d.ts","sourceRoot":"","sources":["../../src/public/check-types.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AACH,YAAY,EACV,YAAY,EACZ,cAAc,EACd,yBAAyB,EACzB,SAAS,GACV,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,qBAAqB,EACrB,UAAU,EACV,KAAK,EACL,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,18 @@
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
+ export { DEFAULT_CHECKS_CONFIG, RULE_NAMES, RULES, getEffectiveRules, mergeChecksConfig, validateRuleName, } from "../utils/rule-registry.js";
@@ -50,8 +50,76 @@ declare const entitySchema: {
50
50
  type: string;
51
51
  enum: string[];
52
52
  };
53
+ fact_kind: {
54
+ type: string;
55
+ enum: string[];
56
+ };
57
+ subject_key: {
58
+ type: string;
59
+ };
60
+ property_key: {
61
+ type: string;
62
+ };
63
+ operator: {
64
+ type: string;
65
+ enum: string[];
66
+ };
67
+ value_type: {
68
+ type: string;
69
+ enum: string[];
70
+ };
71
+ value_string: {
72
+ type: string;
73
+ };
74
+ value_int: {
75
+ type: string;
76
+ };
77
+ value_number: {
78
+ type: string;
79
+ };
80
+ value_bool: {
81
+ type: string;
82
+ };
83
+ unit: {
84
+ type: string;
85
+ };
86
+ scope: {
87
+ type: string;
88
+ };
89
+ polarity: {
90
+ type: string;
91
+ enum: string[];
92
+ };
93
+ closed_world: {
94
+ type: string;
95
+ };
96
+ valid_from: {
97
+ type: string;
98
+ };
99
+ valid_to: {
100
+ type: string;
101
+ };
102
+ canonical_key: {
103
+ type: string;
104
+ };
53
105
  };
54
106
  required: string[];
107
+ allOf: {
108
+ if: {
109
+ properties: {
110
+ type: {
111
+ const: string;
112
+ };
113
+ };
114
+ };
115
+ else: {
116
+ not: {
117
+ anyOf: {
118
+ required: string[];
119
+ }[];
120
+ };
121
+ };
122
+ }[];
55
123
  additionalProperties: boolean;
56
124
  };
57
125
  export default entitySchema;
@@ -1 +1 @@
1
- {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/entity.ts"],"names":[],"mappings":"AAkBA,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgEjB,CAAC;AAEF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/entity.ts"],"names":[],"mappings":"AAwBA,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoHjB,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -68,6 +68,26 @@ const entitySchema = {
68
68
  "fact",
69
69
  ],
70
70
  },
71
+ // Typed fact fields - only valid when type === "fact"
72
+ fact_kind: {
73
+ type: "string",
74
+ enum: ["subject", "property_value", "observation", "meta"],
75
+ },
76
+ subject_key: { type: "string" },
77
+ property_key: { type: "string" },
78
+ operator: { type: "string", enum: ["eq", "neq", "lt", "lte", "gt", "gte"] },
79
+ value_type: { type: "string", enum: ["string", "int", "number", "bool"] },
80
+ value_string: { type: "string" },
81
+ value_int: { type: "integer" },
82
+ value_number: { type: "number" },
83
+ value_bool: { type: "boolean" },
84
+ unit: { type: "string" },
85
+ scope: { type: "string" },
86
+ polarity: { type: "string", enum: ["require", "forbid"] },
87
+ closed_world: { type: "boolean" },
88
+ valid_from: { type: "string" },
89
+ valid_to: { type: "string" },
90
+ canonical_key: { type: "string" },
71
91
  },
72
92
  required: [
73
93
  "id",
@@ -78,6 +98,38 @@ const entitySchema = {
78
98
  "source",
79
99
  "type",
80
100
  ],
101
+ allOf: [
102
+ // Forbid fact-only fields on non-fact entities
103
+ {
104
+ if: {
105
+ properties: { type: { const: "fact" } },
106
+ },
107
+ // Fact entities can have fact fields (no restriction)
108
+ // Non-fact entities cannot have fact fields
109
+ else: {
110
+ not: {
111
+ anyOf: [
112
+ { required: ["fact_kind"] },
113
+ { required: ["subject_key"] },
114
+ { required: ["property_key"] },
115
+ { required: ["operator"] },
116
+ { required: ["value_type"] },
117
+ { required: ["value_string"] },
118
+ { required: ["value_int"] },
119
+ { required: ["value_number"] },
120
+ { required: ["value_bool"] },
121
+ { required: ["unit"] },
122
+ { required: ["scope"] },
123
+ { required: ["polarity"] },
124
+ { required: ["closed_world"] },
125
+ { required: ["valid_from"] },
126
+ { required: ["valid_to"] },
127
+ { required: ["canonical_key"] },
128
+ ],
129
+ },
130
+ },
131
+ },
132
+ ],
81
133
  additionalProperties: false,
82
134
  };
83
135
  export default entitySchema;
@@ -1 +1 @@
1
- {"version":3,"file":"shards.d.ts","sourceRoot":"","sources":["../../src/relationships/shards.ts"],"names":[],"mappings":"AAuBA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAqFjE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kBAAkB,EAAE,GAC5B,IAAI,CAoBN;AAqCD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,GAC3C;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CA4BzC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUnD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAUlE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,kBAAkB,EAAE,CAKtB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,MAAM,CAMR;AAID;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,QAAQ,EAAE,kBAAkB,EAAE,GAC7B,kBAAkB,EAAE,CAgCtB"}
1
+ {"version":3,"file":"shards.d.ts","sourceRoot":"","sources":["../../src/relationships/shards.ts"],"names":[],"mappings":"AAuBA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAsFjE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,kBAAkB,EAAE,GAC5B,IAAI,CAoBN;AAqCD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,CAAC,GAC3C;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CA4BzC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUnD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,EAAE,CAUlE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,kBAAkB,EAAE,EAC7B,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,kBAAkB,EAAE,CAKtB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,MAAM,CAMR;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAE1B,QAAQ,EAAE,kBAAkB,EAAE,EAC9B,QAAQ,EAAE,kBAAkB,EAAE,GAC7B,kBAAkB,EAAE,CAgCtB"}
@@ -46,6 +46,7 @@ export function computeShardPath(kbRoot, entityId) {
46
46
  * Throws on parse errors.
47
47
  */
48
48
  export function readShard(shardPath) {
49
+ // implements REQ-005
49
50
  if (!fs.existsSync(shardPath)) {
50
51
  return [];
51
52
  }
@@ -78,7 +79,7 @@ export function readShard(shardPath) {
78
79
  // Handle created_at - YAML may auto-convert ISO dates to Date objects
79
80
  let createdAt;
80
81
  if (rec.created_at instanceof Date) {
81
- createdAt = rec.created_at.toISOString().replace(/\.000Z$/, 'Z');
82
+ createdAt = rec.created_at.toISOString().replace(/\.000Z$/, "Z");
82
83
  }
83
84
  else if (typeof rec.created_at === "string" && rec.created_at) {
84
85
  createdAt = rec.created_at;
@@ -150,7 +151,7 @@ function serializeToYAML(records) {
150
151
  lines.push(` confidence: ${record.confidence}`);
151
152
  }
152
153
  }
153
- return lines.join("\n") + "\n";
154
+ return `${lines.join("\n")}\n`;
154
155
  }
155
156
  /**
156
157
  * Sorts records deterministically by from, then type, then to.
@@ -234,7 +235,9 @@ export function relationshipIdFor(type, from, to) {
234
235
  * Deduplicates by (type, from, to) tuple.
235
236
  * On conflict, keeps the record with newer created_at timestamp.
236
237
  */
237
- export function mergeRecords(existing, incoming) {
238
+ export function mergeRecords(
239
+ // implements REQ-005
240
+ existing, incoming) {
238
241
  const recordMap = new Map();
239
242
  // Helper to create key from record
240
243
  const makeKey = (r) => `${r.type}|${r.from}|${r.to}`;
@@ -0,0 +1,120 @@
1
+ {
2
+ "$id": "entity.schema.json",
3
+ "title": "Entity",
4
+ "type": "object",
5
+ "properties": {
6
+ "id": { "type": "string" },
7
+ "title": { "type": "string" },
8
+ "status": {
9
+ "type": "string",
10
+ "enum": [
11
+ "active",
12
+ "inactive",
13
+ "draft",
14
+ "archived",
15
+ "deleted",
16
+ "approved",
17
+ "rejected",
18
+ "pending",
19
+ "in_progress",
20
+ "superseded",
21
+ "open",
22
+ "closed",
23
+ "deprecated",
24
+ "passing",
25
+ "failing",
26
+ "skipped",
27
+ "proposed",
28
+ "accepted",
29
+ "removed"
30
+ ]
31
+ },
32
+ "created_at": { "type": "string" },
33
+ "updated_at": { "type": "string" },
34
+ "source": { "type": "string" },
35
+ "tags": { "type": "array", "items": { "type": "string" } },
36
+ "owner": { "type": "string" },
37
+ "priority": { "type": "string" },
38
+ "severity": { "type": "string" },
39
+ "links": { "type": "array", "items": { "type": "string" } },
40
+ "text_ref": { "type": "string" },
41
+ "type": {
42
+ "type": "string",
43
+ "enum": [
44
+ "req",
45
+ "scenario",
46
+ "test",
47
+ "adr",
48
+ "flag",
49
+ "event",
50
+ "symbol",
51
+ "fact"
52
+ ]
53
+ },
54
+ "fact_kind": {
55
+ "type": "string",
56
+ "enum": ["subject", "property_value", "observation", "meta"]
57
+ },
58
+ "subject_key": { "type": "string" },
59
+ "property_key": { "type": "string" },
60
+ "operator": {
61
+ "type": "string",
62
+ "enum": ["eq", "neq", "lt", "lte", "gt", "gte"]
63
+ },
64
+ "value_type": {
65
+ "type": "string",
66
+ "enum": ["string", "int", "number", "bool"]
67
+ },
68
+ "value_string": { "type": "string" },
69
+ "value_int": { "type": "integer" },
70
+ "value_number": { "type": "number" },
71
+ "value_bool": { "type": "boolean" },
72
+ "unit": { "type": "string" },
73
+ "scope": { "type": "string" },
74
+ "polarity": { "type": "string", "enum": ["require", "forbid"] },
75
+ "closed_world": { "type": "boolean" },
76
+ "valid_from": { "type": "string" },
77
+ "valid_to": { "type": "string" },
78
+ "canonical_key": { "type": "string" }
79
+ },
80
+ "required": [
81
+ "id",
82
+ "title",
83
+ "status",
84
+ "created_at",
85
+ "updated_at",
86
+ "source",
87
+ "type"
88
+ ],
89
+ "allOf": [
90
+ {
91
+ "if": {
92
+ "properties": { "type": { "const": "fact" } }
93
+ },
94
+ "then": {},
95
+ "else": {
96
+ "not": {
97
+ "anyOf": [
98
+ { "required": ["fact_kind"] },
99
+ { "required": ["subject_key"] },
100
+ { "required": ["property_key"] },
101
+ { "required": ["operator"] },
102
+ { "required": ["value_type"] },
103
+ { "required": ["value_string"] },
104
+ { "required": ["value_int"] },
105
+ { "required": ["value_number"] },
106
+ { "required": ["value_bool"] },
107
+ { "required": ["unit"] },
108
+ { "required": ["scope"] },
109
+ { "required": ["polarity"] },
110
+ { "required": ["closed_world"] },
111
+ { "required": ["valid_from"] },
112
+ { "required": ["valid_to"] },
113
+ { "required": ["canonical_key"] }
114
+ ]
115
+ }
116
+ }
117
+ }
118
+ ],
119
+ "additionalProperties": false
120
+ }
@@ -0,0 +1,9 @@
1
+ export interface SearchMatch {
2
+ entity: Record<string, unknown>;
3
+ score: number;
4
+ reasons: string[];
5
+ snippet?: string;
6
+ }
7
+ export declare function rankEntities(entities: Record<string, unknown>[], query: string, workspaceRoot: string): Promise<SearchMatch[]>;
8
+ export declare function loadMarkdownBody(source: string, workspaceRoot: string): Promise<string | null>;
9
+ //# sourceMappingURL=search-ranking.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search-ranking.d.ts","sourceRoot":"","sources":["../src/search-ranking.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACnC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC,CAyBxB;AA+FD,wBAAsB,gBAAgB,CAEpC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB"}