kibi-cli 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/cli.js +62 -0
  2. package/dist/commands/aggregated-checks.js +4 -6
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +26 -24
  5. package/dist/commands/coverage.d.ts +12 -0
  6. package/dist/commands/coverage.d.ts.map +1 -0
  7. package/dist/commands/coverage.js +24 -0
  8. package/dist/commands/discovery-shared.d.ts +11 -0
  9. package/dist/commands/discovery-shared.d.ts.map +1 -0
  10. package/dist/commands/discovery-shared.js +280 -0
  11. package/dist/commands/doctor.js +8 -8
  12. package/dist/commands/gaps.d.ts +12 -0
  13. package/dist/commands/gaps.d.ts.map +1 -0
  14. package/dist/commands/gaps.js +28 -0
  15. package/dist/commands/graph.d.ts +13 -0
  16. package/dist/commands/graph.d.ts.map +1 -0
  17. package/dist/commands/graph.js +35 -0
  18. package/dist/commands/query.d.ts.map +1 -1
  19. package/dist/commands/query.js +2 -8
  20. package/dist/commands/search.d.ts +9 -0
  21. package/dist/commands/search.d.ts.map +1 -0
  22. package/dist/commands/search.js +38 -0
  23. package/dist/commands/status.d.ts +6 -0
  24. package/dist/commands/status.d.ts.map +1 -0
  25. package/dist/commands/status.js +9 -0
  26. package/dist/commands/sync.d.ts.map +1 -1
  27. package/dist/commands/sync.js +0 -15
  28. package/dist/diagnostics.d.ts.map +1 -1
  29. package/dist/diagnostics.js +0 -2
  30. package/dist/extractors/manifest.d.ts +2 -0
  31. package/dist/extractors/manifest.d.ts.map +1 -1
  32. package/dist/extractors/manifest.js +1 -0
  33. package/dist/extractors/markdown.d.ts.map +1 -1
  34. package/dist/extractors/markdown.js +9 -1
  35. package/dist/public/extractors/symbols-coordinator.d.ts.map +1 -1
  36. package/dist/public/extractors/symbols-coordinator.js +1 -2
  37. package/dist/public/prolog/index.d.ts.map +1 -1
  38. package/dist/public/prolog/index.js +1 -2
  39. package/dist/public/schemas/entity.d.ts.map +1 -1
  40. package/dist/public/schemas/entity.js +1 -3
  41. package/dist/public/schemas/relationship.d.ts.map +1 -1
  42. package/dist/public/schemas/relationship.js +1 -3
  43. package/dist/search-ranking.d.ts +9 -0
  44. package/dist/search-ranking.d.ts.map +1 -0
  45. package/dist/search-ranking.js +143 -0
  46. package/dist/traceability/git-staged.d.ts.map +1 -1
  47. package/dist/traceability/git-staged.js +33 -7
  48. package/dist/traceability/symbol-extract.d.ts +6 -1
  49. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  50. package/dist/traceability/symbol-extract.js +62 -34
  51. package/dist/traceability/temp-kb.d.ts.map +1 -1
  52. package/dist/traceability/temp-kb.js +4 -3
  53. package/dist/traceability/validate.d.ts.map +1 -1
  54. package/dist/traceability/validate.js +8 -7
  55. package/package.json +10 -2
  56. package/src/public/extractors/symbols-coordinator.ts +1 -2
  57. package/src/public/prolog/index.ts +1 -2
  58. package/src/public/schemas/entity.ts +1 -3
  59. package/src/public/schemas/relationship.ts +1 -3
package/dist/cli.js CHANGED
@@ -19,10 +19,15 @@ import { readFileSync } from "node:fs";
19
19
  import { Command } from "commander";
20
20
  import { branchEnsureCommand } from "./commands/branch.js";
21
21
  import { checkCommand } from "./commands/check.js";
22
+ import { coverageCommand } from "./commands/coverage.js";
22
23
  import { doctorCommand } from "./commands/doctor.js";
24
+ import { gapsCommand } from "./commands/gaps.js";
23
25
  import { gcCommand } from "./commands/gc.js";
26
+ import { graphCommand } from "./commands/graph.js";
24
27
  import { initCommand } from "./commands/init.js";
25
28
  import { queryCommand } from "./commands/query.js";
