kibi-cli 2.2.0 → 2.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 (183) hide show
  1. package/dist/cli-operation-loader.d.ts.map +1 -1
  2. package/dist/cli-operation-loader.js +3 -0
  3. package/dist/cli-operation-metadata.d.ts +4 -0
  4. package/dist/cli-operation-metadata.d.ts.map +1 -1
  5. package/dist/cli-operation-metadata.js +5 -0
  6. package/dist/cli-register-reporting.d.ts.map +1 -1
  7. package/dist/cli-register-reporting.js +4 -0
  8. package/dist/commands/check-diff.d.ts +5 -0
  9. package/dist/commands/check-diff.d.ts.map +1 -0
  10. package/dist/commands/check-diff.js +62 -0
  11. package/dist/commands/check-generated.d.ts.map +1 -1
  12. package/dist/commands/check-generated.js +41 -18
  13. package/dist/commands/check.d.ts.map +1 -1
  14. package/dist/commands/check.js +210 -73
  15. package/dist/commands/doctor.d.ts.map +1 -1
  16. package/dist/commands/doctor.js +63 -29
  17. package/dist/commands/github-init.d.ts.map +1 -1
  18. package/dist/commands/github-init.js +9 -2
  19. package/dist/commands/sync/manifest.d.ts.map +1 -1
  20. package/dist/commands/sync/manifest.js +3 -1
  21. package/dist/engine.d.ts +15 -1
  22. package/dist/engine.d.ts.map +1 -1
  23. package/dist/engine.js +252 -52
  24. package/dist/extractors/symbols-coordinator.d.ts +7 -0
  25. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  26. package/dist/extractors/symbols-coordinator.js +75 -39
  27. package/dist/operations/modeling/predicate-inference-1.js +1 -1
  28. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  29. package/dist/operations/mutation/source-authoring.js +175 -53
  30. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  31. package/dist/operations/mutation/upsert.js +5 -7
  32. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  33. package/dist/operations/planning/apply-plan.js +5 -9
  34. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  35. package/dist/operations/proof/ingest-proof.js +1 -0
  36. package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
  37. package/dist/operations/semantic-advisor/analysis-receipt.js +7 -1
  38. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  39. package/dist/operations/semantic-advisor/analyze-prose.js +26 -4
  40. package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
  41. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  42. package/dist/operations/semantic-advisor/clauses.js +7 -1
  43. package/dist/performance-trace.d.ts +17 -0
  44. package/dist/performance-trace.d.ts.map +1 -0
  45. package/dist/performance-trace.js +134 -0
  46. package/dist/plugins/approved-source-analyzers.d.ts +13 -0
  47. package/dist/plugins/approved-source-analyzers.d.ts.map +1 -0
  48. package/dist/plugins/approved-source-analyzers.generated.d.ts +71 -0
  49. package/dist/plugins/approved-source-analyzers.generated.d.ts.map +1 -0
  50. package/dist/plugins/approved-source-analyzers.generated.js +77 -0
  51. package/dist/plugins/approved-source-analyzers.js +3 -0
  52. package/dist/plugins/maintenance-source-analysis.d.ts +14 -0
  53. package/dist/plugins/maintenance-source-analysis.d.ts.map +1 -0
  54. package/dist/plugins/maintenance-source-analysis.js +191 -0
  55. package/dist/plugins/registry.d.ts +2 -1
  56. package/dist/plugins/registry.d.ts.map +1 -1
  57. package/dist/plugins/registry.js +6 -1
  58. package/dist/plugins/source-analysis-service.d.ts +36 -1
  59. package/dist/plugins/source-analysis-service.d.ts.map +1 -1
  60. package/dist/plugins/source-analysis-service.js +184 -27
  61. package/dist/plugins/source-change-analysis.d.ts +15 -0
  62. package/dist/plugins/source-change-analysis.d.ts.map +1 -0
  63. package/dist/plugins/source-change-analysis.js +54 -0
  64. package/dist/plugins/source-classification.d.ts +41 -0
  65. package/dist/plugins/source-classification.d.ts.map +1 -0
  66. package/dist/plugins/source-classification.js +490 -0
  67. package/dist/prolog/swipl-resolver.d.ts +88 -0
  68. package/dist/prolog/swipl-resolver.d.ts.map +1 -0
  69. package/dist/prolog/swipl-resolver.js +446 -0
  70. package/dist/prolog.d.ts +15 -1
  71. package/dist/prolog.d.ts.map +1 -1
  72. package/dist/prolog.js +67 -6
  73. package/dist/public/impact/analyzer.d.ts.map +1 -1
  74. package/dist/public/impact/analyzer.js +56 -5
  75. package/dist/public/impact/diagnostics.d.ts.map +1 -1
  76. package/dist/public/impact/diagnostics.js +3 -1
  77. package/dist/public/impact/manifest.d.ts.map +1 -1
  78. package/dist/public/impact/manifest.js +15 -1
  79. package/dist/public/impact/source-changes.d.ts.map +1 -1
  80. package/dist/public/impact/source-changes.js +35 -27
  81. package/dist/public/impact/types.d.ts +2 -0
  82. package/dist/public/impact/types.d.ts.map +1 -1
  83. package/dist/public/operations/catalog.d.ts +44 -0
  84. package/dist/public/operations/catalog.d.ts.map +1 -1
  85. package/dist/public/operations/catalog.js +2 -0
  86. package/dist/public/operations/contracts.d.ts.map +1 -1
  87. package/dist/public/operations/contracts.js +309 -8
  88. package/dist/public/operations/impact-review-preparation.d.ts +14 -0
  89. package/dist/public/operations/impact-review-preparation.d.ts.map +1 -0
  90. package/dist/public/operations/impact-review-preparation.js +379 -0
  91. package/dist/public/operations/index.d.ts +2 -0
  92. package/dist/public/operations/index.d.ts.map +1 -1
  93. package/dist/public/operations/index.js +2 -0
  94. package/dist/public/operations/node-ports.d.ts.map +1 -1
  95. package/dist/public/operations/node-ports.js +1 -35
  96. package/dist/public/operations/proof-receipt-projection.d.ts +5 -0
  97. package/dist/public/operations/proof-receipt-projection.d.ts.map +1 -0
  98. package/dist/public/operations/proof-receipt-projection.js +36 -0
  99. package/dist/public/operations/specs/impact-review-preparation.d.ts +47 -0
  100. package/dist/public/operations/specs/impact-review-preparation.d.ts.map +1 -0
  101. package/dist/public/operations/specs/impact-review-preparation.js +43 -0
  102. package/dist/public/operations/types.d.ts +1 -1
  103. package/dist/public/operations/types.d.ts.map +1 -1
  104. package/dist/public/skills/kibi-usage/resources/operation-access.md +1 -0
  105. package/dist/traceability/analysis-gate.d.ts +39 -0
  106. package/dist/traceability/analysis-gate.d.ts.map +1 -0
  107. package/dist/traceability/analysis-gate.js +51 -0
  108. package/dist/traceability/git-change-snapshot.d.ts +27 -0
  109. package/dist/traceability/git-change-snapshot.d.ts.map +1 -0
  110. package/dist/traceability/git-change-snapshot.js +411 -0
  111. package/dist/traceability/git-staged.d.ts +5 -1
  112. package/dist/traceability/git-staged.d.ts.map +1 -1
  113. package/dist/traceability/git-staged.js +16 -11
  114. package/dist/traceability/impact-evaluator.d.ts +54 -0
  115. package/dist/traceability/impact-evaluator.d.ts.map +1 -0
  116. package/dist/traceability/impact-evaluator.js +270 -0
  117. package/dist/traceability/impact-git-tree.d.ts +12 -0
  118. package/dist/traceability/impact-git-tree.d.ts.map +1 -0
  119. package/dist/traceability/impact-git-tree.js +46 -0
  120. package/dist/traceability/impact-knowledge.d.ts +16 -0
  121. package/dist/traceability/impact-knowledge.d.ts.map +1 -0
  122. package/dist/traceability/impact-knowledge.js +165 -0
  123. package/dist/traceability/impact-policy.d.ts +15 -0
  124. package/dist/traceability/impact-policy.d.ts.map +1 -0
  125. package/dist/traceability/impact-policy.js +48 -0
  126. package/dist/traceability/impact-policy.v1.schema.json +48 -0
  127. package/dist/traceability/impact-pr-diff.d.ts +5 -0
  128. package/dist/traceability/impact-pr-diff.d.ts.map +1 -0
  129. package/dist/traceability/impact-pr-diff.js +83 -0
  130. package/dist/traceability/impact-record-validation.d.ts +9 -0
  131. package/dist/traceability/impact-record-validation.d.ts.map +1 -0
  132. package/dist/traceability/impact-record-validation.js +113 -0
  133. package/dist/traceability/impact-review.d.ts +148 -0
  134. package/dist/traceability/impact-review.d.ts.map +1 -0
  135. package/dist/traceability/impact-review.js +494 -0
  136. package/dist/traceability/impact-review.schema.json +365 -0
  137. package/dist/traceability/impact-side-evidence.d.ts +13 -0
  138. package/dist/traceability/impact-side-evidence.d.ts.map +1 -0
  139. package/dist/traceability/impact-side-evidence.js +240 -0
  140. package/dist/traceability/reviewed-coordinate-refresh.d.ts +10 -0
  141. package/dist/traceability/reviewed-coordinate-refresh.d.ts.map +1 -0
  142. package/dist/traceability/reviewed-coordinate-refresh.js +49 -0
  143. package/dist/traceability/snapshot-knowledge.d.ts +4 -0
  144. package/dist/traceability/snapshot-knowledge.d.ts.map +1 -0
  145. package/dist/traceability/snapshot-knowledge.js +98 -0
  146. package/dist/traceability/staged-file-coverage.d.ts +23 -0
  147. package/dist/traceability/staged-file-coverage.d.ts.map +1 -1
  148. package/dist/traceability/staged-impact-contract.d.ts.map +1 -1
  149. package/dist/traceability/staged-impact-contract.js +2 -16
  150. package/dist/traceability/staged-symbols-manifest.d.ts +4 -0
  151. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  152. package/dist/traceability/staged-symbols-manifest.js +17 -6
  153. package/dist/traceability/symbol-extract.d.ts +3 -5
  154. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  155. package/dist/traceability/symbol-extract.js +49 -34
  156. package/dist/traceability/temp-kb.d.ts +1 -1
  157. package/dist/traceability/temp-kb.d.ts.map +1 -1
  158. package/dist/traceability/temp-kb.js +49 -13
  159. package/dist/traceability/trusted-pr-event.d.ts +13 -0
  160. package/dist/traceability/trusted-pr-event.d.ts.map +1 -0
  161. package/dist/traceability/trusted-pr-event.js +105 -0
  162. package/dist/utils/canonical-path.d.ts +3 -0
  163. package/dist/utils/canonical-path.d.ts.map +1 -0
  164. package/dist/utils/canonical-path.js +33 -0
  165. package/package.json +14 -5
  166. package/src/public/impact/analyzer.ts +88 -5
  167. package/src/public/impact/diagnostics.ts +7 -5
  168. package/src/public/impact/manifest.ts +14 -1
  169. package/src/public/impact/source-changes.ts +39 -34
  170. package/src/public/impact/types.ts +5 -0
  171. package/src/public/operations/catalog.ts +2 -0
  172. package/src/public/operations/contracts.ts +385 -9
  173. package/src/public/operations/impact-review-preparation.ts +548 -0
  174. package/src/public/operations/index.ts +2 -0
  175. package/src/public/operations/node-ports.ts +1 -39
  176. package/src/public/operations/proof-receipt-projection.ts +39 -0
  177. package/src/public/operations/specs/impact-review-preparation.ts +46 -0
  178. package/src/public/operations/types.ts +1 -0
  179. package/src/public/skills/kibi-usage/resources/operation-access.md +1 -0
  180. package/src/traceability/impact-policy.v1.schema.json +48 -0
  181. package/src/traceability/impact-review.schema.json +365 -0
  182. package/templates/github/kibi-badge.yml +0 -4
  183. package/templates/github/kibi-report.yml +0 -4
