kibi-cli 0.5.0 → 0.6.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 (82) hide show
  1. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  2. package/dist/commands/aggregated-checks.js +3 -2
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +97 -51
  5. package/dist/commands/discovery-shared.d.ts +12 -5
  6. package/dist/commands/discovery-shared.d.ts.map +1 -1
  7. package/dist/commands/discovery-shared.js +21 -13
  8. package/dist/commands/doctor.js +9 -1
  9. package/dist/commands/init-helpers.d.ts.map +1 -1
  10. package/dist/commands/init-helpers.js +2 -3
  11. package/dist/commands/query.d.ts.map +1 -1
  12. package/dist/commands/query.js +15 -5
  13. package/dist/commands/search.js +1 -1
  14. package/dist/commands/sync/cache.d.ts +14 -4
  15. package/dist/commands/sync/cache.d.ts.map +1 -1
  16. package/dist/commands/sync/cache.js +36 -14
  17. package/dist/commands/sync/extraction.d.ts.map +1 -1
  18. package/dist/commands/sync/extraction.js +4 -9
  19. package/dist/commands/sync/manifest.d.ts +14 -3
  20. package/dist/commands/sync/manifest.d.ts.map +1 -1
  21. package/dist/commands/sync/manifest.js +29 -10
  22. package/dist/commands/sync/persistence.d.ts.map +1 -1
  23. package/dist/commands/sync/persistence.js +9 -5
  24. package/dist/commands/sync/staging.d.ts +19 -3
  25. package/dist/commands/sync/staging.d.ts.map +1 -1
  26. package/dist/commands/sync/staging.js +50 -27
  27. package/dist/commands/sync.d.ts.map +1 -1
  28. package/dist/commands/sync.js +16 -20
  29. package/dist/diagnostics.d.ts +1 -10
  30. package/dist/diagnostics.d.ts.map +1 -1
  31. package/dist/diagnostics.js +6 -12
  32. package/dist/env.d.ts +7 -0
  33. package/dist/env.d.ts.map +1 -0
  34. package/dist/env.js +41 -0
  35. package/dist/extractors/manifest.d.ts.map +1 -1
  36. package/dist/extractors/manifest.js +17 -15
  37. package/dist/extractors/markdown.d.ts +2 -0
  38. package/dist/extractors/markdown.d.ts.map +1 -1
  39. package/dist/extractors/markdown.js +124 -30
  40. package/dist/extractors/relationships.d.ts.map +1 -1
  41. package/dist/extractors/relationships.js +10 -4
  42. package/dist/extractors/symbols-coordinator.d.ts +5 -2
  43. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  44. package/dist/extractors/symbols-coordinator.js +5 -2
  45. package/dist/extractors/symbols-ts.d.ts.map +1 -1
  46. package/dist/extractors/symbols-ts.js +56 -10
  47. package/dist/prolog/codec.d.ts +0 -43
  48. package/dist/prolog/codec.d.ts.map +1 -1
  49. package/dist/prolog/codec.js +68 -74
  50. package/dist/prolog.d.ts.map +1 -1
  51. package/dist/prolog.js +45 -29
  52. package/dist/public/schemas/entity.d.ts +3 -0
  53. package/dist/public/schemas/entity.d.ts.map +1 -1
  54. package/dist/public/schemas/entity.js +1 -0
  55. package/dist/public/schemas/relationship.d.ts.map +1 -1
  56. package/dist/public/schemas/relationship.js +1 -0
  57. package/dist/query/service.d.ts +9 -0
  58. package/dist/query/service.d.ts.map +1 -1
  59. package/dist/query/service.js +27 -10
  60. package/dist/schemas/entity.schema.json +23 -0
  61. package/dist/search-ranking.d.ts.map +1 -1
  62. package/dist/search-ranking.js +19 -3
  63. package/dist/traceability/git-staged.d.ts.map +1 -1
  64. package/dist/traceability/git-staged.js +22 -6
  65. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  66. package/dist/traceability/symbol-extract.js +10 -4
  67. package/dist/traceability/temp-kb.d.ts +12 -0
  68. package/dist/traceability/temp-kb.d.ts.map +1 -1
  69. package/dist/traceability/temp-kb.js +42 -17
  70. package/dist/traceability/validate.d.ts.map +1 -1
  71. package/dist/traceability/validate.js +11 -2
  72. package/dist/utils/branch-resolver.d.ts +4 -0
  73. package/dist/utils/branch-resolver.d.ts.map +1 -1
  74. package/dist/utils/branch-resolver.js +29 -12
  75. package/dist/utils/config.d.ts.map +1 -1
  76. package/dist/utils/config.js +8 -2
  77. package/dist/utils/rule-registry.js +2 -2
  78. package/package.json +3 -9
  79. package/src/public/schemas/entity.ts +1 -0
  80. package/src/public/schemas/relationship.ts +1 -0
  81. package/src/schemas/entity.schema.json +23 -0
  82. package/src/schemas/relationship.schema.json +1 -0