29
+ import { searchCommand } from "./commands/search.js";
30
+ import { statusCommand } from "./commands/status.js";
26
31
  import { syncCommand } from "./commands/sync.js";
27
32
  const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
28
33
  const VERSION = packageJson.version ?? "0.1.0";
@@ -59,6 +64,63 @@ program
59
64
  .action(async (type, options) => {
60
65
  await queryCommand(type, options);
61
66
  });
67
+ program
68
+ .command("search [query]")
69
+ .description("Search knowledge base metadata and markdown content")
70
+ .option("--type <type>", "Filter by entity type")
71
+ .option("--format <format>", "Output format: json|table", "table")
72
+ .option("--limit <n>", "Limit results", "20")
73
+ .option("--offset <n>", "Skip results", "0")
74
+ .action(async (query, options) => {
75
+ await searchCommand(query, options);
76
+ });
77
+ program
78
+ .command("status")
79
+ .description("Show KB snapshot and freshness metadata")
80
+ .option("--format <format>", "Output format: json|table", "table")
81
+ .action(async (options) => {
82
+ await statusCommand(options);
83
+ });
84
+ program
85
+ .command("gaps [type]")
86
+ .description("Find entities missing or present on selected relationships")
87
+ .option("--missing-rel <rels>", "Comma-separated missing relationship filters")
88
+ .option("--present-rel <rels>", "Comma-separated present relationship filters")
89
+ .option("--tag <tags>", "Comma-separated tag filter")
90
+ .option("--source <path>", "Source file substring filter")
91
+ .option("--limit <n>", "Limit results", "100")
92
+ .option("--offset <n>", "Skip results", "0")
93
+ .option("--format <format>", "Output format: json|table", "table")
94
+ .action(async (type, options) => {
95
+ await gapsCommand(type, options);
96
+ });
97
+ program
98
+ .command("coverage")
99
+ .description("Generate curated coverage reports")
100
+ .option("--by <group>", "Coverage mode: req|symbol|type", "req")
101
+ .option("--tag <tags>", "Comma-separated tag filter")
102
+ .option("--include-passing", "Include passing rows", false)
103
+ .option("--no-include-transitive", "Disable transitive symbol coverage")
104
+ .option("--limit <n>", "Limit results", "100")
105
+ .option("--offset <n>", "Skip results", "0")
106
+ .option("--format <format>", "Output format: json|table", "table")
107
+ .action(async (options) => {
108
+ await coverageCommand(options);
109
+ });
110
+ program
111
+ .command("graph")
112
+ .description("Traverse the KB graph from one or more seed IDs")
113
+ .option("--from <ids>", "Comma-separated seed IDs")
114
+ .option("--relationships <rels>", "Comma-separated relationship filter")
115
+ .option("--direction <direction>", "Direction: outgoing|incoming|both", "outgoing")
116
+ .option("--depth <n>", "Traversal depth", "1")
117
+ .option("--entity-types <types>", "Comma-separated entity type filter")
118
+ .option("--max-nodes <n>", "Maximum node count", "200")
119
+ .option("--max-edges <n>", "Maximum edge count", "500")
120
+ .option("--format <format>", "Output format: json|table", "table")
121
+ .action(async (options) => {
122
+ await graphCommand(options);
123
+ });
62
124
  program
63
125
  .command("check")
64
126
  .description("Check KB consistency and integrity")
