kibi-cli 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +5 -0
- package/dist/commands/aggregated-checks.d.ts +9 -0
- package/dist/commands/aggregated-checks.d.ts.map +1 -0
- package/dist/commands/aggregated-checks.js +55 -0
- package/dist/commands/check.d.ts +5 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +143 -11
- package/dist/commands/doctor.js +23 -2
- package/dist/commands/init-helpers.d.ts.map +1 -1
- package/dist/commands/init-helpers.js +4 -2
- package/dist/commands/query.d.ts.map +1 -1
- package/dist/commands/query.js +20 -7
- package/dist/commands/sync.js +1 -1
- package/dist/prolog.d.ts +1 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +55 -6
- package/dist/traceability/git-staged.d.ts +29 -0
- package/dist/traceability/git-staged.d.ts.map +1 -0
- package/dist/traceability/git-staged.js +141 -0
- package/dist/traceability/symbol-extract.d.ts +15 -0
- package/dist/traceability/symbol-extract.d.ts.map +1 -0
- package/dist/traceability/symbol-extract.js +228 -0
- package/dist/traceability/temp-kb.d.ts +14 -0
- package/dist/traceability/temp-kb.d.ts.map +1 -0
- package/dist/traceability/temp-kb.js +121 -0
- package/dist/traceability/validate.d.ts +17 -0
- package/dist/traceability/validate.d.ts.map +1 -0
- package/dist/traceability/validate.js +150 -0
- package/package.json +5 -12
package/dist/cli.js
CHANGED
|
@@ -87,6 +87,11 @@ program
|
|
|
87
87
|
.command("check")
|
|
88
88
|
.description("Check KB consistency and integrity")
|
|
89
89
|
.option("--fix", "Suggest fixes for violations")
|
|
90
|
+
.option("--kb-path <dir>", "Path to KB directory (overrides branch resolution)")
|
|
91
|
+
.option("--rules <csv>", "Comma-separated allowlist of rule names to run")
|
|
92
|
+
.option("--staged", "Run check only against staged changes (experimental)")
|
|
93
|
+
.option("--min-links <n>", "Minimum number of links required for symbol coverage", "1")
|
|
94
|
+
.option("--dry-run", "Do not modify files; only print what would happen")
|
|
90
95
|
.action(async (options) => {
|
|
91
96
|
await checkCommand(options);
|
|
92
97
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PrologProcess } from "../prolog.js";
|
|
2
|
+
import type { Violation } from "./check.js";
|
|
3
|
+
/**
|
|
4
|
+
* Run all checks using the aggregated Prolog predicates.
|
|
5
|
+
* This makes a single Prolog call and parses JSON output, significantly
|
|
6
|
+
* faster than running individual checks with multiple round-trips.
|
|
7
|
+
*/
|
|
8
|
+
export declare function runAggregatedChecks(prolog: PrologProcess, rulesAllowlist: Set<string> | null): Promise<Violation[]>;
|
|
9
|
+
//# sourceMappingURL=aggregated-checks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregated-checks.d.ts","sourceRoot":"","sources":["../../src/commands/aggregated-checks.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAmB,MAAM,cAAc,CAAC;AACnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAU5C;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,GACjC,OAAO,CAAC,SAAS,EAAE,CAAC,CAqDtB"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveKbPlPath } from "../prolog.js";
|
|
3
|
+
/**
|
|
4
|
+
* Run all checks using the aggregated Prolog predicates.
|
|
5
|
+
* This makes a single Prolog call and parses JSON output, significantly
|
|
6
|
+
* faster than running individual checks with multiple round-trips.
|
|
7
|
+
*/
|
|
8
|
+
export async function runAggregatedChecks(prolog, rulesAllowlist) {
|
|
9
|
+
const violations = [];
|
|
10
|
+
const checksPlPath = path.join(path.dirname(resolveKbPlPath()), "checks.pl");
|
|
11
|
+
const checksPlPathEscaped = checksPlPath.replace(/'/g, "''");
|
|
12
|
+
const query = `(use_module('${checksPlPathEscaped}'), call(checks:check_all_json(JsonString)))`;
|
|
13
|
+
try {
|
|
14
|
+
const result = await prolog.query(query);
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
console.warn("Aggregated checks query failed, falling back to individual checks");
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
let violationsDict;
|
|
20
|
+
try {
|
|
21
|
+
const jsonString = result.bindings.JsonString;
|
|
22
|
+
if (!jsonString) {
|
|
23
|
+
throw new Error("No JSON string in binding");
|
|
24
|
+
}
|
|
25
|
+
let parsed = JSON.parse(jsonString);
|
|
26
|
+
if (typeof parsed === "string") {
|
|
27
|
+
parsed = JSON.parse(parsed);
|
|
28
|
+
}
|
|
29
|
+
violationsDict = parsed;
|
|
30
|
+
}
|
|
31
|
+
catch (parseError) {
|
|
32
|
+
console.warn("Failed to parse violations JSON:", parseError);
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
for (const ruleViolations of Object.values(violationsDict)) {
|
|
36
|
+
for (const v of ruleViolations) {
|
|
37
|
+
const isAllowed = !rulesAllowlist || rulesAllowlist.has(v.rule);
|
|
38
|
+
if (isAllowed) {
|
|
39
|
+
violations.push({
|
|
40
|
+
rule: v.rule,
|
|
41
|
+
entityId: v.entityId,
|
|
42
|
+
description: v.description,
|
|
43
|
+
suggestion: v.suggestion || undefined,
|
|
44
|
+
source: v.source || undefined,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return violations;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.warn("Error running aggregated checks:", error);
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AA8DA,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;AAED,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA6MvE"}
|
package/dist/commands/check.js
CHANGED
|
@@ -44,26 +44,158 @@
|
|
|
44
44
|
*/
|
|
45
45
|
import * as path from "node:path";
|
|
46
46
|
import { PrologProcess } from "../prolog.js";
|
|
47
|
+
import { getStagedFiles } from "../traceability/git-staged.js";
|
|
48
|
+
import { extractSymbolsFromStagedFile } from "../traceability/symbol-extract.js";
|
|
49
|
+
import { cleanupTempKb, consultOverlay, createOverlayFacts, createTempKb, } from "../traceability/temp-kb.js";
|
|
50
|
+
import { formatViolations as formatStagedViolations, validateStagedSymbols, } from "../traceability/validate.js";
|
|
51
|
+
import { runAggregatedChecks } from "./aggregated-checks.js";
|
|
52
|
+
import { getCurrentBranch } from "./init-helpers.js";
|
|
47
53
|
export async function checkCommand(options) {
|
|
48
54
|
try {
|
|
49
|
-
|
|
55
|
+
// Resolve KB path with priority:
|
|
56
|
+
// --kb-path > git branch --show-current > KIBI_BRANCH env > develop > main
|
|
57
|
+
let resolvedKbPath = "";
|
|
58
|
+
if (options.kbPath) {
|
|
59
|
+
resolvedKbPath = options.kbPath;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const envBranch = process.env.KIBI_BRANCH;
|
|
63
|
+
let branch = envBranch || undefined;
|
|
64
|
+
if (!branch) {
|
|
65
|
+
try {
|
|
66
|
+
branch = await getCurrentBranch(process.cwd());
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
branch = undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!branch)
|
|
73
|
+
branch = envBranch || "develop";
|
|
74
|
+
// fallback to main if develop isn't present? keep path consistent
|
|
75
|
+
resolvedKbPath = path.join(process.cwd(), ".kb/branches", branch || "main");
|
|
76
|
+
}
|
|
77
|
+
// If --staged mode requested, run staged-symbol traceability gate.
|
|
78
|
+
// We skip creating the main prolog session entirely in this path.
|
|
79
|
+
if (options.staged) {
|
|
80
|
+
const minLinks = options.minLinks ? Number(options.minLinks) : 1;
|
|
81
|
+
let tempCtx = null;
|
|
82
|
+
try {
|
|
83
|
+
// Get staged files
|
|
84
|
+
const stagedFiles = getStagedFiles();
|
|
85
|
+
if (!stagedFiles || stagedFiles.length === 0) {
|
|
86
|
+
console.log("No staged files found.");
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
// Extract symbols from staged files
|
|
90
|
+
const allSymbols = [];
|
|
91
|
+
for (const f of stagedFiles) {
|
|
92
|
+
try {
|
|
93
|
+
const symbols = extractSymbolsFromStagedFile(f);
|
|
94
|
+
if (symbols && symbols.length) {
|
|
95
|
+
allSymbols.push(...symbols);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
console.error(`Error extracting symbols from staged file ${f.path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (allSymbols.length === 0) {
|
|
103
|
+
console.log("No exported symbols found in staged files.");
|
|
104
|
+
process.exit(0);
|
|
105
|
+
}
|
|
106
|
+
// Create temp KB
|
|
107
|
+
tempCtx = await createTempKb(resolvedKbPath);
|
|
108
|
+
// Write overlay facts THEN consult so Prolog sees the changed_symbol facts
|
|
109
|
+
const overlayFacts = createOverlayFacts(allSymbols);
|
|
110
|
+
const fs = await import("node:fs/promises");
|
|
111
|
+
await fs.writeFile(tempCtx.overlayPath, overlayFacts, "utf8");
|
|
112
|
+
await consultOverlay(tempCtx);
|
|
113
|
+
// Validate staged symbols using the temp KB prolog session
|
|
114
|
+
const violationsRaw = await validateStagedSymbols({
|
|
115
|
+
minLinks,
|
|
116
|
+
prolog: tempCtx.prolog,
|
|
117
|
+
});
|
|
118
|
+
const violationsFormatted = formatStagedViolations(violationsRaw);
|
|
119
|
+
if (violationsRaw && violationsRaw.length > 0) {
|
|
120
|
+
console.log(violationsFormatted);
|
|
121
|
+
await cleanupTempKb(tempCtx.tempDir);
|
|
122
|
+
if (options.dryRun) {
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
console.log("✓ No violations found in staged symbols.");
|
|
128
|
+
await cleanupTempKb(tempCtx.tempDir);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
console.error(`Error running staged validation: ${err instanceof Error ? err.message : String(err)}`);
|
|
133
|
+
if (tempCtx) {
|
|
134
|
+
try {
|
|
135
|
+
await cleanupTempKb(tempCtx.tempDir);
|
|
136
|
+
}
|
|
137
|
+
catch { }
|
|
138
|
+
}
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const prolog = new PrologProcess({ timeout: 120000 });
|
|
50
143
|
await prolog.start();
|
|
51
|
-
const
|
|
52
|
-
const attachResult = await prolog.query(`kb_attach('${
|
|
144
|
+
const kbPathEscaped = resolvedKbPath.replace(/'/g, "''");
|
|
145
|
+
const attachResult = await prolog.query(`kb_attach('${kbPathEscaped}')`);
|
|
53
146
|
if (!attachResult.success) {
|
|
54
147
|
await prolog.terminate();
|
|
55
148
|
console.error(`Error: Failed to attach KB: ${attachResult.error}`);
|
|
56
149
|
process.exit(1);
|
|
57
150
|
}
|
|
58
151
|
const violations = [];
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
152
|
+
// Parse rules allowlist if provided
|
|
153
|
+
let rulesAllowlist = null;
|
|
154
|
+
if (options.rules) {
|
|
155
|
+
const parts = options.rules
|
|
156
|
+
.split(",")
|
|
157
|
+
.map((s) => s.trim())
|
|
158
|
+
.filter(Boolean);
|
|
159
|
+
rulesAllowlist = new Set(parts);
|
|
160
|
+
}
|
|
161
|
+
// Helper to conditionally run a check by name
|
|
162
|
+
async function runCheck(name, fn, ...args) {
|
|
163
|
+
if (rulesAllowlist?.has(name) === false)
|
|
164
|
+
return;
|
|
165
|
+
const res = await fn(prolog, ...args);
|
|
166
|
+
if (res && res.length)
|
|
167
|
+
violations.push(...res);
|
|
168
|
+
}
|
|
169
|
+
// Use aggregated checks (single Prolog call) when possible for better performance
|
|
170
|
+
// This is significantly faster in Bun/Docker environments where one-shot mode
|
|
171
|
+
// spawns a new Prolog process for each query
|
|
172
|
+
const supportedRules = [
|
|
173
|
+
"must-priority-coverage",
|
|
174
|
+
"symbol-coverage",
|
|
175
|
+
"no-dangling-refs",
|
|
176
|
+
"no-cycles",
|
|
177
|
+
"required-fields",
|
|
178
|
+
"deprecated-adr-no-successor",
|
|
179
|
+
"domain-contradictions",
|
|
180
|
+
];
|
|
181
|
+
const canUseAggregated = !rulesAllowlist ||
|
|
182
|
+
Array.from(rulesAllowlist).every((r) => supportedRules.includes(r));
|
|
183
|
+
if (canUseAggregated) {
|
|
184
|
+
// Fast path: single Prolog call returning all violations
|
|
185
|
+
const aggregatedViolations = await runAggregatedChecks(prolog, rulesAllowlist);
|
|
186
|
+
violations.push(...aggregatedViolations);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// Legacy path: individual checks for backward compatibility
|
|
190
|
+
await runCheck("must-priority-coverage", checkMustPriorityCoverage);
|
|
191
|
+
await runCheck("symbol-coverage", checkSymbolCoverage);
|
|
192
|
+
await runCheck("no-dangling-refs", checkNoDanglingRefs);
|
|
193
|
+
await runCheck("no-cycles", checkNoCycles);
|
|
194
|
+
const allEntityIds = await getAllEntityIds(prolog);
|
|
195
|
+
await runCheck("required-fields", checkRequiredFields, allEntityIds);
|
|
196
|
+
await runCheck("deprecated-adr-no-successor", checkDeprecatedAdrs);
|
|
197
|
+
await runCheck("domain-contradictions", checkDomainContradictions);
|
|
198
|
+
}
|
|
67
199
|
await prolog.query("kb_detach");
|
|
68
200
|
await prolog.terminate();
|
|
69
201
|
if (violations.length === 0) {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -247,10 +247,30 @@ function checkPreCommitHook() {
|
|
|
247
247
|
try {
|
|
248
248
|
const preCommitStats = statSync(preCommitPath);
|
|
249
249
|
const preCommitExecutable = (preCommitStats.mode & 0o111) !== 0;
|
|
250
|
+
// Read hook content to determine whether it's using the new staged check
|
|
251
|
+
const content = readFileSync(preCommitPath, "utf-8");
|
|
252
|
+
const usesKibi = content.includes("kibi check");
|
|
253
|
+
const usesStaged = content.includes("kibi check --staged");
|
|
254
|
+
if (!usesKibi) {
|
|
255
|
+
// Fail if hook doesn't invoke kibi at all
|
|
256
|
+
return {
|
|
257
|
+
passed: false,
|
|
258
|
+
message: "pre-commit hook installed but does not invoke kibi",
|
|
259
|
+
remediation: "Run: kibi init --hooks to install recommended hooks",
|
|
260
|
+
};
|
|
261
|
+
}
|
|
250
262
|
if (preCommitExecutable) {
|
|
263
|
+
if (usesStaged) {
|
|
264
|
+
return {
|
|
265
|
+
passed: true,
|
|
266
|
+
message: "Installed and executable (uses 'kibi check --staged')",
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// Warn but pass if using legacy kibi check without --staged
|
|
251
270
|
return {
|
|
252
271
|
passed: true,
|
|
253
|
-
message: "Installed and executable",
|
|
272
|
+
message: "Installed and executable (uses legacy 'kibi check' — consider running 'kibi init' to update hooks to use '--staged')",
|
|
273
|
+
remediation: "Run: kibi init --hooks to update git hooks to the latest template",
|
|
254
274
|
};
|
|
255
275
|
}
|
|
256
276
|
return {
|
|
@@ -262,7 +282,8 @@ function checkPreCommitHook() {
|
|
|
262
282
|
catch (error) {
|
|
263
283
|
return {
|
|
264
284
|
passed: false,
|
|
265
|
-
message: "Unable to check hook permissions",
|
|
285
|
+
message: "Unable to check hook permissions or read content",
|
|
286
|
+
remediation: "Run: kibi init --hooks",
|
|
266
287
|
};
|
|
267
288
|
}
|
|
268
289
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init-helpers.d.ts","sourceRoot":"","sources":["../../src/commands/init-helpers.ts"],"names":[],"mappings":"AAkFA,wBAAsB,gBAAgB,CACpC,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,IAAI,CAQN;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAMpD;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAajD;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,MAAM,GACtB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAuBnE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAapD"}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
fi
|
|
43
43
|
done
|
|
44
44
|
*/
|
|
45
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
45
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from "node:fs";
|
|
46
46
|
import * as path from "node:path";
|
|
47
47
|
import fg from "fast-glob";
|
|
48
48
|
const POST_CHECKOUT_HOOK = `#!/bin/sh
|
|
@@ -53,7 +53,7 @@ kibi sync
|
|
|
53
53
|
`;
|
|
54
54
|
const PRE_COMMIT_HOOK = `#!/bin/sh
|
|
55
55
|
set -e
|
|
56
|
-
kibi check
|
|
56
|
+
kibi check --staged
|
|
57
57
|
`;
|
|
58
58
|
const DEFAULT_CONFIG = {
|
|
59
59
|
paths: {
|
|
@@ -136,6 +136,8 @@ ${content}`, {
|
|
|
136
136
|
writeFileSync(hookPath, `#!/bin/sh
|
|
137
137
|
${content}`, { mode: 0o755 });
|
|
138
138
|
}
|
|
139
|
+
// Explicitly ensure hook is executable (mode option can be inconsistent in Docker)
|
|
140
|
+
chmodSync(hookPath, 0o755);
|
|
139
141
|
}
|
|
140
142
|
export function installGitHooks(gitDir) {
|
|
141
143
|
const hooksDir = path.join(gitDir, "hooks");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AAoDA,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;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,EACxB,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,IAAI,CAAC,CAwKf"}
|
package/dist/commands/query.js
CHANGED
|
@@ -45,9 +45,11 @@
|
|
|
45
45
|
import * as path from "node:path";
|
|
46
46
|
import Table from "cli-table3";
|
|
47
47
|
import { PrologProcess } from "../prolog.js";
|
|
48
|
+
import relationshipSchema from "../public/schemas/relationship.js";
|
|
49
|
+
const REL_TYPES = relationshipSchema.properties.type.enum;
|
|
48
50
|
export async function queryCommand(type, options) {
|
|
49
51
|
try {
|
|
50
|
-
const prolog = new PrologProcess();
|
|
52
|
+
const prolog = new PrologProcess({ timeout: 120000 });
|
|
51
53
|
await prolog.start();
|
|
52
54
|
await prolog.query("set_prolog_flag(answer_write_options, [max_depth(0), spacing(next_argument)])");
|
|
53
55
|
let currentBranch = "main";
|
|
@@ -73,15 +75,26 @@ export async function queryCommand(type, options) {
|
|
|
73
75
|
let results = [];
|
|
74
76
|
// Query relationships mode
|
|
75
77
|
if (options.relationships) {
|
|
76
|
-
const
|
|
78
|
+
const fromId = String(options.relationships);
|
|
79
|
+
const safeFromId = fromId.replace(/'/g, "''");
|
|
80
|
+
// Query all relationship types for the given source ID
|
|
81
|
+
const goal = `findall([Type,From,To], (From='${safeFromId}', kb_relationship(Type, From, To)), Results)`;
|
|
77
82
|
const queryResult = await prolog.query(goal);
|
|
78
83
|
if (queryResult.success && queryResult.bindings.Results) {
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
const rows = parseListOfLists(queryResult.bindings.Results);
|
|
85
|
+
const parsed = rows
|
|
86
|
+
.filter((r) => r.length >= 3)
|
|
87
|
+
.map((r) => ({
|
|
88
|
+
type: parsePrologValue(r[0]),
|
|
89
|
+
from: parsePrologValue(r[1]),
|
|
90
|
+
to: parsePrologValue(r[2]),
|
|
84
91
|
}));
|
|
92
|
+
results = parsed.filter((rel) => rel &&
|
|
93
|
+
typeof rel.type === "string" &&
|
|
94
|
+
typeof rel.from === "string" &&
|
|
95
|
+
typeof rel.to === "string" &&
|
|
96
|
+
rel.from === fromId &&
|
|
97
|
+
REL_TYPES.includes(rel.type));
|
|
85
98
|
}
|
|
86
99
|
}
|
|
87
100
|
// Query entities mode
|
package/dist/commands/sync.js
CHANGED
|
@@ -327,7 +327,7 @@ export async function syncCommand(options = {}) {
|
|
|
327
327
|
process.exit(0);
|
|
328
328
|
}
|
|
329
329
|
// Connect to KB
|
|
330
|
-
const prolog = new PrologProcess();
|
|
330
|
+
const prolog = new PrologProcess({ timeout: 120000 });
|
|
331
331
|
await prolog.start();
|
|
332
332
|
const kbPath = path.join(process.cwd(), `.kb/branches/${currentBranch}`);
|
|
333
333
|
const mainPath = path.join(process.cwd(), ".kb/branches/main");
|
package/dist/prolog.d.ts
CHANGED
package/dist/prolog.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"AAuDA,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,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;IAsH1D,eAAe,IAAI,IAAI;IAIvB,OAAO,CAAC,eAAe;YAYT,YAAY;IA0B1B,OAAO,CAAC,WAAW;IA8EnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,cAAc;IA8BtB,SAAS,IAAI,OAAO;IAIpB,MAAM,IAAI,MAAM;IAIV,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAyBjC"}
|
package/dist/prolog.js
CHANGED
|
@@ -49,7 +49,7 @@ import path from "node:path";
|
|
|
49
49
|
import { fileURLToPath } from "node:url";
|
|
50
50
|
const importMetaDir = path.dirname(fileURLToPath(import.meta.url));
|
|
51
51
|
const require = createRequire(import.meta.url);
|
|
52
|
-
function resolveKbPlPath() {
|
|
52
|
+
export function resolveKbPlPath() {
|
|
53
53
|
const overrideKbPath = process.env.KIBI_KB_PL_PATH;
|
|
54
54
|
if (overrideKbPath && existsSync(overrideKbPath)) {
|
|
55
55
|
return overrideKbPath;
|
|
@@ -120,7 +120,27 @@ export class PrologProcess {
|
|
|
120
120
|
await this.waitForReady();
|
|
121
121
|
}
|
|
122
122
|
async waitForReady() {
|
|
123
|
-
|
|
123
|
+
// Wait for Prolog to initialize and detect startup failures explicitly.
|
|
124
|
+
const start = Date.now();
|
|
125
|
+
const maxStartWait = 2000; // ms
|
|
126
|
+
while (Date.now() - start < maxStartWait) {
|
|
127
|
+
// If process exited or was killed, surface the error buffer.
|
|
128
|
+
if (!this.process || this.process.killed) {
|
|
129
|
+
throw new Error(`Prolog process terminated unexpectedly during startup: ${this.translateError(this.errorBuffer)}`);
|
|
130
|
+
}
|
|
131
|
+
// If stderr contains an ERROR, fail fast with translated message.
|
|
132
|
+
if (this.errorBuffer.includes("ERROR")) {
|
|
133
|
+
throw new Error(`Failed to load kb module: ${this.translateError(this.errorBuffer)}`);
|
|
134
|
+
}
|
|
135
|
+
// If stdout or stderr shows any output, assume ready.
|
|
136
|
+
if (this.outputBuffer.length > 0 || this.errorBuffer.length > 0) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
// brief pause
|
|
140
|
+
// eslint-disable-next-line no-await-in-loop
|
|
141
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
142
|
+
}
|
|
143
|
+
// Final sanity check
|
|
124
144
|
if (this.errorBuffer.includes("ERROR")) {
|
|
125
145
|
throw new Error(`Failed to load kb module: ${this.translateError(this.errorBuffer)}`);
|
|
126
146
|
}
|
|
@@ -158,13 +178,30 @@ export class PrologProcess {
|
|
|
158
178
|
this.errorBuffer = "";
|
|
159
179
|
this.process.stdin.write(`${goal}.
|
|
160
180
|
`);
|
|
181
|
+
const debug = !!process.env.KIBI_PROLOG_DEBUG;
|
|
182
|
+
const normalizedGoal = isSingleGoal
|
|
183
|
+
? this.normalizeGoal(goal)
|
|
184
|
+
: undefined;
|
|
185
|
+
const start = Date.now();
|
|
186
|
+
if (debug && normalizedGoal)
|
|
187
|
+
console.error(`[prolog debug] start query: ${normalizedGoal}`);
|
|
161
188
|
return new Promise((resolve, reject) => {
|
|
162
189
|
const timeoutId = setTimeout(() => {
|
|
163
|
-
|
|
190
|
+
const msg = `Query timeout after ${this.timeout / 1000}s`;
|
|
191
|
+
if (debug) {
|
|
192
|
+
const tailOut = this.outputBuffer.slice(-2048);
|
|
193
|
+
const tailErr = this.errorBuffer.slice(-2048);
|
|
194
|
+
console.error(`[prolog debug] timeout: ${msg}`);
|
|
195
|
+
console.error(`[prolog debug] last stdout: ---\n${tailOut}\n---`);
|
|
196
|
+
console.error(`[prolog debug] last stderr: ---\n${tailErr}\n---`);
|
|
197
|
+
}
|
|
198
|
+
reject(new Error(msg));
|
|
164
199
|
}, this.timeout);
|
|
165
200
|
const checkResult = () => {
|
|
166
201
|
if (this.errorBuffer.length > 0 && this.errorBuffer.includes("ERROR")) {
|
|
167
202
|
clearTimeout(timeoutId);
|
|
203
|
+
if (debug && normalizedGoal)
|
|
204
|
+
console.error(`[prolog debug] query error: ${normalizedGoal} error=${this.errorBuffer.split("\n")[0]}`);
|
|
168
205
|
resolve({
|
|
169
206
|
success: false,
|
|
170
207
|
bindings: {},
|
|
@@ -172,7 +209,9 @@ export class PrologProcess {
|
|
|
172
209
|
});
|
|
173
210
|
}
|
|
174
211
|
else if (this.outputBuffer.includes("true.") ||
|
|
175
|
-
this.outputBuffer.match(/^[A-Z_][A-Za-z0-9_]*\s*=\s*.+\./m)
|
|
212
|
+
this.outputBuffer.match(/^[A-Z_][A-Za-z0-9_]*\s*=\s*.+\./m) ||
|
|
213
|
+
// Match multi-line output ending with ] (Prolog list/term output without trailing period)
|
|
214
|
+
this.outputBuffer.match(/\]\s*$/m)) {
|
|
176
215
|
clearTimeout(timeoutId);
|
|
177
216
|
const result = {
|
|
178
217
|
success: true,
|
|
@@ -181,11 +220,20 @@ export class PrologProcess {
|
|
|
181
220
|
if (cacheable) {
|
|
182
221
|
this.cache.set(goalKey, result);
|
|
183
222
|
}
|
|
223
|
+
if (debug && normalizedGoal) {
|
|
224
|
+
console.error(`[prolog debug] query success: ${normalizedGoal} elapsed=${(Date.now() - start) / 1000}s`);
|
|
225
|
+
}
|
|
184
226
|
resolve(result);
|
|
227
|
+
// Send newline to exit Prolog's interactive prompt
|
|
228
|
+
if (this.process?.stdin) {
|
|
229
|
+
this.process.stdin.write("\n");
|
|
230
|
+
}
|
|
185
231
|
}
|
|
186
232
|
else if (this.outputBuffer.includes("false.") ||
|
|
187
233
|
this.outputBuffer.includes("fail.")) {
|
|
188
234
|
clearTimeout(timeoutId);
|
|
235
|
+
if (debug && normalizedGoal)
|
|
236
|
+
console.error(`[prolog debug] query failed (false): ${normalizedGoal}`);
|
|
189
237
|
resolve({
|
|
190
238
|
success: false,
|
|
191
239
|
bindings: {},
|
|
@@ -264,7 +312,7 @@ export class PrologProcess {
|
|
|
264
312
|
(result.error.message.includes("timed out") ||
|
|
265
313
|
// Bun/Node differ here; keep a conservative timeout detection.
|
|
266
314
|
result.error.message.includes("ETIMEDOUT"))) {
|
|
267
|
-
throw new Error(
|
|
315
|
+
throw new Error(`Query timeout after ${this.timeout / 1000}s`);
|
|
268
316
|
}
|
|
269
317
|
const stdout = result.stdout ?? "";
|
|
270
318
|
const stderr = result.stderr ?? "";
|
|
@@ -319,7 +367,7 @@ export class PrologProcess {
|
|
|
319
367
|
return "Invalid query syntax";
|
|
320
368
|
}
|
|
321
369
|
if (errorText.includes("timeout_error")) {
|
|
322
|
-
return
|
|
370
|
+
return `Operation exceeded ${this.timeout / 1000}s timeout`;
|
|
323
371
|
}
|
|
324
372
|
const simpleError = errorText
|
|
325
373
|
.replace(/ERROR:\s*/g, "")
|
|
@@ -357,3 +405,4 @@ export class PrologProcess {
|
|
|
357
405
|
}
|
|
358
406
|
}
|
|
359
407
|
}
|
|
408
|
+
// FIX_VERSION_2024_03_06
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type Status = "A" | "M" | "R" | "D";
|
|
2
|
+
export interface HunkRange {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
}
|
|
6
|
+
export interface StagedFile {
|
|
7
|
+
path: string;
|
|
8
|
+
status: Status;
|
|
9
|
+
oldPath?: string;
|
|
10
|
+
hunkRanges: HunkRange[];
|
|
11
|
+
content?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse null-separated name-status output from git
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseNameStatusNull(input: string): Array<{
|
|
17
|
+
status: string;
|
|
18
|
+
parts: string[];
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Parse unified diff hunks (new-file coordinates) from git diff output
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseHunksFromDiff(diffText: string, isNewFile?: boolean): HunkRange[];
|
|
24
|
+
/**
|
|
25
|
+
* Get staged files with statuses, hunks and content.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getStagedFiles(): StagedFile[];
|
|
28
|
+
export default getStagedFiles;
|
|
29
|
+
//# sourceMappingURL=git-staged.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-staged.d.ts","sourceRoot":"","sources":["../../src/traceability/git-staged.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,MAAM,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAaD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,GACZ,KAAK,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAS5C;AAoBD;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,SAAS,UAAQ,GAChB,SAAS,EAAE,CAoBb;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,UAAU,EAAE,CAmF7C;AAED,eAAe,cAAc,CAAC"}
|