@@ -16,6 +16,7 @@
16
16
  * Doubles single-quote characters per ISO Prolog standard.
17
17
  */
18
18
  export function escapeAtom(value) {
19
+ // implements REQ-009
19
20
  return value.replace(/'/g, "''");
20
21
  }
21
22
  /**
@@ -23,6 +24,7 @@ export function escapeAtom(value) {
23
24
  * Simple atoms (lowercase start, alphanumeric + underscore) pass through.
24
25
  */
25
26
  export function toPrologAtom(value) {
27
+ // implements REQ-009
26
28
  const simplePrologAtom = /^[a-z][a-zA-Z0-9_]*$/;
27
29
  return simplePrologAtom.test(value)
28
30
  ? value
@@ -48,14 +50,11 @@ export function toPrologString(value) {
48
50
  * Alias for escapeAtom for semantic clarity.
49
51
  */
50
52
  export function escapeAtomContent(value) {
53
+ // implements REQ-009
51
54
  return value.replace(/'/g, "''");
52
55
  }
53
- /**
54
- * Parse a Prolog list of lists into a JavaScript array.
55
- * Input: "[[a,b,c],[d,e,f]]"
56
- * Output: [["a", "b", "c"], ["d", "e", "f"]]
57
- */
58
56
  export function parseListOfLists(listStr) {
57
+ // implements REQ-009
59
58
  const cleaned = listStr.trim().replace(/^\[/, "").replace(/\]$/, "");
60
59
  if (cleaned === "") {
61
60
  return [];
@@ -102,41 +101,44 @@ export function parseListOfLists(listStr) {
102
101
  }
103
102
  return results;
104
103
  }
105
- /**
106
- * Parse a single entity from Prolog binding format.
107
- * Input: "[abc123, req, [id=abc123, title=\"Test\", ...]]"
108
- */
109
- export function parseEntityFromBinding(bindingStr) {
104
+ export function parseEntityFromBinding(
105
+ // implements REQ-009
106
+ bindingStr) {
110
107
  const cleaned = bindingStr.trim().replace(/^\[/, "").replace(/\]$/, "");
111
108
  const parts = splitTopLevelGeneral(cleaned, ",");
112
109
  if (parts.length < 3) {
113
110
  return {};
114
111
  }
115
- const id = parts[0].trim();
116
- const type = parts[1].trim();
112
+ const idPart = parts[0];
113
+ const typePart = parts[1];
114
+ if (idPart === undefined || typePart === undefined) {
115
+ return {};
116
+ }
117
+ const id = idPart.trim();
118
+ const type = typePart.trim();
117
119
  const propsStr = parts.slice(2).join(",").trim();
118
120
  const props = parsePropertyList(propsStr);
119
121
  return { ...props, id: normalizeEntityId(stripOuterQuotes(id)), type };
120
122
  }
121
- /**
122
- * Parse entity from array returned by parseListOfLists.
123
- * Input: ["abc123", "req", "[id=abc123, title=\"Test\", ...]"]
124
- */
125
123
  export function parseEntityFromList(data) {
124
+ // implements REQ-009
126
125
  if (data.length < 3) {
127
126
  return {};
128
127
  }
129
- const id = data[0].trim();
130
- const type = data[1].trim();
131
- const propsStr = data[2].trim();
128
+ const [idPart, typePart, propsPart] = data;
129
+ if (idPart === undefined ||
130
+ typePart === undefined ||
131
+ propsPart === undefined) {
132
+ return {};
133
+ }
134
+ const id = idPart.trim();
135
+ const type = typePart.trim();
136
+ const propsStr = propsPart.trim();
132
137
  const props = parsePropertyList(propsStr);
133
138
  return { ...props, id: normalizeEntityId(stripOuterQuotes(id)), type };
134
139
  }
135
- /**
136
- * Parse Prolog property list into JavaScript object.
137
- * Input: "[id=abc123, title=\"Test\"]"
138
- */
139
140
  export function parsePropertyList(propsStr) {
141
+ // implements REQ-009
140
142
  const props = {};
141
143
  let cleaned = propsStr.trim();
142
144
  if (cleaned.startsWith("[")) {
@@ -160,9 +162,6 @@ export function parsePropertyList(propsStr) {
160
162
  }
161
163
  return props;
162
164
  }
163
- /**
164
- * Parse a single Prolog value, handling typed literals and URIs.
165
- */
166
165
  export function parsePrologValue(valueInput) {
167
166
  // implements REQ-009
168
167
  const value = valueInput.trim();
@@ -185,8 +184,13 @@ export function parsePrologValue(valueInput) {
185
184
  const innerContent = value.substring(innerStart, innerEnd);
186
185
  const parts = splitTopLevelGeneral(innerContent, ",");
187
186
  if (parts.length >= 2) {
188
- let literalValue = parts[0].trim();
189
- const datatype = parts[1].trim();
187
+ const literalPart = parts[0];
188
+ const datatypePart = parts[1];
189
+ if (literalPart === undefined || datatypePart === undefined) {
190
+ return value;
191
+ }
192
+ let literalValue = literalPart.trim();
193
+ const datatype = datatypePart.trim();
190
194
  if (literalValue.startsWith('"') && literalValue.endsWith('"')) {
191
195
  literalValue = literalValue.substring(1, literalValue.length - 1);
192
196
  }
@@ -240,10 +244,6 @@ export function parsePrologValue(valueInput) {
240
244
  }
241
245
  return value;
242
246
  }
243
- /**
244
- * General-purpose split at top level (not inside brackets or quotes).
245
- * More robust version used by property parsing.
246
- */
247
247
  export function splitTopLevelGeneral(str, delimiter) {
248
248
  // implements REQ-009
249
249
  const results = [];
@@ -292,17 +292,10 @@ export function splitTopLevelGeneral(str, delimiter) {
292
292
  }
293
293
  return results;
294
294
  }
295
- /**
296
- * Split a string by delimiter at the top level (not inside brackets or quotes).
297
- * @see splitTopLevelGeneral
298
- */
299
295
  export function splitTopLevel(str, delimiter) {
300
296
  // implements REQ-009
301
297
  return splitTopLevelGeneral(str, delimiter);
302
298
  }
303
- /**
304
- * Strip outer quotes from a string value.
305
- */
306
299
  function stripOuterQuotes(value) {
307
300
  if (value.startsWith("'") && value.endsWith("'")) {
308
301
  return value.slice(1, -1);
@@ -312,9 +305,6 @@ function stripOuterQuotes(value) {
312
305
  }
313
306
  return value;
314
307
  }
315
- /**
316
- * Normalize entity ID by extracting filename from file:// URI.
317
- */
318
308
  function normalizeEntityId(value) {
319
309
  if (!value.startsWith("file:///")) {
320
310
  return value;
@@ -322,11 +312,8 @@ function normalizeEntityId(value) {
322
312
  const idx = value.lastIndexOf("/");
323
313
  return idx === -1 ? value : value.slice(idx + 1);
324
314
  }
325
- /**
326
- * Parse an atom list from Prolog response.
327
- * Input: "[a, b, c]" or atom string
328
- */
329
315
  export function parseAtomList(raw) {
316
+ // implements REQ-009
330
317
  const trimmed = raw.trim();
331
318
  if (trimmed === "[]" || trimmed.length === 0) {
332
319
  return [];
@@ -339,37 +326,39 @@ export function parseAtomList(raw) {
339
326
  .map((token) => stripQuotes(token.trim()))
340
327
  .filter((token) => token.length > 0);
341
328
  }
342
- /**
343
- * Parse a list of pairs from Prolog response.
344
- */
345
329
  export function parsePairList(raw) {
330
+ // implements REQ-009
346
331
  const rows = parseListRows(raw);
347
332
  const pairs = [];
348
333
  for (const row of rows) {
349
334
  const parts = splitTopLevelGeneral(row, ",").map((part) => stripQuotes(part.trim()));
350
335
  if (parts.length >= 2) {
351
- pairs.push([parts[0], parts[1]]);
336
+ const first = parts[0];
337
+ const second = parts[1];
338
+ if (first !== undefined && second !== undefined) {
339
+ pairs.push([first, second]);
340
+ }
352
341
  }
353
342
  }
354
343
  return pairs;
355
344
  }
356
- /**
357
- * Parse a list of triples from Prolog response.
358
- */
359
345
  export function parseTriples(raw) {
346
+ // implements REQ-009
360
347
  const rows = parseListRows(raw);
361
348
  const triples = [];
362
349
  for (const row of rows) {
363
350
  const parts = splitTopLevelGeneral(row, ",").map((part) => stripQuotes(part.trim()));
364
351
  if (parts.length >= 3) {
365
- triples.push([parts[0], parts[1], parts[2]]);
352
+ const first = parts[0];
353
+ const second = parts[1];
354
+ const third = parts[2];
355
+ if (first !== undefined && second !== undefined && third !== undefined) {
356
+ triples.push([first, second, third]);
357
+ }
366
358
  }
367
359
  }
368
360
  return triples;
369
361
  }
370
- /**
371
- * Parse list rows from Prolog response.
372
- */
373
362
  function parseListRows(raw) {
374
363
  const trimmed = raw.trim();
375
364
  if (trimmed === "[]" || trimmed.length === 0) {
@@ -409,20 +398,12 @@ function parseListRows(raw) {
409
398
  }
410
399
  return rows;
411
400
  }
412
- /**
413
- * Unwrap outer list brackets.
414
- */
415
401
  function unwrapList(value) {
416
402
  if (value.startsWith("[") && value.endsWith("]")) {
417
403
  return value.slice(1, -1).trim();
418
404
  }
419
405
  return value;
420
406
  }
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
407
  export function parseViolationRows(raw) {
427
408
  // implements REQ-006
428
409
  const trimmed = raw.trim();
@@ -446,20 +427,33 @@ export function parseViolationRows(raw) {
446
427
  const parts = splitTopLevelGeneral(inner, ",");
447
428
  if (parts.length < 4)
448
429
  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, "");
430
+ const rulePart = parts[0];
431
+ const entityIdPart = parts[1];
432
+ const descriptionPart = parts[2];
433
+ const suggestionPart = parts[3];
434
+ if (rulePart === undefined ||
435
+ entityIdPart === undefined ||
436
+ descriptionPart === undefined ||
437
+ suggestionPart === undefined) {
438
+ continue;
439
+ }
440
+ const rule = rulePart.trim().replace(/^'|'$/g, "");
441
+ const entityId = entityIdPart.trim().replace(/^'|'$/g, "");
442
+ const description = descriptionPart.trim().replace(/^"|"$/g, "");
443
+ const suggestion = suggestionPart.trim().replace(/^"|"$/g, "");
453
444
  const source = parts.length >= 5
454
- ? parts[4].trim().replace(/^'|'$/g, "") || undefined
445
+ ? parts[4]?.trim().replace(/^'|'$/g, "") || undefined
455
446
  : undefined;
456
- violations.push({ rule, entityId, description, suggestion, source });
447
+ violations.push({
448
+ rule,
449
+ entityId,
450
+ description,
451
+ suggestion,
452
+ ...(source !== undefined ? { source } : {}),
453
+ });
457
454
  }
458
455
  return violations;
459
456
  }
460
- /**
461
- * Strip quotes from a value (single or double).
462
- */
463
457
  function stripQuotes(value) {
464
458
  if (value.startsWith("'") && value.endsWith("'")) {
465
459
  return value.slice(1, -1);
@@ -1 +1 @@
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"}
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;YAsCd,YAAY;IA0CpB,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;IA2C1B,OAAO,CAAC,WAAW;IAsGnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,cAAc;IA+BtB,SAAS,IAAI,OAAO;IAIpB,MAAM,IAAI,MAAM;IAIV,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBjC"}
package/dist/prolog.js CHANGED
@@ -20,36 +20,41 @@ import { existsSync } from "node:fs";
20
20
  import { createRequire } from "node:module";
21
21
  import path from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
+ import { getKbPlPathOverride, isPrologDebugEnabled } from "./env.js";
23
24
  const importMetaDir = path.dirname(fileURLToPath(import.meta.url));
24
25
  const require = createRequire(import.meta.url);
25
26
  export function resolveKbPlPath() {
26
27
  // implements REQ-009
27
- const overrideKbPath = process.env.KIBI_KB_PL_PATH;
28
- if (overrideKbPath && existsSync(overrideKbPath)) {
28
+ const overrideKbPath = getKbPlPathOverride();
29
+ if (overrideKbPath) {
29
30
  return overrideKbPath;
30
31
  }
32
+ // Strategy 1: Resolve kibi-core package and derive the source file path.
33
+ // This works in npm workspaces where kibi-core is a direct dependency of kibi-cli.
31
34
  try {
32
- const installedKbPl = require.resolve("kibi-core/src/kb.pl");
33
- if (existsSync(installedKbPl))
34
- return installedKbPl;
35
+ try {
36
+ // First try: resolve as a file within the package
37
+ return require.resolve("kibi-core/src/kb.pl");
38
+ }
39
+ catch {
40
+ // Fall back: resolve package entry point and derive path
41
+ const coreMain = require.resolve("kibi-core");
42
+ const coreDir = path.dirname(coreMain);
43
+ return path.join(coreDir, "src", "kb.pl");
44
+ }
35
45
  }
36
46
  catch {
37
- // require.resolve not available or package not installed
47
+ // Both resolution strategies failed
38
48
  }
39
- const startDirs = [importMetaDir, process.cwd()];
40
- for (const startDir of startDirs) {
41
- let currentDir = path.resolve(startDir);
42
- while (true) {
43
- const candidate = path.join(currentDir, "packages", "core", "src", "kb.pl");
44
- if (existsSync(candidate)) {
45
- return candidate;
46
- }
47
- const parentDir = path.dirname(currentDir);
48
- if (parentDir === currentDir) {
49
- break;
50
- }
51
- currentDir = parentDir;
49
+ // Strategy 2: Walk up from importMetaDir looking for packages/core/src/kb.pl.
50
+ // This works when running from the source tree (e.g., during development).
51
+ let currentDir = importMetaDir;
52
+ while (currentDir !== path.dirname(currentDir)) {
53
+ const candidate = path.join(currentDir, "packages", "core", "src", "kb.pl");
54
+ if (existsSync(candidate)) {
55
+ return candidate;
52
56
  }
57
+ currentDir = path.dirname(currentDir);
53
58
  }
54
59
  throw new Error("Unable to resolve kb.pl. Expected kibi-core to be installed (node_modules) " +
55
60
  "or to be running inside the monorepo checkout.");
@@ -88,6 +93,7 @@ export class PrologProcess {
88
93
  this.process.stderr.on("data", (chunk) => {
89
94
  this.errorBuffer += chunk.toString();
90
95
  });
96
+ this.process.stdin.write("true.\n");
91
97
  if (!this.onProcessExit) {
92
98
  this.onProcessExit = () => {
93
99
  void this.terminate();
@@ -109,13 +115,14 @@ export class PrologProcess {
109
115
  if (this.errorBuffer.includes("ERROR")) {
110
116
  throw new Error(`Failed to load kb module: ${this.translateError(this.errorBuffer)}`);
111
117
  }
112
- // If stdout or stderr shows any output, assume ready.
113
- if (this.outputBuffer.length > 0 || this.errorBuffer.length > 0) {
114
- break;
118
+ if (this.outputBuffer.includes("true.")) {
119
+ this.outputBuffer = "";
120
+ this.errorBuffer = "";
121
+ return;
115
122
  }
116
123
  // brief pause
117
124
  // eslint-disable-next-line no-await-in-loop
118
- await new Promise((resolve) => setTimeout(resolve, 50));
125
+ await new Promise((resolve) => setTimeout(resolve, 10));
119
126
  }
120
127
  // Final sanity check
121
128
  if (this.errorBuffer.includes("ERROR")) {
@@ -154,7 +161,7 @@ export class PrologProcess {
154
161
  const runInteractiveQuery = async () => {
155
162
  this.outputBuffer = "";
156
163
  this.errorBuffer = "";
157
- const debug = !!process.env.KIBI_PROLOG_DEBUG;
164
+ const debug = isPrologDebugEnabled();
158
165
  const normalizedGoal = this.normalizeGoal(goal);
159
166
  const wrappedGoal = /^once\s*\(/.test(normalizedGoal)
160
167
  ? normalizedGoal
@@ -282,6 +289,14 @@ export class PrologProcess {
282
289
  }
283
290
  const attachMatch = trimmedGoal.match(/^kb_attach\('(.+)'\)$/);
284
291
  if (attachMatch) {
292
+ const attachPath = attachMatch[1] ?? null;
293
+ if (!attachPath) {
294
+ return {
295
+ success: false,
296
+ bindings: {},
297
+ error: "Invalid KB attach path",
298
+ };
299
+ }
285
300
  if (this.attachedKbPath !== null) {
286
301
  return {
287
302
  success: false,
@@ -291,7 +306,7 @@ export class PrologProcess {
291
306
  }
292
307
  const attachResult = this.execOneShot(trimmedGoal, null);
293
308
  if (attachResult.success) {
294
- this.attachedKbPath = attachMatch[1];
309
+ this.attachedKbPath = attachPath;
295
310
  }
296
311
  return attachResult;
297
312
  }
@@ -389,7 +404,9 @@ export class PrologProcess {
389
404
  const match = line.match(/^([A-Z_][A-Za-z0-9_]*)\s*=\s*(.+)\.?\s*$/);
390
405
  if (match) {
391
406
  const [, varName, value] = match;
392
- bindings[varName] = value.trim().replace(/\.$/, "").replace(/,$/, "");
407
+ if (varName !== undefined && value !== undefined) {
408
+ bindings[varName] = value.trim().replace(/\.$/, "").replace(/,$/, "");
409
+ }
393
410
  }
394
411
  }
395
412
  return bindings;
@@ -409,12 +426,11 @@ export class PrologProcess {
409
426
  if (errorText.includes("timeout_error")) {
410
427
  return `Operation exceeded ${this.timeout / 1000}s timeout`;
411
428
  }
412
- const simpleError = errorText
429
+ const simpleError = (errorText
413
430
  .replace(/ERROR:\s*/g, "")
414
431
  .replace(/^\*\*.*\*\*$/gm, "")
415
432
  .replace(/^\s+/gm, "")
416
- .split("\n")[0]
417
- .trim();
433
+ .split("\n")[0] ?? "").trim();
418
434
  return simpleError || "Unknown error";
419
435
  }
420
436
  isRunning() {
@@ -46,6 +46,9 @@ declare const entitySchema: {
46
46
  text_ref: {
47
47
  type: string;
48
48
  };
49
+ sourceFile: {
50
+ type: string;
51
+ };
49
52
  type: {
50
53
  type: string;
51
54
  enum: string[];
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"entity.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/entity.ts"],"names":[],"mappings":"AAwBA,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqHjB,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -55,6 +55,7 @@ const entitySchema = {
55
55
  severity: { type: "string" },
56
56
  links: { type: "array", items: { type: "string" } },
57
57
  text_ref: { type: "string" },
58
+ sourceFile: { type: "string" },
58
59
  type: {
59
60
  type: "string",
60
61
  enum: [
@@ -1 +1 @@
1
- {"version":3,"file":"relationship.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/relationship.ts"],"names":[],"mappings":"AAkBA,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCvB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"relationship.d.ts","sourceRoot":"","sources":["../../../src/public/schemas/relationship.ts"],"names":[],"mappings":"AAkBA,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCvB,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
@@ -29,6 +29,7 @@ const relationshipSchema = {
29
29
  "validates",
30
30
  "implements",
31
31
  "covered_by",
32
+ "executable_for",
32
33
  "constrained_by",
33
34
  "constrains",
34
35
  "requires_property",
@@ -11,6 +11,14 @@ export interface QueryResult {
11
11
  entities: Array<Record<string, unknown>>;
12
12
  totalCount: number;
13
13
  }
14
+ interface QueryCodecDeps {
15
+ escapeAtom: (value: string) => string;
16
+ parseListOfLists: (value: string) => string[][];
17
+ parseEntityFromList: (value: string[]) => Record<string, unknown>;
18
+ parseEntityFromBinding: (value: string) => Record<string, unknown>;
19
+ }
20
+ export declare function _setQueryCodecDepsForTests(deps: QueryCodecDeps): void;
21
+ export declare function _resetQueryCodecDepsForTests(): void;
14
22
  export declare const VALID_ENTITY_TYPES: string[];
15
23
  /**
16
24
  * Build a Prolog query goal from filters.
@@ -32,4 +40,5 @@ export declare function getInvalidTypeError(type: string): string;
32
40
  * Build human-readable summary text for query results.
33
41
  */
34
42
  export declare function buildQuerySummaryText(result: QueryResult, filters: QueryFilters): string;
43
+ export {};
35
44
  //# sourceMappingURL=service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/query/service.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAQlD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,kBAAkB,UAS9B,CAAC;AAEF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAqClE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAmCtB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,YAAY,GACpB,MAAM,CAkBR"}
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/query/service.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAQlD,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,cAAc;IACtB,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,EAAE,EAAE,CAAC;IAChD,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClE,sBAAsB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpE;AAWD,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,cAAc,GAAG,IAAI,CAGrE;AAED,wBAAgB,4BAA4B,IAAI,IAAI,CAGnD;AAED,eAAO,MAAM,kBAAkB,UAS9B,CAAC;AAEF;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAsClE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,WAAW,CAAC,CAwCtB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,YAAY,GACpB,MAAM,CAkBR"}
@@ -8,6 +8,21 @@
8
8
  * (at your option) any later version.
9
9
  */
10
10
  import { escapeAtom, parseEntityFromBinding, parseEntityFromList, parseListOfLists, } from "../prolog/codec.js";
11
+ const defaultCodecDeps = {
12
+ escapeAtom,
13
+ parseListOfLists,
14
+ parseEntityFromList,
15
+ parseEntityFromBinding,
16
+ };
17
+ let codecDeps = defaultCodecDeps;
18
+ export function _setQueryCodecDepsForTests(deps) {
19
+ // implements REQ-003
20
+ codecDeps = deps;
21
+ }
22
+ export function _resetQueryCodecDepsForTests() {
23
+ // implements REQ-003
24
+ codecDeps = defaultCodecDeps;
25
+ }
11
26
  export const VALID_ENTITY_TYPES = [
12
27
  "req",
13
28
  "scenario",
@@ -22,33 +37,34 @@ export const VALID_ENTITY_TYPES = [
22
37
  * Build a Prolog query goal from filters.
23
38
  */
24
39
  export function buildEntityQueryGoal(filters) {
40
+ // implements REQ-003
25
41
  const { type, id, sourceFile, tags } = filters;
26
42
  if (sourceFile) {
27
- const safeSource = escapeAtom(sourceFile);
43
+ const safeSource = codecDeps.escapeAtom(sourceFile);
28
44
  if (type) {
29
- const safeType = escapeAtom(type);
45
+ const safeType = codecDeps.escapeAtom(type);
30
46
  return `findall([Id,'${safeType}',Props], (kb_entities_by_source('${safeSource}', SourceIds), member(Id, SourceIds), kb_entity(Id, '${safeType}', Props)), Results)`;
31
47
  }
32
48
  return `findall([Id,Type,Props], (kb_entities_by_source('${safeSource}', SourceIds), member(Id, SourceIds), kb_entity(Id, Type, Props)), Results)`;
33
49
  }
34
50
  if (id && type) {
35
- const safeId = escapeAtom(id);
36
- const safeType = escapeAtom(type);
51
+ const safeId = codecDeps.escapeAtom(id);
52
+ const safeType = codecDeps.escapeAtom(type);
37
53
  return `findall(['${safeId}','${safeType}',Props], kb_entity('${safeId}', '${safeType}', Props), Results)`;
38
54
  }
39
55
  if (id) {
40
- const safeId = escapeAtom(id);
56
+ const safeId = codecDeps.escapeAtom(id);
41
57
  return `findall(['${safeId}',Type,Props], kb_entity('${safeId}', Type, Props), Results)`;
42
58
  }
43
59
  if (tags && tags.length > 0) {
44
60
  if (type) {
45
- const safeType = escapeAtom(type);
61
+ const safeType = codecDeps.escapeAtom(type);
46
62
  return `findall([Id,'${safeType}',Props], kb_entity(Id, '${safeType}', Props), Results)`;
47
63
  }
48
64
  return "findall([Id,Type,Props], kb_entity(Id, Type, Props), Results)";
49
65
  }
50
66
  if (type) {
51
- const safeType = escapeAtom(type);
67
+ const safeType = codecDeps.escapeAtom(type);
52
68
  return `findall([Id,'${safeType}',Props], kb_entity(Id, '${safeType}', Props), Results)`;
53
69
  }
54
70
  return "findall([Id,Type,Props], kb_entity(Id, Type, Props), Results)";
@@ -57,20 +73,21 @@ export function buildEntityQueryGoal(filters) {
57
73
  * Execute a filtered entity query against the KB.
58
74
  */
59
75
  export async function queryEntities(prolog, filters) {
76
+ // implements REQ-003
60
77
  const { tags, limit = 100, offset = 0 } = filters;
61
78
  const goal = buildEntityQueryGoal(filters);
62
79
  const queryResult = await prolog.query(goal);
63
80
  let entities = [];
64
81
  if (queryResult.success) {
65
82
  if (queryResult.bindings.Results) {
66
- const entitiesData = parseListOfLists(queryResult.bindings.Results);
83
+ const entitiesData = codecDeps.parseListOfLists(queryResult.bindings.Results);
67
84
  for (const data of entitiesData) {
68
- const entity = parseEntityFromList(data);
85
+ const entity = codecDeps.parseEntityFromList(data);
69
86
  entities.push(entity);
70
87
  }
71
88
  }
72
89
  else if (queryResult.bindings.Result) {
73
- const entity = parseEntityFromBinding(queryResult.bindings.Result);
90
+ const entity = codecDeps.parseEntityFromBinding(queryResult.bindings.Result);
74
91
  entities = [entity];
75
92
  }
76
93
  }
@@ -38,6 +38,15 @@
38
38
  "severity": { "type": "string" },
39
39
  "links": { "type": "array", "items": { "type": "string" } },
40
40
  "text_ref": { "type": "string" },
41
+ "sourceFile": { "type": "string" },
42
+ "verification_scope": {
43
+ "type": "string",
44
+ "enum": ["unit", "integration", "end_to_end"]
45
+ },
46
+ "verification_perspective": {
47
+ "type": "string",
48
+ "enum": ["internal", "consumer"]
49
+ },
41
50
  "type": {
42
51
  "type": "string",
43
52
  "enum": [
@@ -87,6 +96,20 @@
87
96
  "type"
88
97
  ],
89
98
  "allOf": [
99
+ {
100
+ "if": {
101
+ "properties": { "type": { "const": "test" } }
102
+ },
103
+ "then": {},
104
+ "else": {
105
+ "not": {
106
+ "anyOf": [
107
+ { "required": ["verification_scope"] },
108
+ { "required": ["verification_perspective"] }
109
+ ]
110
+ }
111
+ }
112
+ },
90
113
  {
91
114
  "if": {
92
115
  "properties": { "type": { "const": "fact" } }
@@ -1 +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"}
1
+ {"version":3,"file":"search-ranking.d.ts","sourceRoot":"","sources":["../src/search-ranking.ts"],"names":[],"mappings":"AAGA,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"}