@@ -23,8 +23,7 @@ export async function runAggregatedChecks(prolog, rulesAllowlist, requireAdr = f
23
23
  try {
24
24
  const result = await prolog.query(query);
25
25
  if (!result.success) {
26
- console.warn("Aggregated checks query failed, falling back to individual checks");
27
- return [];
26
+ throw new Error(`Aggregated checks query failed: ${result.error || "Unknown error"}`);
28
27
  }
29
28
  let violationsDict;
30
29
  try {
@@ -39,8 +38,7 @@ export async function runAggregatedChecks(prolog, rulesAllowlist, requireAdr = f
39
38
  violationsDict = parsed;
40
39
  }
41
40
  catch (parseError) {
42
- console.warn("Failed to parse violations JSON:", parseError);
43
- return [];
41
+ throw new Error(`Failed to parse violations JSON: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
44
42
  }
45
43
  for (const ruleViolations of Object.values(violationsDict)) {
46
44
  for (const v of ruleViolations) {
@@ -59,7 +57,7 @@ export async function runAggregatedChecks(prolog, rulesAllowlist, requireAdr = f
59
57
  return violations;
60
58
  }
61
59
  catch (error) {
62
- console.warn("Error running aggregated checks:", error);
63
- return [];
60
+ const message = error instanceof Error ? error.message : String(error);
61
+ throw new Error(`Error running aggregated checks: ${message}`);
64
62
  }
65
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,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;AAGD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0QvE"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAgDA,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,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;AAGD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA+RvE"}
@@ -16,24 +16,24 @@
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
18
  import * as path from "node:path";
19
+ import { extractFromManifest } from "../extractors/manifest.js";
19
20
  import { PrologProcess } from "../prolog.js";
20
21
  import { escapeAtom } from "../prolog/codec.js";
21
22
  import { getStagedFiles } from "../traceability/git-staged.js";
22
23
  import { validateStagedMarkdown } from "../traceability/markdown-validate.js";
23
- import { extractSymbolsFromStagedFile } from "../traceability/symbol-extract.js";
24
+ import { extractSymbolsFromStagedFile, } from "../traceability/symbol-extract.js";
24
25
  import { cleanupTempKb, consultOverlay, createOverlayFacts, createTempKb, } from "../traceability/temp-kb.js";
25
26
  import { formatViolations as formatStagedViolations, validateStagedSymbols, } from "../traceability/validate.js";
26
27
  import { loadConfig } from "../utils/config.js";
27
28
  import { RULES, getEffectiveRules, } from "../utils/rule-registry.js";
28
29
  import { runAggregatedChecks } from "./aggregated-checks.js";
29
30
  import { getCurrentBranch } from "./init-helpers.js";
31
+ import { discoverSourceFiles } from "./sync/discovery.js";
30
32
  // implements REQ-006
31
33
  export async function checkCommand(options) {
32
34
  let prolog = null;
33
35
  let attached = false;
34
36
  try {
35
- // Resolve KB path with priority:
36
- // --kb-path > git branch --show-current > KIBI_BRANCH env > develop > main
37
37
  let resolvedKbPath = "";
38
38
  if (options.kbPath) {
39
39
  resolvedKbPath = options.kbPath;
@@ -54,13 +54,32 @@ export async function checkCommand(options) {
54
54
  // fallback to main if develop isn't present? keep path consistent
55
55
  resolvedKbPath = path.join(process.cwd(), ".kb/branches", branch || "main");
56
56
  }
57
- // If --staged mode requested, run staged-symbol traceability gate.
58
- // We skip creating the main prolog session entirely in this path.
59
57
  if (options.staged) {
60
58
  const minLinks = options.minLinks ? Number(options.minLinks) : 1;
61
59
  let tempCtx = null;
62
60
  try {
63
- // Get staged files
61
+ const config = loadConfig(process.cwd());
62
+ const manifestLookup = new Map();
63
+ const { manifestFiles } = await discoverSourceFiles(process.cwd(), config.paths);
64
+ for (const manifestPath of manifestFiles) {
65
+ try {
66
+ const entries = extractFromManifest(manifestPath);
67
+ for (const entry of entries) {
68
+ // Prefer the per-symbol sourceFile; fall back to entity.source or manifest path
69
+ const sourceFile = entry.sourceFile || entry.entity.source || manifestPath;
70
+ const key = `${sourceFile}:${entry.entity.title}`;
71
+ // Extract requirement links (implements relationships to REQ-*)
72
+ const links = entry.relationships
73
+ .filter((r) => r.type === "implements" &&
74
+ r.to.match(/^[A-Z][A-Z0-9\-_]*$/))
75
+ .map((r) => r.to);
76
+ manifestLookup.set(key, { id: entry.entity.id, links });
77
+ }
78
+ }
79
+ catch {
80
+ // Ignore manifest parsing errors
81
+ }
82
+ }
64
83
  const stagedFiles = getStagedFiles();
65
84
  if (!stagedFiles || stagedFiles.length === 0) {
66
85
  console.log("No staged files found.");
@@ -89,7 +108,7 @@ export async function checkCommand(options) {
89
108
  const allSymbols = [];
90
109
  for (const f of codeFiles) {
91
110
  try {
92
- const symbols = extractSymbolsFromStagedFile(f);
111
+ const symbols = extractSymbolsFromStagedFile(f, manifestLookup);
93
112
  if (symbols?.length) {
94
113
  allSymbols.push(...symbols);
95
114
  }
@@ -108,12 +127,10 @@ export async function checkCommand(options) {
108
127
  }
109
128
  // Create temp KB
110
129
  tempCtx = await createTempKb(resolvedKbPath);
111
- // Write overlay facts THEN consult so Prolog sees the changed_symbol facts
112
130
  const overlayFacts = createOverlayFacts(allSymbols);
113
131
  const fs = await import("node:fs/promises");
114
132
  await fs.writeFile(tempCtx.overlayPath, overlayFacts, "utf8");
115
133
  await consultOverlay(tempCtx);
116
- // Validate staged symbols using the temp KB prolog session
117
134
  const violationsRaw = await validateStagedSymbols({
118
135
  minLinks,
119
136
  prolog: tempCtx.prolog,
@@ -153,13 +170,11 @@ export async function checkCommand(options) {
153
170
  }
154
171
  attached = true;
155
172
  const violations = [];
156
- // Load config to get rule enablement settings
157
173
  const config = loadConfig(process.cwd());
158
174
  const checksConfig = config.checks ?? {
159
175
  rules: Object.fromEntries(RULES.map((r) => [r.name, true])),
160
176
  symbolTraceability: { requireAdr: false },
161
177
  };
162
- // Get effective rules based on config and CLI --rules filter
163
178
  const effectiveRules = getEffectiveRules(checksConfig.rules, options.rules);
164
179
  // Helper to conditionally run a check by name
165
180
  async function runCheck(name, fn, ...args) {
@@ -250,7 +265,6 @@ export async function checkCommand(options) {
250
265
  }
251
266
  async function checkMustPriorityCoverage(prolog) {
252
267
  const violations = [];
253
- // Find all must-priority requirements
254
268
  const mustReqs = await findMustPriorityReqs(prolog);
255
269
  for (const reqId of mustReqs) {
256
270
  const entityResult = await prolog.query(`kb_entity('${reqId}', req, Props)`);
@@ -324,9 +338,7 @@ async function getAllEntityIds(prolog, type) {
324
338
  }
325
339
  async function checkNoDanglingRefs(prolog) {
326
340
  const violations = [];
327
- // Get all entity IDs once
328
341
  const allEntityIds = new Set(await getAllEntityIds(prolog));
329
- // Get all relationships by querying all known relationship types
330
342
  const relTypes = [
331
343
  "depends_on",
332
344
  "verified_by",
@@ -379,7 +391,6 @@ async function checkNoDanglingRefs(prolog) {
379
391
  }
380
392
  async function checkNoCycles(prolog) {
381
393
  const violations = [];
382
- // Get all depends_on relationships
383
394
  const depsResult = await prolog.query("findall([From,To], kb_relationship(depends_on, From, To), Deps)");
384
395
  if (!depsResult.success || !depsResult.bindings.Deps) {
385
396
  return violations;
@@ -393,7 +404,6 @@ async function checkNoCycles(prolog) {
393
404
  if (!content) {
394
405
  return violations;
395
406
  }
396
- // Build adjacency map
397
407
  const graph = new Map();
398
408
  const depMatches = content.matchAll(/\[([^,]+),([^\]]+)\]/g);
399
409
  for (const depMatch of depMatches) {
@@ -407,7 +417,6 @@ async function checkNoCycles(prolog) {
407
417
  fromList.push(to);
408
418
  }
409
419
  }
410
- // DFS to detect cycles
411
420
  const visited = new Set();
412
421
  const recStack = new Set();
413
422
  function hasCycleDFS(node, path) {
@@ -429,7 +438,6 @@ async function checkNoCycles(prolog) {
429
438
  recStack.delete(node);
430
439
  return null;
431
440
  }
432
- // Check each node for cycles
433
441
  for (const node of graph.keys()) {
434
442
  if (!visited.has(node)) {
435
443
  const cyclePath = hasCycleDFS(node, []);
@@ -472,15 +480,12 @@ async function checkRequiredFields(prolog, allEntityIds) {
472
480
  for (const entityId of allEntityIds) {
473
481
  const result = await prolog.query(`kb_entity('${entityId}', Type, Props)`);
474
482
  if (result.success && result.bindings.Props) {
475
- // Parse properties list: [key1=value1, key2=value2, ...]
476
483
  const propsStr = result.bindings.Props;
477
484
  const propKeys = new Set();
478
- // Extract keys from Props
479
485
  const keyMatches = propsStr.matchAll(/(\w+)\s*=/g);
480
486
  for (const match of keyMatches) {
481
487
  propKeys.add(match[1]);
482
488
  }
483
- // Check for missing required fields
484
489
  for (const field of required) {
485
490
  if (!propKeys.has(field)) {
486
491
  violations.push({
@@ -515,7 +520,6 @@ async function checkDeprecatedAdrs(prolog) {
515
520
  .split(",")
516
521
  .map((id) => id.trim().replace(/^'|'$/g, ""));
517
522
  for (const adrId of adrIds) {
518
- // Get source for better error message
519
523
  const entityResult = await prolog.query(`kb_entity('${adrId}', adr, Props)`);
520
524
  let source = "";
521
525
  if (entityResult.success && entityResult.bindings.Props) {
@@ -585,10 +589,8 @@ async function checkSymbolTraceability(prolog, requireAdr) {
585
589
  if (!result.success || !result.bindings.Violations) {
586
590
  return violations;
587
591
  }
588
- // Parse the violations from Prolog format
589
592
  const violationsStr = result.bindings.Violations;
590
593
  if (violationsStr && violationsStr !== "[]") {
591
- // Parse each violation term
592
594
  const violationRegex = /violation\(([^,]+),'?([^',]+)'?,([^,]+),([^,]+),'?([^']*)'?\)/g;
593
595
  let match;
594
596
  do {
@@ -0,0 +1,12 @@
1
+ interface CoverageOptions {
2
+ by?: "req" | "symbol" | "type";
3
+ tag?: string;
4
+ includePassing?: boolean;
5
+ includeTransitive?: boolean;
6
+ limit?: string;
7
+ offset?: string;
8
+ format?: "json" | "table";
9
+ }
10
+ export declare function coverageCommand(options: CoverageOptions): Promise<void>;
11
+ export {};
12
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/commands/coverage.ts"],"names":[],"mappings":"AAQA,UAAU,eAAe;IACvB,EAAE,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAGD,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAgC7E"}
@@ -0,0 +1,24 @@
1
+ import { escapeAtom } from "../prolog/codec.js";
2
+ import { printDiscoveryResult, resolveCurrentKbPath, runJsonModuleQuery, withPrologProcess, } from "./discovery-shared.js";
3
+ // implements REQ-002, REQ-003
4
+ export async function coverageCommand(options) {
5
+ await withPrologProcess(async (prolog) => {
6
+ const kbPath = await resolveCurrentKbPath();
7
+ const by = options.by || "req";
8
+ const tags = options.tag
9
+ ? `[${options.tag
10
+ .split(",")
11
+ .map((item) => item.trim())
12
+ .filter(Boolean)
13
+ .map((item) => `'${escapeAtom(item)}'`)
14
+ .join(",")}]`
15
+ : "[]";
16
+ const includePassing = options.includePassing ?? false;
17
+ const includeTransitive = options.includeTransitive ?? true;
18
+ const limit = Number.parseInt(options.limit || "100", 10);
19
+ const offset = Number.parseInt(options.offset || "0", 10);
20
+ const result = await runJsonModuleQuery(prolog, "discovery.pl", `discovery:coverage_report_json('${by}', ${tags}, ${includePassing}, ${includeTransitive}, ${limit}, ${offset}, JsonString)`, "coverage query failed", kbPath);
21
+ const summary = (result.summary ?? {});
22
+ printDiscoveryResult(options.format, result, `Coverage summary: ${summary.fullyCovered ?? 0} fully covered out of ${summary.total ?? 0}.`);
23
+ });
24
+ }
@@ -0,0 +1,11 @@
1
+ import { PrologProcess } from "../prolog.js";
2
+ export interface DiscoveryCommandOptions {
3
+ format?: "json" | "table";
4
+ }
5
+ export declare function withAttachedBranchProlog<T>(callback: (prolog: PrologProcess) => Promise<T>): Promise<T>;
6
+ export declare function withPrologProcess<T>(callback: (prolog: PrologProcess) => Promise<T>): Promise<T>;
7
+ export declare function resolveCurrentKbPath(): Promise<string>;
8
+ export declare function resolveCoreModulePath(fileName: string): string;
9
+ export declare function runJsonModuleQuery<T>(prolog: PrologProcess, fileName: string, goal: string, errorLabel: string, kbPath?: string): Promise<T>;
10
+ export declare function printDiscoveryResult(format: "json" | "table" | undefined, structured: unknown, fallbackText: string): void;
11
+ //# sourceMappingURL=discovery-shared.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery-shared.d.ts","sourceRoot":"","sources":["../../src/commands/discovery-shared.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAmB,MAAM,cAAc,CAAC;AAI9D,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAGD,wBAAsB,wBAAwB,CAAC,CAAC,EAC9C,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC9C,OAAO,CAAC,CAAC,CAAC,CAsCZ;AAGD,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC9C,OAAO,CAAC,CAAC,CAAC,CAcZ;AAGD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D;AAGD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE9D;AAGD,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,CAAC,CAsBZ;AAGD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,EACpC,UAAU,EAAE,OAAO,EACnB,YAAY,EAAE,MAAM,GACnB,IAAI,CAQN"}
@@ -0,0 +1,280 @@
1
+ import path from "node:path";
2
+ import Table from "cli-table3";
3
+ import { PrologProcess, resolveKbPlPath } from "../prolog.js";
4
+ import { escapeAtom } from "../prolog/codec.js";
5
+ import { getCurrentBranch } from "./init-helpers.js";
6
+ // implements REQ-003
7
+ export async function withAttachedBranchProlog(callback) {
8
+ let prolog = null;
9
+ let attached = false;
10
+ try {
11
+ prolog = new PrologProcess({ timeout: 120000 });
12
+ await prolog.start();
13
+ await prolog.query("set_prolog_flag(answer_write_options, [max_depth(0), spacing(next_argument)])");
14
+ let branch;
15
+ try {
16
+ branch = process.env.KIBI_BRANCH || (await getCurrentBranch(process.cwd()));
17
+ }
18
+ catch {
19
+ branch = process.env.KIBI_BRANCH || "main";
20
+ }
21
+ const kbPath = path.join(process.cwd(), ".kb/branches", branch);
22
+ const attachResult = await prolog.query(`kb_attach('${escapeAtom(kbPath)}')`);
23
+ if (!attachResult.success) {
24
+ throw new Error(`Failed to attach KB: ${attachResult.error || "Unknown error"}`);
25
+ }
26
+ attached = true;
27
+ return await callback(prolog);
28
+ }
29
+ finally {
30
+ if (prolog) {
31
+ if (attached) {
32
+ try {
33
+ await prolog.query("kb_detach");
34
+ }
35
+ catch { }
36
+ }
37
+ try {
38
+ await prolog.terminate();
39
+ }
40
+ catch { }
41
+ }
42
+ }
43
+ }
44
+ // implements REQ-003
45
+ export async function withPrologProcess(callback) {
46
+ const prolog = new PrologProcess({ timeout: 120000 });
47
+ try {
48
+ await prolog.start();
49
+ ;
50
+ prolog.useOneShotMode = true;
51
+ await prolog.query("set_prolog_flag(answer_write_options, [max_depth(0), spacing(next_argument)])");
52
+ return await callback(prolog);
53
+ }
54
+ finally {
55
+ try {
56
+ await prolog.terminate();
57
+ }
58
+ catch { }
59
+ }
60
+ }
61
+ // implements REQ-003
62
+ export async function resolveCurrentKbPath() {
63
+ let branch;
64
+ try {
65
+ branch = process.env.KIBI_BRANCH || (await getCurrentBranch(process.cwd()));
66
+ }
67
+ catch {
68
+ branch = process.env.KIBI_BRANCH || "main";
69
+ }
70
+ return path.join(process.cwd(), ".kb/branches", branch);
71
+ }
72
+ // implements REQ-003
73
+ export function resolveCoreModulePath(fileName) {
74
+ return path.join(path.dirname(resolveKbPlPath()), fileName);
75
+ }
76
+ // implements REQ-003
77
+ export async function runJsonModuleQuery(prolog, fileName, goal, errorLabel, kbPath) {
78
+ const modulePath = escapeAtom(resolveCoreModulePath(fileName).replace(/\\/g, "/"));
79
+ const wrappedGoal = kbPath
80
+ ? `(use_module('${modulePath}'), kb_attach('${escapeAtom(kbPath)}'), ${goal}, kb_detach)`
81
+ : `(use_module('${modulePath}'), ${goal})`;
82
+ const result = await prolog.query(wrappedGoal);
83
+ if (!result.success) {
84
+ throw new Error(`${errorLabel}: ${result.error || "Unknown error"}`);
85
+ }
86
+ const rawJson = result.bindings.JsonString;
87
+ if (!rawJson) {
88
+ throw new Error(`${errorLabel}: missing JsonString binding`);
89
+ }
90
+ let parsed = JSON.parse(rawJson);
91
+ if (typeof parsed === "string") {
92
+ parsed = JSON.parse(parsed);
93
+ }
94
+ return parsed;
95
+ }
96
+ // implements REQ-003
97
+ export function printDiscoveryResult(format, structured, fallbackText) {
98
+ if (format === "json") {
99
+ console.log(JSON.stringify(structured, null, 2));
100
+ return;
101
+ }
102
+ const rendered = renderDiscoveryTable(structured);
103
+ console.log(rendered || fallbackText);
104
+ }
105
+ function renderDiscoveryTable(structured) {
106
+ if (!structured || typeof structured !== "object") {
107
+ return null;
108
+ }
109
+ const payload = structured;
110
+ if (Array.isArray(payload.results)) {
111
+ return renderSearchTable(payload);
112
+ }
113
+ if (typeof payload.branch === "string" && typeof payload.syncState === "string") {
114
+ return renderStatusTable(payload);
115
+ }
116
+ if (Array.isArray(payload.nodes) && Array.isArray(payload.edges)) {
117
+ return renderGraphTable(payload);
118
+ }
119
+ if (Array.isArray(payload.rows) && payload.summary && typeof payload.summary === "object") {
120
+ return renderCoverageTable(payload);
121
+ }
122
+ if (Array.isArray(payload.rows)) {
123
+ return renderGapsTable(payload);
124
+ }
125
+ return null;
126
+ }
127
+ function renderSearchTable(payload) {
128
+ const rows = Array.isArray(payload.results) ? payload.results : [];
129
+ const table = new Table({
130
+ head: ["ID", "Type", "Title", "Score", "Reasons", "Snippet"],
131
+ wordWrap: true,
132
+ colWidths: [20, 10, 32, 8, 28, 44],
133
+ });
134
+ for (const row of rows) {
135
+ const match = row;
136
+ const entity = (match.entity ?? {});
137
+ const reasons = Array.isArray(match.reasons) ? match.reasons.join(", ") : "";
138
+ table.push([
139
+ stringifyCell(entity.id),
140
+ stringifyCell(entity.type),
141
+ stringifyCell(entity.title),
142
+ stringifyCell(match.score),
143
+ reasons,
144
+ stringifyCell(match.snippet),
145
+ ]);
146
+ }
147
+ return [
148
+ `Search results: ${stringifyCell(payload.count)} total`,
149
+ table.toString(),
150
+ ].join("\n");
151
+ }
152
+ function renderStatusTable(payload) {
153
+ const table = new Table({
154
+ head: ["Field", "Value"],
155
+ colWidths: [18, 72],
156
+ wordWrap: true,
157
+ });
158
+ table.push(["Branch", stringifyCell(payload.branch)], ["Sync State", stringifyCell(payload.syncState)], ["Dirty", stringifyCell(payload.dirty)], ["Snapshot", stringifyCell(payload.snapshotId)], ["Synced At", stringifyCell(payload.syncedAt)], ["KB Path", stringifyCell(payload.kbPath)]);
159
+ return table.toString();
160
+ }
161
+ function renderGapsTable(payload) {
162
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
163
+ const table = new Table({
164
+ head: ["ID", "Type", "Status", "Missing", "Present", "Source"],
165
+ colWidths: [20, 10, 12, 24, 24, 40],
166
+ wordWrap: true,
167
+ });
168
+ for (const row of rows) {
169
+ const item = row;
170
+ table.push([
171
+ stringifyCell(item.id),
172
+ stringifyCell(item.type),
173
+ stringifyCell(item.status),
174
+ joinCells(item.missingRelationships),
175
+ joinCells(item.presentRelationships),
176
+ stringifyCell(item.source),
177
+ ]);
178
+ }
179
+ return [`Gap rows: ${stringifyCell(payload.count)}`, table.toString()].join("\n");
180
+ }
181
+ function renderCoverageTable(payload) {
182
+ const summary = (payload.summary ?? {});
183
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
184
+ const summaryTable = new Table({
185
+ head: ["Metric", "Value"],
186
+ colWidths: [24, 16],
187
+ });
188
+ for (const [key, value] of Object.entries(summary)) {
189
+ summaryTable.push([key, stringifyCell(value)]);
190
+ }
191
+ const firstRow = rows[0];
192
+ const isRequirementCoverage = firstRow && Object.hasOwn(firstRow, "scenarioCount");
193
+ const table = isRequirementCoverage
194
+ ? new Table({
195
+ head: ["ID", "Status", "Priority", "Coverage", "Scen", "Tests", "Symbols", "Gaps"],
196
+ colWidths: [20, 12, 12, 18, 8, 8, 10, 28],
197
+ wordWrap: true,
198
+ })
199
+ : new Table({
200
+ head: ["ID", "Type", "Coverage", "Details", "Gaps"],
201
+ colWidths: [20, 10, 18, 28, 28],
202
+ wordWrap: true,
203
+ });
204
+ for (const row of rows) {
205
+ const item = row;
206
+ if (isRequirementCoverage) {
207
+ table.push([
208
+ stringifyCell(item.id),
209
+ stringifyCell(item.status),
210
+ stringifyCell(item.priority),
211
+ stringifyCell(item.coverageStatus),
212
+ stringifyCell(item.scenarioCount),
213
+ stringifyCell(item.testCount),
214
+ stringifyCell(item.transitiveSymbolCount),
215
+ joinCells(item.gaps),
216
+ ]);
217
+ }
218
+ else {
219
+ table.push([
220
+ stringifyCell(item.id),
221
+ stringifyCell(item.type),
222
+ stringifyCell(item.coverageStatus),
223
+ `req=${stringifyCell(item.directRequirementCount)} test=${stringifyCell(item.testCount)} count=${stringifyCell(item.count)}`,
224
+ joinCells(item.gaps),
225
+ ]);
226
+ }
227
+ }
228
+ return [summaryTable.toString(), table.toString()].join("\n\n");
229
+ }
230
+ function renderGraphTable(payload) {
231
+ const nodes = Array.isArray(payload.nodes) ? payload.nodes : [];
232
+ const edges = Array.isArray(payload.edges) ? payload.edges : [];
233
+ const nodeTable = new Table({
234
+ head: ["Node ID", "Type", "Title", "Status"],
235
+ colWidths: [22, 10, 36, 12],
236
+ wordWrap: true,
237
+ });
238
+ for (const row of nodes) {
239
+ const item = row;
240
+ nodeTable.push([
241
+ stringifyCell(item.id),
242
+ stringifyCell(item.type),
243
+ stringifyCell(item.title),
244
+ stringifyCell(item.status),
245
+ ]);
246
+ }
247
+ const edgeTable = new Table({
248
+ head: ["Relationship", "From", "To"],
249
+ colWidths: [18, 22, 22],
250
+ wordWrap: true,
251
+ });
252
+ for (const row of edges) {
253
+ const item = row;
254
+ edgeTable.push([
255
+ stringifyCell(item.type),
256
+ stringifyCell(item.from),
257
+ stringifyCell(item.to),
258
+ ]);
259
+ }
260
+ return [
261
+ `Nodes: ${nodes.length} Edges: ${edges.length} Truncated: ${stringifyCell(payload.truncated)}`,
262
+ nodeTable.toString(),
263
+ edgeTable.toString(),
264
+ ].join("\n\n");
265
+ }
266
+ function stringifyCell(value) {
267
+ if (value === null || value === undefined || value === "") {
268
+ return "-";
269
+ }
270
+ if (typeof value === "string") {
271
+ return value;
272
+ }
273
+ return String(value);
274
+ }
275
+ function joinCells(value) {
276
+ if (!Array.isArray(value) || value.length === 0) {
277
+ return "-";
278
+ }
279
+ return value.map((item) => stringifyCell(item)).join(", ");
280
+ }