@@ -0,0 +1,446 @@
1
+ /*
2
+ * Kibi — repo-local, per-branch, queryable long-term memory for software projects
3
+ * Copyright (C) 2026 Piotr Franczyk
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ */
10
+ /**
11
+ * Resolves the SWI-Prolog executable Kibi runs, in this order:
12
+ *
13
+ * 1. `KIBI_SWIPL=<absolute path>` (an explicit operator choice always wins).
14
+ * 2. The bundled platform package (`kibi-swipl-<platform>`), after its build
15
+ * manifest and binary checksum verify. `KIBI_SWIPL=system` skips this step.
16
+ * 3. `swipl` on `PATH`, subject to the SWI-Prolog 9.0 minimum.
17
+ * 4. Otherwise an error naming the platform, the platform package that would
18
+ * have covered it, and the OS install command.
19
+ */
20
+ import { execFileSync, spawnSync } from "node:child_process";
21
+ import { constants, accessSync, existsSync, readFileSync, statSync, } from "node:fs";
22
+ import path from "node:path";
23
+ import { BUNDLED_BINARY_PATH, MANIFEST_FILE, PLATFORM_PACKAGES, locatePlatformPackage, platformKey, sha256File, validateBuildManifest, } from "kibi-swipl";
24
+ export const MINIMUM_SWIPL_MAJOR = 9;
25
+ /** Structured failure; `message` is the complete operator-facing guidance. */
26
+ export class SwiplResolutionError extends Error {
27
+ code;
28
+ /** Detected host, e.g. "linux-x64 (glibc)". */
29
+ platform;
30
+ /** Platform package that would have covered this host, when one exists. */
31
+ packageName;
32
+ constructor(code, message, platform, packageName) {
33
+ super(message);
34
+ this.name = "SwiplResolutionError";
35
+ this.code = code;
36
+ this.platform = platform;
37
+ this.packageName = packageName;
38
+ }
39
+ }
40
+ function detectGlibc() {
41
+ if (process.platform !== "linux")
42
+ return undefined;
43
+ try {
44
+ const report = process.report?.getReport?.();
45
+ if (report !== undefined) {
46
+ return typeof report.header?.glibcVersionRuntime === "string";
47
+ }
48
+ }
49
+ catch {
50
+ // Fall back to the musl loader probe below.
51
+ }
52
+ return !["/lib/ld-musl-x86_64.so.1", "/lib/ld-musl-aarch64.so.1"].some((loader) => existsSync(loader));
53
+ }
54
+ function defaultRunVersion(bin) {
55
+ return execFileSync(bin, ["--version"], {
56
+ encoding: "utf8",
57
+ timeout: 10_000,
58
+ stdio: ["ignore", "pipe", "pipe"],
59
+ });
60
+ }
61
+ function defaultDeps() {
62
+ return {
63
+ platform: process.platform,
64
+ arch: process.arch,
65
+ glibc: detectGlibc(),
66
+ locatePackage: (name) => locatePlatformPackage(name),
67
+ runVersion: defaultRunVersion,
68
+ };
69
+ }
70
+ /** Parse `SWI-Prolog version 10.0.2 for x86_64-linux` into its version. */
71
+ export function parseSwiplVersion(output) {
72
+ const match = /version\s+(\d+)\.(\d+)(?:\.(\d+))?/i.exec(output);
73
+ if (match === null)
74
+ return undefined;
75
+ const [, major, minor, patch] = match;
76
+ return {
77
+ version: `${major}.${minor}${patch === undefined ? "" : `.${patch}`}`,
78
+ major: Number.parseInt(major ?? "0", 10),
79
+ };
80
+ }
81
+ function installCommand(platform) {
82
+ if (platform === "linux") {
83
+ return "sudo apt-add-repository ppa:swi-prolog/stable && sudo apt-get update && sudo apt-get install swi-prolog";
84
+ }
85
+ if (platform === "darwin")
86
+ return "brew install swi-prolog";
87
+ return "download the installer from https://www.swi-prolog.org/download/stable and add swipl to PATH";
88
+ }
89
+ function hostLabel(deps) {
90
+ const libc = deps.platform === "linux" && deps.glibc !== undefined
91
+ ? ` (${deps.glibc ? "glibc" : "musl"})`
92
+ : "";
93
+ return `${deps.platform}-${deps.arch}${libc}`;
94
+ }
95
+ function isExecutableFile(candidate) {
96
+ try {
97
+ if (!statSync(candidate).isFile())
98
+ return false;
99
+ accessSync(candidate, constants.X_OK);
100
+ return true;
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
106
+ function inspectBundle(deps) {
107
+ const key = platformKey({
108
+ platform: deps.platform,
109
+ arch: deps.arch,
110
+ glibc: deps.glibc,
111
+ });
112
+ if (key === undefined)
113
+ return { kind: "unsupported" };
114
+ const packageName = PLATFORM_PACKAGES[key].package;
115
+ const packageDir = deps.locatePackage(packageName);
116
+ if (packageDir === undefined)
117
+ return { kind: "missing", key, packageName };
118
+ const manifestPath = path.join(packageDir, MANIFEST_FILE);
119
+ const binary = path.join(packageDir, BUNDLED_BINARY_PATH);
120
+ if (!existsSync(manifestPath)) {
121
+ // A workspace checkout links the package without the pipeline payload.
122
+ // A binary without a manifest is a damaged install, not a placeholder.
123
+ if (!existsSync(binary))
124
+ return { kind: "empty", key, packageName };
125
+ return {
126
+ kind: "corrupt",
127
+ key,
128
+ packageName,
129
+ reason: `${MANIFEST_FILE} is missing next to ${BUNDLED_BINARY_PATH}`,
130
+ };
131
+ }
132
+ const corrupt = (reason) => ({
133
+ kind: "corrupt",
134
+ key,
135
+ packageName,
136
+ reason,
137
+ });
138
+ let manifest;
139
+ let packageJson;
140
+ try {
141
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
142
+ }
143
+ catch (error) {
144
+ return corrupt(`${MANIFEST_FILE} is not valid JSON (${error instanceof Error ? error.message : String(error)})`);
145
+ }
146
+ try {
147
+ packageJson = JSON.parse(readFileSync(path.join(packageDir, "package.json"), "utf8"));
148
+ }
149
+ catch (error) {
150
+ return corrupt(`package.json is unreadable (${error instanceof Error ? error.message : String(error)})`);
151
+ }
152
+ const packageVersion = packageJson.kibi?.swiplVersion;
153
+ const checked = validateBuildManifest(manifest, {
154
+ target: key,
155
+ swiplVersion: typeof packageVersion === "string" ? packageVersion : undefined,
156
+ });
157
+ if (!checked.ok)
158
+ return corrupt(checked.reason);
159
+ if (!isExecutableFile(binary)) {
160
+ return corrupt(`${BUNDLED_BINARY_PATH} is missing or not executable`);
161
+ }
162
+ let actual;
163
+ try {
164
+ actual = sha256File(binary);
165
+ }
166
+ catch (error) {
167
+ return corrupt(`${BUNDLED_BINARY_PATH} is unreadable (${error instanceof Error ? error.message : String(error)})`);
168
+ }
169
+ if (actual !== checked.binarySha256) {
170
+ return corrupt(`${BUNDLED_BINARY_PATH} SHA-256 ${actual} does not match the manifest (${checked.binarySha256})`);
171
+ }
172
+ const home = path.join(packageDir, checked.home);
173
+ let homeIsDirectory = false;
174
+ try {
175
+ homeIsDirectory = statSync(home).isDirectory();
176
+ }
177
+ catch {
178
+ // reported below
179
+ }
180
+ if (!homeIsDirectory)
181
+ return corrupt(`${checked.home} is missing`);
182
+ return {
183
+ kind: "ok",
184
+ key,
185
+ packageName,
186
+ resolved: {
187
+ bin: binary,
188
+ home,
189
+ version: checked.swiplVersion,
190
+ source: "bundled",
191
+ },
192
+ };
193
+ }
194
+ function probeVersion(bin, deps) {
195
+ let output;
196
+ try {
197
+ output = deps.runVersion(bin);
198
+ }
199
+ catch (error) {
200
+ return {
201
+ kind: "unusable",
202
+ detail: error instanceof Error ? error.message : String(error),
203
+ };
204
+ }
205
+ const parsed = parseSwiplVersion(output);
206
+ if (parsed === undefined) {
207
+ return {
208
+ kind: "unusable",
209
+ detail: `unrecognized --version output ${JSON.stringify(output.trim().slice(0, 80))}`,
210
+ };
211
+ }
212
+ if (parsed.major < MINIMUM_SWIPL_MAJOR) {
213
+ return { kind: "too-old", version: parsed.version };
214
+ }
215
+ return { kind: "ok", version: parsed.version };
216
+ }
217
+ function findOnPath(env, deps) {
218
+ for (const directory of (env.PATH ?? "").split(path.delimiter)) {
219
+ if (directory === "" || !path.isAbsolute(directory))
220
+ continue;
221
+ const candidate = path.join(directory, "swipl");
222
+ if (!isExecutableFile(candidate))
223
+ continue;
224
+ const probe = probeVersion(candidate, deps);
225
+ if (probe.kind === "ok") {
226
+ return {
227
+ kind: "ok",
228
+ resolved: { bin: candidate, version: probe.version, source: "path" },
229
+ };
230
+ }
231
+ return probe.kind === "too-old"
232
+ ? { kind: "too-old", bin: candidate, version: probe.version }
233
+ : { kind: "unusable", bin: candidate, detail: probe.detail };
234
+ }
235
+ return { kind: "absent" };
236
+ }
237
+ function resolveFromEnv(override, deps) {
238
+ const platform = hostLabel(deps);
239
+ const key = platformKey({
240
+ platform: deps.platform,
241
+ arch: deps.arch,
242
+ glibc: deps.glibc,
243
+ });
244
+ const packageName = key === undefined ? undefined : PLATFORM_PACKAGES[key].package;
245
+ const invalid = (detail) => new SwiplResolutionError("swipl_env_invalid", `KIBI_SWIPL=${override} ${detail}. Set KIBI_SWIPL to the absolute path of a SWI-Prolog ${MINIMUM_SWIPL_MAJOR}.0+ executable, to "system" to use swipl from PATH, or unset it to use the bundled runtime.`, platform, packageName);
246
+ if (!path.isAbsolute(override))
247
+ return fail(invalid("is not an absolute path"));
248
+ if (!isExecutableFile(override)) {
249
+ return fail(invalid("is not an executable file"));
250
+ }
251
+ const probe = probeVersion(override, deps);
252
+ if (probe.kind === "unusable") {
253
+ return fail(invalid(`could not report its version (${probe.detail})`));
254
+ }
255
+ if (probe.kind === "too-old") {
256
+ return fail(invalid(`is SWI-Prolog ${probe.version}, but ${MINIMUM_SWIPL_MAJOR}.0 or newer is required`));
257
+ }
258
+ return { bin: override, version: probe.version, source: "env" };
259
+ }
260
+ function fail(error) {
261
+ throw error;
262
+ }
263
+ function notFoundError(deps, bundle, lookup) {
264
+ const platform = hostLabel(deps);
265
+ const command = installCommand(deps.platform);
266
+ const lines = [
267
+ `Kibi could not find a usable SWI-Prolog (${MINIMUM_SWIPL_MAJOR}.0 or newer is required).`,
268
+ `Detected platform: ${platform}.`,
269
+ ];
270
+ let packageName;
271
+ if (bundle === "skipped") {
272
+ lines.push("Bundled SWI-Prolog: skipped because KIBI_SWIPL=system.");
273
+ const key = platformKey({
274
+ platform: deps.platform,
275
+ arch: deps.arch,
276
+ glibc: deps.glibc,
277
+ });
278
+ if (key !== undefined)
279
+ packageName = PLATFORM_PACKAGES[key].package;
280
+ }
281
+ else if (bundle.kind === "unsupported") {
282
+ lines.push(`Bundled SWI-Prolog: no bundled build exists for ${platform} (supported: ${Object.keys(PLATFORM_PACKAGES).join(", ")}).`);
283
+ }
284
+ else {
285
+ packageName = bundle.packageName;
286
+ if (bundle.kind === "empty") {
287
+ lines.push(`Bundled SWI-Prolog: package ${bundle.packageName} is installed but carries no runtime (a source checkout that was not populated from a release build).`);
288
+ }
289
+ else {
290
+ lines.push(`Bundled SWI-Prolog: package ${bundle.packageName} would cover this platform but is not installed. It is skipped by installs that omit optional dependencies (npm --omit=optional, --no-optional) and by pnpm supportedArchitectures settings that exclude this platform.`);
291
+ lines.push(` Add it with: npm install --save-dev ${bundle.packageName}`);
292
+ }
293
+ }
294
+ if (bundle === "skipped" && packageName !== undefined) {
295
+ lines.push(` To use it, unset KIBI_SWIPL and install ${packageName}.`);
296
+ }
297
+ switch (lookup.kind) {
298
+ case "absent":
299
+ lines.push("System SWI-Prolog: swipl was not found on PATH.");
300
+ break;
301
+ case "too-old":
302
+ lines.push(`System SWI-Prolog: swipl on PATH (${lookup.bin}) is version ${lookup.version}, older than ${MINIMUM_SWIPL_MAJOR}.0.`);
303
+ break;
304
+ case "unusable":
305
+ lines.push(`System SWI-Prolog: swipl on PATH (${lookup.bin}) could not report its version (${lookup.detail}).`);
306
+ break;
307
+ case "ok":
308
+ break;
309
+ }
310
+ lines.push(` Install it with: ${command}`);
311
+ lines.push(`Or set KIBI_SWIPL to the absolute path of a SWI-Prolog ${MINIMUM_SWIPL_MAJOR}.0+ executable.`);
312
+ return new SwiplResolutionError("swipl_not_found", lines.join("\n"), platform, packageName);
313
+ }
314
+ /** Uncached resolution with injectable host facts; the test seam. */
315
+ export function resolveSwiplWith(env, deps) {
316
+ const override = env.KIBI_SWIPL?.trim() ?? "";
317
+ if (override !== "" && override !== "system") {
318
+ return resolveFromEnv(override, deps);
319
+ }
320
+ let bundle = "skipped";
321
+ if (override === "") {
322
+ bundle = inspectBundle(deps);
323
+ if (bundle.kind === "ok")
324
+ return bundle.resolved;
325
+ if (bundle.kind === "corrupt") {
326
+ throw new SwiplResolutionError("swipl_bundle_corrupt", [
327
+ `The bundled SWI-Prolog package ${bundle.packageName} is damaged: ${bundle.reason}.`,
328
+ `Detected platform: ${hostLabel(deps)}.`,
329
+ `Reinstall it (for example: npm install --save-dev ${bundle.packageName}), or set KIBI_SWIPL=system to use swipl from PATH instead.`,
330
+ ].join("\n"), hostLabel(deps), bundle.packageName);
331
+ }
332
+ }
333
+ const lookup = findOnPath(env, deps);
334
+ if (lookup.kind === "ok")
335
+ return lookup.resolved;
336
+ throw notFoundError(deps, bundle, lookup);
337
+ }
338
+ const cache = new Map();
339
+ /**
340
+ * Resolve the SWI-Prolog executable for this process. The result is cached for
341
+ * the lifetime of the process, keyed by the inputs that select it.
342
+ */
343
+ export function resolveSwipl(env = process.env) {
344
+ const key = `${env.KIBI_SWIPL ?? ""}\0${env.PATH ?? ""}`;
345
+ const cached = cache.get(key);
346
+ if (cached !== undefined)
347
+ return cached;
348
+ const resolved = resolveSwiplWith(env, defaultDeps());
349
+ cache.set(key, resolved);
350
+ return resolved;
351
+ }
352
+ /** Test seam: forget every cached resolution. */
353
+ export function resetSwiplResolverCache() {
354
+ cache.clear();
355
+ }
356
+ /** Stable identity of a resolved runtime, compared across engine processes. */
357
+ export function swiplIdentity(resolved) {
358
+ return `${resolved.bin}@${resolved.version}`;
359
+ }
360
+ /** Environment additions a child needs to run the resolved SWI-Prolog. */
361
+ export function swiplChildEnv(resolved) {
362
+ return resolved.home === undefined ? {} : { SWI_HOME_DIR: resolved.home };
363
+ }
364
+ /** Report the bundled package's state without resolving a runtime. */
365
+ export function inspectSwiplBundle(deps = defaultDeps()) {
366
+ const bundle = inspectBundle(deps);
367
+ switch (bundle.kind) {
368
+ case "unsupported":
369
+ return { state: "unsupported" };
370
+ case "missing":
371
+ case "empty":
372
+ return { state: bundle.kind, packageName: bundle.packageName };
373
+ case "corrupt":
374
+ return {
375
+ state: "corrupt",
376
+ packageName: bundle.packageName,
377
+ reason: bundle.reason,
378
+ };
379
+ case "ok":
380
+ return { state: "installed", packageName: bundle.packageName };
381
+ }
382
+ }
383
+ /** Libraries Kibi loads at runtime; a SWI-Prolog lacking one cannot serve it. */
384
+ export const REQUIRED_SWIPL_LIBRARIES = [
385
+ "semweb/rdf_db",
386
+ "semweb/rdf_persistency",
387
+ "semweb/sparql_client",
388
+ "pcre",
389
+ "crypto",
390
+ "sha",
391
+ "http/json",
392
+ "http/json_convert",
393
+ "chr",
394
+ "clpfd",
395
+ "thread",
396
+ "persistency",
397
+ "filesex",
398
+ "readutil",
399
+ "date",
400
+ "aggregate",
401
+ "solution_sequences",
402
+ ];
403
+ const MISSING_LIBRARY_MARKER = "__KIBI_MISSING_LIBRARY__:";
404
+ function defaultGoalRunner(bin, args, env) {
405
+ const result = spawnSync(bin, [...args], {
406
+ encoding: "utf8",
407
+ timeout: 60_000,
408
+ stdio: ["ignore", "pipe", "pipe"],
409
+ env,
410
+ });
411
+ return {
412
+ status: result.status,
413
+ stdout: result.stdout ?? "",
414
+ stderr: result.stderr ?? "",
415
+ ...(result.error === undefined ? {} : { error: result.error }),
416
+ };
417
+ }
418
+ /**
419
+ * Load every required library in one throwaway SWI-Prolog process and report
420
+ * the ones that fail. A probe that cannot run is reported as such, never as a
421
+ * pass.
422
+ */
423
+ export function probeRequiredLibraries(resolved, run = defaultGoalRunner, env = process.env) {
424
+ const goal = `forall(member(L, [${REQUIRED_SWIPL_LIBRARIES.join(", ")}]), (catch(use_module(library(L)), _, fail) -> true ; format("${MISSING_LIBRARY_MARKER}~w~n", [L])))`;
425
+ const result = run(resolved.bin, ["-q", "-g", goal, "-t", "halt"], {
426
+ ...env,
427
+ ...swiplChildEnv(resolved),
428
+ });
429
+ if (result.error !== undefined) {
430
+ return { kind: "failed", detail: result.error.message };
431
+ }
432
+ if (result.status !== 0) {
433
+ const detail = result.stderr.trim().split("\n")[0] ?? "";
434
+ return {
435
+ kind: "failed",
436
+ detail: `exit status ${result.status ?? "none"}${detail === "" ? "" : `: ${detail}`}`,
437
+ };
438
+ }
439
+ const libraries = result.stdout
440
+ .split("\n")
441
+ .filter((line) => line.startsWith(MISSING_LIBRARY_MARKER))
442
+ .map((line) => line.slice(MISSING_LIBRARY_MARKER.length).trim());
443
+ return libraries.length === 0
444
+ ? { kind: "ok" }
445
+ : { kind: "missing", libraries };
446
+ }
package/dist/prolog.d.ts CHANGED
@@ -1,7 +1,14 @@
1
1
  import { type PrologErrorRecord } from "./prolog/error-terms.js";
2
+ import { type ResolvedSwipl } from "./prolog/swipl-resolver.js";
2
3
  export declare function resolveKbPlPath(): string;
3
4
  export interface PrologOptions {
5
+ /**
6
+ * Explicit SWI-Prolog executable. When omitted, the executable comes from
7
+ * the resolver (KIBI_SWIPL, the bundled platform package, then PATH).
8
+ */
4
9
  swiplPath?: string;
10
+ /** Resolver seam for tests; defaults to the per-process SWI-Prolog resolver. */
11
+ swiplResolver?: () => ResolvedSwipl;
5
12
  timeout?: number;
6
13
  /**
7
14
  * Force one-shot SWI execution. Production callers are Node-only and keep
@@ -37,7 +44,8 @@ export declare function registerProcessExitOnce(current: (() => void) | null, ha
37
44
  export declare function bindProcessExitHandler(current: (() => void) | null, terminate: () => void | Promise<void>, on?: (event: "exit", listener: () => void) => void): () => void;
38
45
  export declare class PrologProcess {
39
46
  private process;
40
- private swiplPath;
47
+ private explicitSwiplPath;
48
+ private readonly swiplResolver;
41
49
  private timeout;
42
50
  private outputBuffer;
43
51
  private outputBufferBytes;
@@ -71,6 +79,12 @@ export declare class PrologProcess {
71
79
  * restart it (and re-establish its session state) before querying again.
72
80
  */
73
81
  needsRestart(): boolean;
82
+ /**
83
+ * The executable and SWI home to launch. An explicit `swiplPath` is used
84
+ * verbatim; otherwise the per-process resolver decides, so a missing or
85
+ * damaged runtime fails with its complete remediation text.
86
+ */
87
+ private launchTarget;
74
88
  start(): Promise<void>;
75
89
  private waitForReady;
76
90
  query(goal: string | string[]): Promise<QueryResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prolog.d.ts","sourceRoot":"","sources":["../src/prolog.ts"],"names":[],"mappings":"AA0BA,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,KAAK,aAAa,EAGnB,MAAM,4BAA4B,CAAC;AA2BpC,wBAAgB,eAAe,IAAI,MAAM,CAsCxC;AACD,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,aAAa,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,IAAI,+BAA+B;IAC5C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM;CAO3B;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;IACf,WAAW,CAAC,EAAE,iBAAiB,CAAC;CACjC;AAGD,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,EAC5B,OAAO,EAAE,MAAM,IAAI,EACnB,EAAE,GAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,IAChB,GAC5B,MAAM,IAAI,CAIZ;AAGD,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,EAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACrC,EAAE,GAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,IAChB,GAC5B,MAAM,IAAI,CAQZ;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,iBAAiB,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAsB;IACpD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,YAAY,CAAM;IAC1B,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,WAAW,CAAM;IACzB,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,KAAK,CAAuC;IACpD,OAAO,CAAC,oBAAoB,CAAoC;IAChE,OAAO,CAAC,kBAAkB,CAA8B;IACxD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,cAAc,CAAU;IAChC,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,iBAAiB,CAAuB;gBAEpC,OAAO,GAAE,aAAkB;IAiBvC,wBAAwB,CACtB,EAAE,GAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,IAChB,GAC5B,IAAI;IAQP;;;OAGG;IACH,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED;;;OAGG;IACH,YAAY,IAAI,OAAO;IAIvB;;;;OAIG;IACH,OAAO,CAAC,YAAY;IAOd,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAsEd,YAAY;IAgDpB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IA+O1D,wEAAwE;IAClE,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC;IAIhE,eAAe,IAAI,IAAI;IAIvB,OAAO,CAAC,eAAe;IAwBvB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,kBAAkB;YAIZ,YAAY;IA2C1B,OAAO,CAAC,WAAW;IAkOnB,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,iBAAiB;IAazB,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,SAAS;IAOjB,OAAO,CAAC,kBAAkB;IAU1B,OAAO,CAAC,mBAAmB;IAK3B,OAAO,CAAC,iBAAiB;YAkBX,oBAAoB;IAyBlC,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,eAAe;IAmCvB,sEAAsE;IACtE,OAAO,CAAC,mBAAmB;IAY3B,OAAO,CAAC,cAAc;IAoGtB,SAAS,IAAI,OAAO;IASpB,MAAM,IAAI,MAAM;IAIhB,OAAO,CAAC,gBAAgB;IASlB,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;CAqCjC"}
package/dist/prolog.js CHANGED
@@ -19,9 +19,12 @@ import { spawn } from "node:child_process";
19
19
  import { existsSync } from "node:fs";
20
20
  import { createRequire } from "node:module";
21
21
  import path from "node:path";
22
+ import { performance } from "node:perf_hooks";
22
23
  import { fileURLToPath } from "node:url";
23
24
  import { getKbPlPathOverride, isPrologDebugEnabled } from "./env.js";
25
+ import { writePerformanceTraceEvent } from "./performance-trace.js";
24
26
  import { extractPrologErrorRecord, } from "./prolog/error-terms.js";
27
+ import { resolveSwipl, swiplChildEnv, } from "./prolog/swipl-resolver.js";
25
28
  const importMetaDir = path.dirname(fileURLToPath(import.meta.url));
26
29
  const DEFAULT_PROLOG_OUTPUT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
27
30
  const PROLOG_OUTPUT_OVERFLOW_ERROR = "Query exceeded bounded Prolog output capacity (ENOBUFS); narrow the operation or reduce stored entity size";
@@ -110,7 +113,8 @@ export function bindProcessExitHandler(current, terminate, on = (event, listener
110
113
  }
111
114
  export class PrologProcess {
112
115
  process = null;
113
- swiplPath;
116
+ explicitSwiplPath;
117
+ swiplResolver;
114
118
  timeout;
115
119
  outputBuffer = "";
116
120
  outputBufferBytes = 0;
@@ -133,7 +137,8 @@ export class PrologProcess {
133
137
  interactiveStarted = false;
134
138
  terminationReason = null;
135
139
  constructor(options = {}) {
136
- this.swiplPath = options.swiplPath || "swipl";
140
+ this.explicitSwiplPath = options.swiplPath || undefined;
141
+ this.swiplResolver = options.swiplResolver ?? (() => resolveSwipl());
137
142
  this.timeout = options.timeout || 30000;
138
143
  this.maxOutputBytes =
139
144
  options.maxOutputBytes !== undefined &&
@@ -164,15 +169,31 @@ export class PrologProcess {
164
169
  needsRestart() {
165
170
  return this.interactiveStarted && !this.isProcessUsable();
166
171
  }
172
+ /**
173
+ * The executable and SWI home to launch. An explicit `swiplPath` is used
174
+ * verbatim; otherwise the per-process resolver decides, so a missing or
175
+ * damaged runtime fails with its complete remediation text.
176
+ */
177
+ launchTarget() {
178
+ const explicit = this.explicitSwiplPath;
179
+ if (explicit !== undefined)
180
+ return { bin: explicit, env: {} };
181
+ const resolved = this.swiplResolver();
182
+ return { bin: resolved.bin, env: swiplChildEnv(resolved) };
183
+ }
167
184
  async start() {
168
- if (!existsSync(this.swiplPath) && this.swiplPath !== "swipl") {
169
- throw new Error(`SWI-Prolog not found at ${this.swiplPath}. Please install SWI-Prolog or check your PATH.`);
185
+ const explicit = this.explicitSwiplPath;
186
+ if (explicit !== undefined &&
187
+ !existsSync(explicit) &&
188
+ explicit !== "swipl") {
189
+ throw new Error(`SWI-Prolog not found at ${explicit}. Please install SWI-Prolog or check your PATH.`);
170
190
  }
191
+ const launch = this.launchTarget();
171
192
  const kbPath = resolveKbPlPath();
172
193
  this.interactiveStarted = true;
173
194
  this.terminationReason = null;
174
195
  this.invalidateCache();
175
- this.process = spawn(this.swiplPath, [
196
+ this.process = spawn(launch.bin, [
176
197
  "-g",
177
198
  `use_module('${kbPath}'), use_module(library(semweb/rdf_db)), set_prolog_flag(answer_write_options, [max_depth(0), quoted(true)])`,
178
199
  "--quiet",
@@ -180,6 +201,7 @@ export class PrologProcess {
180
201
  detached: process.platform !== "win32",
181
202
  env: {
182
203
  ...process.env,
204
+ ...launch.env,
183
205
  KIBI_RUNTIME_NAME: process.versions.bun !== undefined ? "bun" : "node",
184
206
  KIBI_RUNTIME_VERSION: process.versions.bun ?? process.versions.node ?? "unknown",
185
207
  KIBI_PACKAGE_VERSIONS: process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS,
@@ -243,12 +265,23 @@ export class PrologProcess {
243
265
  }
244
266
  // implements REQ-core-prolog-process-management
245
267
  async query(goal) {
268
+ // A lost interactive session must fail before any cached answer is served.
269
+ if (!this.useOneShotMode && this.needsRestart()) {
270
+ throw this.lostProcessError();
271
+ }
246
272
  const isSingleGoal = typeof goal === "string";
247
273
  const goalKey = isSingleGoal ? goal : null;
248
274
  const cacheable = goalKey !== null && this.isCacheableGoal(goalKey);
249
275
  if (cacheable) {
276
+ const cacheLookupStartedAt = process.env.KIBI_PERF_TIMINGS === "1" ? performance.now() : null;
250
277
  const cachedResult = this.cache.get(goalKey);
251
278
  if (cachedResult) {
279
+ if (cacheLookupStartedAt !== null) {
280
+ writePerformanceTraceEvent({
281
+ kind: "prolog-process-cache-hit",
282
+ durationMs: performance.now() - cacheLookupStartedAt,
283
+ });
284
+ }
252
285
  return cachedResult;
253
286
  }
254
287
  }
@@ -416,13 +449,23 @@ export class PrologProcess {
416
449
  releaseQuery = resolve;
417
450
  });
418
451
  await previousQuery;
452
+ const tracingEnabled = process.env.KIBI_PERF_TIMINGS === "1";
453
+ let roundTripStartedAt = null;
419
454
  try {
420
455
  if (!this.isProcessUsable()) {
421
456
  throw this.lostProcessError();
422
457
  }
458
+ if (tracingEnabled)
459
+ roundTripStartedAt = performance.now();
423
460
  return await runInteractiveQuery();
424
461
  }
425
462
  finally {
463
+ if (roundTripStartedAt !== null) {
464
+ writePerformanceTraceEvent({
465
+ kind: "prolog-round-trip",
466
+ durationMs: performance.now() - roundTripStartedAt,
467
+ });
468
+ }
426
469
  releaseQuery();
427
470
  }
428
471
  }
@@ -534,10 +577,12 @@ export class PrologProcess {
534
577
  const packageVersions = process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS;
535
578
  let child;
536
579
  try {
537
- child = spawn(this.swiplPath, ["-q", "-g", prologGoal, "-t", "halt"], {
580
+ const launch = this.launchTarget();
581
+ child = spawn(launch.bin, ["-q", "-g", prologGoal, "-t", "halt"], {
538
582
  detached: process.platform !== "win32",
539
583
  env: {
540
584
  ...process.env,
585
+ ...launch.env,
541
586
  KIBI_GOAL: combinedGoal,
542
587
  KIBI_RUNTIME_NAME: runtimeName,
543
588
  KIBI_RUNTIME_VERSION: runtimeVersion,
@@ -820,6 +865,20 @@ export class PrologProcess {
820
865
  // Diagnostic markers intentionally contain words such as `lock` and the
821
866
  // audit path. Remove them before classifying the actual Prolog error so a
822
867
  // contradiction at the check stage is not mistaken for an audit lock.
868
+ // Preserve only bounded identifiers from the formal existence error. Its
869
+ // context can contain authored dictionaries or query data and is never shown.
870
+ const existence = errorText.match(/^__KIBI_ERROR__:error\(existence_error\((key|procedure),([^,()\r\n]+)(?:,|\),)/m);
871
+ const identifier = existence?.[2]?.trim().replace(/^'([^']+)'$/, "$1");
872
+ if (existence?.[1] === "key" && identifier !== undefined) {
873
+ if (/^[A-Za-z][A-Za-z0-9_-]{0,95}$/.test(identifier)) {
874
+ return `Predicate or file not found (missing dictionary key: ${identifier})`;
875
+ }
876
+ }
877
+ if (existence?.[1] === "procedure" && identifier !== undefined) {
878
+ if (/^(?:[A-Za-z][A-Za-z0-9_]{0,95}:)?[A-Za-z][A-Za-z0-9_]{0,95}\/[0-9]{1,3}$/.test(identifier)) {
879
+ return `Predicate or file not found (missing procedure: ${identifier})`;
880
+ }
881
+ }
823
882
  const cleanError = errorText.replace(/^__KIBI_(?:STAGE|RUNTIME|ERROR)__:[^\r\n]*\r?\n?/gm, "");
824
883
  if (cleanError.includes("stale_snapshot")) {
825
884
  return "KB snapshot is stale; reattach or refresh the runtime before retrying (stale_snapshot)";
@@ -861,6 +920,8 @@ export class PrologProcess {
861
920
  cleanError.includes("Unknown procedure")) {
862
921
  return "Predicate or file not found";
863
922
  }
923
+ if (existence !== null)
924
+ return "Predicate or file not found";
864
925
  if (cleanError.includes("permission_error")) {
865
926
  return "Access denied or KB locked";
866
927
  }
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CAmElC"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../../src/public/impact/analyzer.ts"],"names":[],"mappings":"AA6BA,OAAO,KAAK,EACV,wBAAwB,EACxB,uBAAuB,EACxB,MAAM,YAAY,CAAC;AAEpB,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,uBAAuB,CAAC,CA2IlC"}