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
@@ -0,0 +1,281 @@
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 { safeCleanupProlog } from "../utils/prolog-cleanup.js";
6
+ import { getCurrentBranch } from "./init-helpers.js";
7
+ // implements REQ-003
8
+ export async function withAttachedBranchProlog(callback) {
9
+ let prolog = null;
10
+ let attached = false;
11
+ try {
12
+ prolog = new PrologProcess({ timeout: 120000 });
13
+ await prolog.start();
14
+ await prolog.query("set_prolog_flag(answer_write_options, [max_depth(0), spacing(next_argument)])");
15
+ let branch;
16
+ try {
17
+ branch =
18
+ process.env.KIBI_BRANCH || (await getCurrentBranch(process.cwd()));
19
+ }
20
+ catch {
21
+ branch = process.env.KIBI_BRANCH || "main";
22
+ }
23
+ const kbPath = path.join(process.cwd(), ".kb/branches", branch);
24
+ const attachResult = await prolog.query(`kb_attach('${escapeAtom(kbPath)}')`);
25
+ if (!attachResult.success) {
26
+ throw new Error(`Failed to attach KB: ${attachResult.error || "Unknown error"}`);
27
+ }
28
+ attached = true;
29
+ return await callback(prolog);
30
+ }
31
+ finally {
32
+ await safeCleanupProlog(prolog);
33
+ }
34
+ }
35
+ // implements REQ-003
36
+ export async function withPrologProcess(callback) {
37
+ const prolog = new PrologProcess({ timeout: 120000 });
38
+ try {
39
+ await prolog.start();
40
+ prolog.useOneShotMode = true;
41
+ await prolog.query("set_prolog_flag(answer_write_options, [max_depth(0), spacing(next_argument)])");
42
+ return await callback(prolog);
43
+ }
44
+ finally {
45
+ await safeCleanupProlog(prolog);
46
+ }
47
+ }
48
+ // implements REQ-003
49
+ export async function resolveCurrentKbPath() {
50
+ let branch;
51
+ try {
52
+ branch = process.env.KIBI_BRANCH || (await getCurrentBranch(process.cwd()));
53
+ }
54
+ catch {
55
+ branch = process.env.KIBI_BRANCH || "main";
56
+ }
57
+ return path.join(process.cwd(), ".kb/branches", branch);
58
+ }
59
+ // implements REQ-003
60
+ export function resolveCoreModulePath(fileName) {
61
+ return path.join(path.dirname(resolveKbPlPath()), fileName);
62
+ }
63
+ // implements REQ-003
64
+ export async function runJsonModuleQuery(prolog, fileName, goal, errorLabel, kbPath) {
65
+ const modulePath = escapeAtom(resolveCoreModulePath(fileName).replace(/\\/g, "/"));
66
+ const wrappedGoal = kbPath
67
+ ? `(use_module('${modulePath}'), kb_attach('${escapeAtom(kbPath)}'), ${goal}, kb_detach)`
68
+ : `(use_module('${modulePath}'), ${goal})`;
69
+ const result = await prolog.query(wrappedGoal);
70
+ if (!result.success) {
71
+ throw new Error(`${errorLabel}: ${result.error || "Unknown error"}`);
72
+ }
73
+ const rawJson = result.bindings.JsonString;
74
+ if (!rawJson) {
75
+ throw new Error(`${errorLabel}: missing JsonString binding`);
76
+ }
77
+ let parsed = JSON.parse(rawJson);
78
+ if (typeof parsed === "string") {
79
+ parsed = JSON.parse(parsed);
80
+ }
81
+ return parsed;
82
+ }
83
+ // implements REQ-003
84
+ export function printDiscoveryResult(format, structured, fallbackText) {
85
+ if (format === "json") {
86
+ console.log(JSON.stringify(structured, null, 2));
87
+ return;
88
+ }
89
+ const rendered = renderDiscoveryTable(structured);
90
+ console.log(rendered || fallbackText);
91
+ }
92
+ function renderDiscoveryTable(structured) {
93
+ if (!structured || typeof structured !== "object") {
94
+ return null;
95
+ }
96
+ const payload = structured;
97
+ if (Array.isArray(payload.results)) {
98
+ return renderSearchTable(payload);
99
+ }
100
+ if (typeof payload.branch === "string" &&
101
+ typeof payload.syncState === "string") {
102
+ return renderStatusTable(payload);
103
+ }
104
+ if (Array.isArray(payload.nodes) && Array.isArray(payload.edges)) {
105
+ return renderGraphTable(payload);
106
+ }
107
+ if (Array.isArray(payload.rows) &&
108
+ payload.summary &&
109
+ typeof payload.summary === "object") {
110
+ return renderCoverageTable(payload);
111
+ }
112
+ if (Array.isArray(payload.rows)) {
113
+ return renderGapsTable(payload);
114
+ }
115
+ return null;
116
+ }
117
+ function renderSearchTable(payload) {
118
+ const rows = Array.isArray(payload.results) ? payload.results : [];
119
+ const table = new Table({
120
+ head: ["ID", "Type", "Title", "Score", "Reasons", "Snippet"],
121
+ wordWrap: true,
122
+ colWidths: [20, 10, 32, 8, 28, 44],
123
+ });
124
+ for (const row of rows) {
125
+ const match = row;
126
+ const entity = (match.entity ?? {});
127
+ const reasons = Array.isArray(match.reasons)
128
+ ? match.reasons.join(", ")
129
+ : "";
130
+ table.push([
131
+ stringifyCell(entity.id),
132
+ stringifyCell(entity.type),
133
+ stringifyCell(entity.title),
134
+ stringifyCell(match.score),
135
+ reasons,
136
+ stringifyCell(match.snippet),
137
+ ]);
138
+ }
139
+ return [
140
+ `Search results: ${stringifyCell(payload.count)} total`,
141
+ table.toString(),
142
+ ].join("\n");
143
+ }
144
+ function renderStatusTable(payload) {
145
+ const table = new Table({
146
+ head: ["Field", "Value"],
147
+ colWidths: [18, 72],
148
+ wordWrap: true,
149
+ });
150
+ 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)]);
151
+ return table.toString();
152
+ }
153
+ function renderGapsTable(payload) {
154
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
155
+ const table = new Table({
156
+ head: ["ID", "Type", "Status", "Missing", "Present", "Source"],
157
+ colWidths: [20, 10, 12, 24, 24, 40],
158
+ wordWrap: true,
159
+ });
160
+ for (const row of rows) {
161
+ const item = row;
162
+ table.push([
163
+ stringifyCell(item.id),
164
+ stringifyCell(item.type),
165
+ stringifyCell(item.status),
166
+ joinCells(item.missingRelationships),
167
+ joinCells(item.presentRelationships),
168
+ stringifyCell(item.source),
169
+ ]);
170
+ }
171
+ return [`Gap rows: ${stringifyCell(payload.count)}`, table.toString()].join("\n");
172
+ }
173
+ function renderCoverageTable(payload) {
174
+ const summary = (payload.summary ?? {});
175
+ const rows = Array.isArray(payload.rows) ? payload.rows : [];
176
+ const summaryTable = new Table({
177
+ head: ["Metric", "Value"],
178
+ colWidths: [24, 16],
179
+ });
180
+ for (const [key, value] of Object.entries(summary)) {
181
+ summaryTable.push([key, stringifyCell(value)]);
182
+ }
183
+ const firstRow = rows[0];
184
+ const isRequirementCoverage = firstRow && Object.hasOwn(firstRow, "scenarioCount");
185
+ const table = isRequirementCoverage
186
+ ? new Table({
187
+ head: [
188
+ "ID",
189
+ "Status",
190
+ "Priority",
191
+ "Coverage",
192
+ "Scen",
193
+ "Tests",
194
+ "Symbols",
195
+ "Gaps",
196
+ ],
197
+ colWidths: [20, 12, 12, 18, 8, 8, 10, 28],
198
+ wordWrap: true,
199
+ })
200
+ : new Table({
201
+ head: ["ID", "Type", "Coverage", "Details", "Gaps"],
202
+ colWidths: [20, 10, 18, 28, 28],
203
+ wordWrap: true,
204
+ });
205
+ for (const row of rows) {
206
+ const item = row;
207
+ if (isRequirementCoverage) {
208
+ table.push([
209
+ stringifyCell(item.id),
210
+ stringifyCell(item.status),
211
+ stringifyCell(item.priority),
212
+ stringifyCell(item.coverageStatus),
213
+ stringifyCell(item.scenarioCount),
214
+ stringifyCell(item.testCount),
215
+ stringifyCell(item.transitiveSymbolCount),
216
+ joinCells(item.gaps),
217
+ ]);
218
+ }
219
+ else {
220
+ table.push([
221
+ stringifyCell(item.id),
222
+ stringifyCell(item.type),
223
+ stringifyCell(item.coverageStatus),
224
+ `req=${stringifyCell(item.directRequirementCount)} test=${stringifyCell(item.testCount)} count=${stringifyCell(item.count)}`,
225
+ joinCells(item.gaps),
226
+ ]);
227
+ }
228
+ }
229
+ return [summaryTable.toString(), table.toString()].join("\n\n");
230
+ }
231
+ function renderGraphTable(payload) {
232
+ const nodes = Array.isArray(payload.nodes) ? payload.nodes : [];
233
+ const edges = Array.isArray(payload.edges) ? payload.edges : [];
234
+ const nodeTable = new Table({
235
+ head: ["Node ID", "Type", "Title", "Status"],
236
+ colWidths: [22, 10, 36, 12],
237
+ wordWrap: true,
238
+ });
239
+ for (const row of nodes) {
240
+ const item = row;
241
+ nodeTable.push([
242
+ stringifyCell(item.id),
243
+ stringifyCell(item.type),
244
+ stringifyCell(item.title),
245
+ stringifyCell(item.status),
246
+ ]);
247
+ }
248
+ const edgeTable = new Table({
249
+ head: ["Relationship", "From", "To"],
250
+ colWidths: [18, 22, 22],
251
+ wordWrap: true,
252
+ });
253
+ for (const row of edges) {
254
+ const item = row;
255
+ edgeTable.push([
256
+ stringifyCell(item.type),
257
+ stringifyCell(item.from),
258
+ stringifyCell(item.to),
259
+ ]);
260
+ }
261
+ return [
262
+ `Nodes: ${nodes.length} Edges: ${edges.length} Truncated: ${stringifyCell(payload.truncated)}`,
263
+ nodeTable.toString(),
264
+ edgeTable.toString(),
265
+ ].join("\n\n");
266
+ }
267
+ function stringifyCell(value) {
268
+ if (value === null || value === undefined || value === "") {
269
+ return "-";
270
+ }
271
+ if (typeof value === "string") {
272
+ return value;
273
+ }
274
+ return String(value);
275
+ }
276
+ function joinCells(value) {
277
+ if (!Array.isArray(value) || value.length === 0) {
278
+ return "-";
279
+ }
280
+ return value.map((item) => stringifyCell(item)).join(", ");
281
+ }
@@ -1,2 +1,4 @@
1
- export declare function doctorCommand(): Promise<void>;
1
+ export declare function doctorCommand(): Promise<{
2
+ exitCode: number;
3
+ }>;
2
4
  //# sourceMappingURL=doctor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AA2BA,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CA0DnD"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AA4BA,wBAAsB,aAAa,IAAI,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAyDnE"}
@@ -18,6 +18,7 @@
18
18
  import { execSync } from "node:child_process";
19
19
  import { existsSync, readFileSync, statSync } from "node:fs";
20
20
  import * as path from "node:path";
21
+ // implements REQ-003
21
22
  export async function doctorCommand() {
22
23
  const checks = [
23
24
  {
@@ -65,12 +66,10 @@ export async function doctorCommand() {
65
66
  console.log();
66
67
  if (allPassed) {
67
68
  console.log("All checks passed! Your environment is ready.");
68
- process.exit(0);
69
- }
70
- else {
71
- console.log("Some checks failed. Please address the issues above.");
72
- process.exit(1);
69
+ return { exitCode: 0 };
73
70
  }
71
+ console.log("Some checks failed. Please address the issues above.");
72
+ return { exitCode: 1 };
74
73
  }
75
74
  function checkSWIProlog() {
76
75
  try {
@@ -198,7 +197,7 @@ function checkGitHooks() {
198
197
  return {
199
198
  passed: false,
200
199
  message: "Partially installed",
201
- remediation: "Run: kibi init --hooks",
200
+ remediation: "Run: kibi init",
202
201
  };
203
202
  }
204
203
  function checkPreCommitHook() {
@@ -218,7 +217,7 @@ function checkPreCommitHook() {
218
217
  return {
219
218
  passed: false,
220
219
  message: "Not installed",
221
- remediation: "Run: kibi init --hooks",
220
+ remediation: "Run: kibi init",
222
221
  };
223
222
  }
224
223
  try {
@@ -233,7 +232,7 @@ function checkPreCommitHook() {
233
232
  return {
234
233
  passed: false,
235
234
  message: "pre-commit hook installed but does not invoke kibi",
236
- remediation: "Run: kibi init --hooks to install recommended hooks",
235
+ remediation: "Run: kibi init to install recommended hooks",
237
236
  };
238
237
  }
239
238
  if (preCommitExecutable) {
@@ -247,7 +246,7 @@ function checkPreCommitHook() {
247
246
  return {
248
247
  passed: true,
249
248
  message: "Installed and executable (uses legacy 'kibi check' — consider running 'kibi init' to update hooks to use '--staged')",
250
- remediation: "Run: kibi init --hooks to update git hooks to the latest template",
249
+ remediation: "Run: kibi init to update git hooks to the latest template",
251
250
  };
252
251
  }
253
252
  return {
@@ -260,7 +259,7 @@ function checkPreCommitHook() {
260
259
  return {
261
260
  passed: false,
262
261
  message: "Unable to check hook permissions or read content",
263
- remediation: "Run: kibi init --hooks",
262
+ remediation: "Run: kibi init",
264
263
  };
265
264
  }
266
265
  }
@@ -281,7 +280,7 @@ function checkPostRewriteHook() {
281
280
  return {
282
281
  passed: false,
283
282
  message: "Not installed",
284
- remediation: "Run: kibi init --hooks",
283
+ remediation: "Run: kibi init",
285
284
  };
286
285
  }
287
286
  try {
@@ -294,7 +293,7 @@ function checkPostRewriteHook() {
294
293
  return {
295
294
  passed: false,
296
295
  message: "post-rewrite hook installed but does not invoke kibi",
297
- remediation: "Run: kibi init --hooks to install recommended hooks",
296
+ remediation: "Run: kibi init to install recommended hooks",
298
297
  };
299
298
  }
300
299
  if (postRewriteExecutable) {
@@ -313,7 +312,7 @@ function checkPostRewriteHook() {
313
312
  return {
314
313
  passed: false,
315
314
  message: "Unable to check hook permissions or read content",
316
- remediation: "Run: kibi init --hooks",
315
+ remediation: "Run: kibi init",
317
316
  };
318
317
  }
319
318
  }
@@ -0,0 +1,12 @@
1
+ interface GapsOptions {
2
+ missingRel?: string;
3
+ presentRel?: string;
4
+ tag?: string;
5
+ source?: string;
6
+ limit?: string;
7
+ offset?: string;
8
+ format?: "json" | "table";
9
+ }
10
+ export declare function gapsCommand(type: string | undefined, options: GapsOptions): Promise<void>;
11
+ export {};
12
+ //# sourceMappingURL=gaps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gaps.d.ts","sourceRoot":"","sources":["../../src/commands/gaps.ts"],"names":[],"mappings":"AAQA,UAAU,WAAW;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAGD,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAyBf"}
@@ -0,0 +1,28 @@
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 gapsCommand(type, options) {
5
+ await withPrologProcess(async (prolog) => {
6
+ const kbPath = await resolveCurrentKbPath();
7
+ const missing = csvToPrologList(options.missingRel);
8
+ const present = csvToPrologList(options.presentRel);
9
+ const tags = csvToPrologList(options.tag);
10
+ const source = options.source ? `'${escapeAtom(options.source)}'` : "none";
11
+ const limit = Number.parseInt(options.limit || "100", 10);
12
+ const offset = Number.parseInt(options.offset || "0", 10);
13
+ const typeArg = type ? `'${escapeAtom(type)}'` : "none";
14
+ const result = await runJsonModuleQuery(prolog, "discovery.pl", `discovery:find_gaps_json(${typeArg}, ${missing}, ${present}, ${tags}, ${source}, ${limit}, ${offset}, JsonString)`, "gaps query failed", kbPath);
15
+ printDiscoveryResult(options.format, result, `Found ${result.count ?? 0} gap rows.`);
16
+ });
17
+ }
18
+ function csvToPrologList(value) {
19
+ if (!value?.trim()) {
20
+ return "[]";
21
+ }
22
+ return `[${value
23
+ .split(",")
24
+ .map((item) => item.trim())
25
+ .filter(Boolean)
26
+ .map((item) => `'${escapeAtom(item)}'`)
27
+ .join(",")}]`;
28
+ }
@@ -0,0 +1,13 @@
1
+ interface GraphOptions {
2
+ from?: string;
3
+ relationships?: string;
4
+ direction?: "outgoing" | "incoming" | "both";
5
+ depth?: string;
6
+ entityTypes?: string;
7
+ maxNodes?: string;
8
+ maxEdges?: string;
9
+ format?: "json" | "table";
10
+ }
11
+ export declare function graphCommand(options: GraphOptions): Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=graph.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/commands/graph.ts"],"names":[],"mappings":"AAQA,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,CAAC;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC3B;AAGD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCvE"}
@@ -0,0 +1,35 @@
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 graphCommand(options) {
5
+ if (!options.from?.trim()) {
6
+ console.error("Error: --from is required");
7
+ process.exitCode = 1;
8
+ return;
9
+ }
10
+ await withPrologProcess(async (prolog) => {
11
+ const kbPath = await resolveCurrentKbPath();
12
+ const seedIds = csvToPrologList(options.from);
13
+ const relationships = csvToPrologList(options.relationships);
14
+ const direction = options.direction || "outgoing";
15
+ const depth = Number.parseInt(options.depth || "1", 10);
16
+ const entityTypes = csvToPrologList(options.entityTypes);
17
+ const maxNodes = Number.parseInt(options.maxNodes || "200", 10);
18
+ const maxEdges = Number.parseInt(options.maxEdges || "500", 10);
19
+ const result = await runJsonModuleQuery(prolog, "discovery.pl", `discovery:graph_expand_json(${seedIds}, ${relationships}, '${direction}', ${depth}, ${entityTypes}, ${maxNodes}, ${maxEdges}, JsonString)`, "graph query failed", kbPath);
20
+ const nodes = Array.isArray(result.nodes) ? result.nodes.length : 0;
21
+ const edges = Array.isArray(result.edges) ? result.edges.length : 0;
22
+ printDiscoveryResult(options.format, result, `Graph traversal returned ${nodes} nodes and ${edges} edges.`);
23
+ });
24
+ }
25
+ function csvToPrologList(value) {
26
+ if (!value?.trim()) {
27
+ return "[]";
28
+ }
29
+ return `[${value
30
+ .split(",")
31
+ .map((item) => item.trim())
32
+ .filter(Boolean)
33
+ .map((item) => `'${escapeAtom(item)}'`)
34
+ .join(",")}]`;
35
+ }
@@ -1,6 +1,8 @@
1
1
  interface InitOptions {
2
2
  hooks?: boolean;
3
3
  }
4
- export declare function initCommand(options: InitOptions): Promise<void>;
4
+ export declare function initCommand(options: InitOptions): Promise<{
5
+ exitCode: number;
6
+ }>;
5
7
  export {};
6
8
  //# sourceMappingURL=init.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiCA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DrE"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAiCA,UAAU,WAAW;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAGD,wBAAsB,WAAW,CAC/B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA6D/B"}
@@ -22,6 +22,7 @@ import { resolveActiveBranch } from "../utils/branch-resolver.js";
22
22
  import { copySchemaFiles, createConfigFile, createKbDirectoryStructure, installGitHooks, updateGitIgnore, } from "./init-helpers.js";
23
23
  const __filename = fileURLToPath(import.meta.url);
24
24
  const __dirname = path.dirname(__filename);
25
+ // implements REQ-003
25
26
  export async function initCommand(options) {
26
27
  const kbDir = path.join(process.cwd(), ".kb");
27
28
  const kbExists = existsSync(kbDir);
@@ -40,7 +41,7 @@ export async function initCommand(options) {
40
41
  else {
41
42
  console.error("Error: Failed to resolve the active git branch.");
42
43
  console.error(result.error);
43
- process.exit(1);
44
+ return { exitCode: 1 };
44
45
  }
45
46
  }
46
47
  else {
@@ -70,10 +71,10 @@ export async function initCommand(options) {
70
71
  console.log("Next steps:");
71
72
  console.log(" 1. Run 'kibi doctor' to verify setup");
72
73
  console.log(" 2. Run 'kibi sync' to extract entities from documents");
73
- process.exit(0);
74
+ return { exitCode: 0 };
74
75
  }
75
76
  catch (error) {
76
77
  console.error("Error during initialization:", error);
77
- process.exit(1);
78
+ return { exitCode: 1 };
78
79
  }
79
80
  }
@@ -7,6 +7,8 @@ interface QueryOptions {
7
7
  limit?: string;
8
8
  offset?: string;
9
9
  }
10
- export declare function queryCommand(type: string | undefined, options: QueryOptions): Promise<void>;
10
+ export declare function queryCommand(type: string | undefined, options: QueryOptions): Promise<{
11
+ exitCode: number;
12
+ }>;
11
13
  export {};
12
14
  //# sourceMappingURL=query.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AA2CA,UAAU,YAAY;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAGD,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAyKf"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AA4CA,UAAU,YAAY;IACpB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAGD,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA+J/B"}
@@ -22,6 +22,7 @@ import { parseEntityFromBinding, parseEntityFromList, parseListOfLists, parsePro
22
22
  import relationshipSchema from "../public/schemas/relationship.js";
23
23
  import { VALID_ENTITY_TYPES } from "../query/service.js";
24
24
  import { resolveActiveBranch } from "../utils/branch-resolver.js";
25
+ import { safeCleanupProlog } from "../utils/prolog-cleanup.js";
25
26
  const REL_TYPES = relationshipSchema.properties.type.enum;
26
27
  // implements REQ-003
27
28
  export async function queryCommand(type, options) {
@@ -45,7 +46,7 @@ export async function queryCommand(type, options) {
45
46
  else {
46
47
  console.error(`Error: Failed to resolve active branch:\n${branchResult.error}`);
47
48
  await prolog.terminate();
48
- process.exit(1);
49
+ return { exitCode: 1 };
49
50
  }
50
51
  }
51
52
  else {
@@ -56,7 +57,7 @@ export async function queryCommand(type, options) {
56
57
  if (!attachResult.success) {
57
58
  await prolog.terminate();
58
59
  console.error(`Error: Failed to attach KB: ${attachResult.error || "Unknown error"}`);
59
- process.exit(1);
60
+ return { exitCode: 1 };
60
61
  }
61
62
  attached = true;
62
63
  let results = [];
@@ -86,8 +87,7 @@ export async function queryCommand(type, options) {
86
87
  else if (type || options.source) {
87
88
  if (type && !VALID_ENTITY_TYPES.includes(type)) {
88
89
  console.error(`Error: Invalid type '${type}'. Valid types: ${VALID_ENTITY_TYPES.join(", ")}`);
89
- process.exitCode = 1;
90
- return;
90
+ return { exitCode: 1 };
91
91
  }
92
92
  let goal;
93
93
  if (options.source) {
@@ -133,8 +133,7 @@ export async function queryCommand(type, options) {
133
133
  }
134
134
  else {
135
135
  console.error("Error: Must specify entity type, --source, or --relationships option");
136
- process.exitCode = 1;
137
- return;
136
+ return { exitCode: 1 };
138
137
  }
139
138
  const limit = Number.parseInt(options.limit || "100");
140
139
  const offset = Number.parseInt(options.offset || "0");
@@ -146,7 +145,7 @@ export async function queryCommand(type, options) {
146
145
  else {
147
146
  console.log("No entities found");
148
147
  }
149
- return;
148
+ return { exitCode: 0 };
150
149
  }
151
150
  // Format output
152
151
  if (options.format === "table") {
@@ -155,25 +154,15 @@ export async function queryCommand(type, options) {
155
154
  else {
156
155
  console.log(JSON.stringify(paginated, null, 2));
157
156
  }
157
+ return { exitCode: 0 };
158
158
  }
159
159
  catch (error) {
160
160
  const message = error instanceof Error ? error.message : String(error);
161
161
  console.error(`Error: ${message}`);
162
- process.exitCode = 1;
162
+ return { exitCode: 1 };
163
163
  }
164
164
  finally {
165
- if (prolog) {
166
- if (attached) {
167
- try {
168
- await prolog.query("kb_detach");
169
- }
170
- catch { }
171
- }
172
- try {
173
- await prolog.terminate();
174
- }
175
- catch { }
176
- }
165
+ await safeCleanupProlog(prolog);
177
166
  }
178
167
  }
179
168
  /**
@@ -0,0 +1,9 @@
1
+ interface SearchOptions {
2
+ type?: string;
3
+ format?: "json" | "table";
4
+ limit?: string;
5
+ offset?: string;
6
+ }
7
+ export declare function searchCommand(query: string | undefined, options: SearchOptions): Promise<void>;
8
+ export {};
9
+ //# sourceMappingURL=search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AASA,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAGD,wBAAsB,aAAa,CACjC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}