kibi-cli 0.16.1 → 0.18.1
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-json-command.d.ts.map +1 -1
- package/dist/cli-json-command.js +87 -11
- package/dist/cli-register-maintenance.d.ts.map +1 -1
- package/dist/cli-register-maintenance.js +8 -0
- package/dist/cli-register-reporting.d.ts.map +1 -1
- package/dist/cli-register-reporting.js +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/commands/aggregated-checks.d.ts.map +1 -1
- package/dist/commands/aggregated-checks.js +1 -0
- package/dist/commands/check.d.ts.map +1 -1
- package/dist/commands/check.js +1 -1
- package/dist/commands/coverage.d.ts +5 -0
- package/dist/commands/coverage.d.ts.map +1 -1
- package/dist/commands/coverage.js +5 -0
- package/dist/commands/discovery-table.d.ts.map +1 -1
- package/dist/commands/discovery-table.js +96 -5
- package/dist/commands/sync/cache.d.ts +2 -0
- package/dist/commands/sync/cache.d.ts.map +1 -1
- package/dist/commands/sync/cache.js +8 -0
- package/dist/commands/sync/manifest.d.ts +12 -1
- package/dist/commands/sync/manifest.d.ts.map +1 -1
- package/dist/commands/sync/manifest.js +22 -5
- package/dist/commands/sync/persistence.d.ts.map +1 -1
- package/dist/commands/sync/persistence.js +76 -2
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +72 -15
- package/dist/commands/usage-metrics.d.ts +66 -2
- package/dist/commands/usage-metrics.d.ts.map +1 -1
- package/dist/commands/usage-metrics.js +34 -24
- package/dist/commands/usage-remediation.d.ts +9 -0
- package/dist/commands/usage-remediation.d.ts.map +1 -0
- package/dist/commands/usage-remediation.js +52 -0
- package/dist/extractors/markdown.d.ts +17 -1
- package/dist/extractors/markdown.d.ts.map +1 -1
- package/dist/extractors/markdown.js +81 -2
- package/dist/logic/ir.d.ts +112 -0
- package/dist/logic/ir.d.ts.map +1 -0
- package/dist/logic/ir.js +729 -0
- package/dist/operations/modeling/logic-modeling.d.ts +22 -0
- package/dist/operations/modeling/logic-modeling.d.ts.map +1 -0
- package/dist/operations/modeling/logic-modeling.js +120 -0
- package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
- package/dist/operations/modeling/model-requirement.js +83 -2
- package/dist/operations/modeling/predicate-applyplan.d.ts +1 -1
- package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-applyplan.js +29 -4
- package/dist/operations/modeling/predicate-inference.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-inference.js +3 -0
- package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-loader.js +7 -1
- package/dist/operations/modeling/predicate-ranker.d.ts.map +1 -1
- package/dist/operations/modeling/predicate-ranker.js +40 -2
- package/dist/operations/modeling/predicate-types.d.ts +12 -1
- package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
- package/dist/operations/modeling/requirement-types.d.ts +12 -0
- package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
- package/dist/operations/modeling/suggest-predicates.js +35 -11
- package/dist/operations/mutation/audit.js +1 -1
- package/dist/operations/mutation/contradictions.d.ts +1 -1
- package/dist/operations/mutation/contradictions.d.ts.map +1 -1
- package/dist/operations/mutation/contradictions.js +23 -18
- package/dist/operations/mutation/relationships.d.ts.map +1 -1
- package/dist/operations/mutation/relationships.js +8 -0
- package/dist/operations/mutation/serialization.d.ts.map +1 -1
- package/dist/operations/mutation/serialization.js +6 -0
- package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
- package/dist/operations/mutation/strict-fact.js +68 -3
- package/dist/operations/mutation/upsert.d.ts +3 -1
- package/dist/operations/mutation/upsert.d.ts.map +1 -1
- package/dist/operations/mutation/upsert.js +51 -12
- package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
- package/dist/operations/mutation/validate-upsert.js +18 -2
- package/dist/operations/mutation/validation.d.ts.map +1 -1
- package/dist/operations/mutation/validation.js +8 -0
- package/dist/operations/semantic-advisor/analysis-receipt.d.ts +2 -2
- package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analysis-receipt.js +30 -16
- package/dist/operations/semantic-advisor/analyze-prose.d.ts +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/analyze-prose.js +464 -27
- package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
- package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/clauses.js +7 -7
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts +18 -0
- package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -0
- package/dist/operations/semantic-advisor/ingestion-boundary.js +167 -0
- package/dist/operations/semantic-advisor/observation-plan.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/observation-plan.js +15 -3
- package/dist/operations/semantic-advisor/shared.d.ts +9 -0
- package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
- package/dist/operations/semantic-advisor/shared.js +38 -4
- package/dist/operations/semantic-advisor/types.d.ts +66 -1
- package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
- package/dist/prolog/codec.d.ts.map +1 -1
- package/dist/prolog/codec.js +19 -2
- package/dist/prolog.d.ts +6 -0
- package/dist/prolog.d.ts.map +1 -1
- package/dist/prolog.js +247 -63
- package/dist/public/diagnostic-usage.d.ts +17 -0
- package/dist/public/diagnostic-usage.d.ts.map +1 -0
- package/dist/public/diagnostic-usage.js +152 -0
- package/dist/public/distribution-parity.d.ts +90 -0
- package/dist/public/distribution-parity.d.ts.map +1 -0
- package/dist/public/distribution-parity.js +367 -0
- package/dist/public/impact/full-kb-quality.d.ts +2 -0
- package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
- package/dist/public/impact/full-kb-quality.js +53 -1
- package/dist/public/impact-diagnostics.d.ts +2 -0
- package/dist/public/impact-diagnostics.d.ts.map +1 -1
- package/dist/public/impact-diagnostics.js +1 -0
- package/dist/public/operations/catalog.d.ts +235 -11
- package/dist/public/operations/catalog.d.ts.map +1 -1
- package/dist/public/operations/check-executor.d.ts.map +1 -1
- package/dist/public/operations/check-executor.js +4 -0
- package/dist/public/operations/discovery-executors.d.ts +6 -0
- package/dist/public/operations/discovery-executors.d.ts.map +1 -1
- package/dist/public/operations/discovery-executors.js +18 -2
- package/dist/public/operations/index.d.ts +5 -0
- package/dist/public/operations/index.d.ts.map +1 -1
- package/dist/public/operations/index.js +5 -0
- package/dist/public/operations/legacy-migration-plan.d.ts +113 -0
- package/dist/public/operations/legacy-migration-plan.d.ts.map +1 -0
- package/dist/public/operations/legacy-migration-plan.js +409 -0
- package/dist/public/operations/node-ports.d.ts.map +1 -1
- package/dist/public/operations/node-ports.js +90 -0
- package/dist/public/operations/repair-plan.d.ts +184 -0
- package/dist/public/operations/repair-plan.d.ts.map +1 -0
- package/dist/public/operations/repair-plan.js +397 -0
- package/dist/public/operations/runtime-types.d.ts +7 -0
- package/dist/public/operations/runtime-types.d.ts.map +1 -1
- package/dist/public/operations/specs/check.d.ts +3 -3
- package/dist/public/operations/specs/check.d.ts.map +1 -1
- package/dist/public/operations/specs/check.js +5 -2
- package/dist/public/operations/specs/discovery.d.ts +1 -1
- package/dist/public/operations/specs/discovery.js +1 -1
- package/dist/public/operations/specs/modeling.d.ts +35 -1
- package/dist/public/operations/specs/modeling.d.ts.map +1 -1
- package/dist/public/operations/specs/modeling.js +32 -1
- package/dist/public/operations/specs/mutation-schemas.d.ts +133 -3
- package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
- package/dist/public/operations/specs/mutation-schemas.js +67 -1
- package/dist/public/operations/specs/reporting.d.ts +42 -1
- package/dist/public/operations/specs/reporting.d.ts.map +1 -1
- package/dist/public/operations/specs/reporting.js +70 -6
- package/dist/public/operations/specs/semantic.d.ts +30 -2
- package/dist/public/operations/specs/semantic.d.ts.map +1 -1
- package/dist/public/operations/specs/semantic.js +35 -3
- package/dist/public/operations/specs/upsert.d.ts +133 -3
- package/dist/public/operations/specs/upsert.d.ts.map +1 -1
- package/dist/public/operations/workspace-snapshot.d.ts +10 -0
- package/dist/public/operations/workspace-snapshot.d.ts.map +1 -0
- package/dist/public/operations/workspace-snapshot.js +20 -0
- package/dist/public/schemas/entity.d.ts.map +1 -1
- package/dist/public/schemas/entity.js +332 -0
- package/dist/public/schemas/relationship.d.ts.map +1 -1
- package/dist/public/schemas/relationship.js +1 -0
- package/dist/public/skills/kibi-traceability/SKILL.md +7 -1
- package/dist/public/skills/kibi-usage/SKILL.md +44 -12
- package/dist/public/skills/kibi-usage/resources/fact-lanes.md +12 -2
- package/dist/public/skills/kibi-usage/resources/logic-ir.md +43 -0
- package/dist/public/skills/kibi-usage/resources/operation-access.md +19 -19
- package/dist/public/skills/kibi-usage/resources/ui-requirements.md +96 -0
- package/dist/public/skills/kibi-usage/resources/workflows.md +40 -8
- package/dist/public/symbol-granularity.d.ts +9 -0
- package/dist/public/symbol-granularity.d.ts.map +1 -1
- package/dist/public/symbol-granularity.js +16 -0
- package/dist/public/telemetry-acceptance.d.ts +89 -0
- package/dist/public/telemetry-acceptance.d.ts.map +1 -0
- package/dist/public/telemetry-acceptance.js +597 -0
- package/dist/public/telemetry-remediation.d.ts +33 -0
- package/dist/public/telemetry-remediation.d.ts.map +1 -0
- package/dist/public/telemetry-remediation.js +257 -0
- package/dist/public/verification-receipt.d.ts +75 -0
- package/dist/public/verification-receipt.d.ts.map +1 -0
- package/dist/public/verification-receipt.js +129 -0
- package/dist/schemas/entity.schema.json +401 -4
- package/dist/traceability/staged-diagnostics.d.ts +1 -1
- package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
- package/dist/traceability/staged-diagnostics.js +17 -10
- package/dist/traceability/staged-impact-contract.js +1 -1
- package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
- package/dist/traceability/staged-symbols-manifest.js +28 -10
- package/dist/traceability/temp-kb.d.ts.map +1 -1
- package/dist/traceability/temp-kb.js +49 -3
- package/dist/types/entities.d.ts +1 -0
- package/dist/types/entities.d.ts.map +1 -1
- package/dist/types/relationships.d.ts +1 -1
- package/dist/types/relationships.d.ts.map +1 -1
- package/dist/utils/rule-registry.d.ts +2 -0
- package/dist/utils/rule-registry.d.ts.map +1 -1
- package/dist/utils/rule-registry.js +18 -0
- package/dist/utils/schema-version.d.ts +1 -1
- package/dist/utils/schema-version.js +1 -1
- package/package.json +34 -2
- package/src/public/diagnostic-usage.ts +206 -0
- package/src/public/distribution-parity.ts +580 -0
- package/src/public/impact/full-kb-quality.ts +74 -1
- package/src/public/impact-diagnostics.ts +16 -0
- package/src/public/operations/check-executor.ts +4 -0
- package/src/public/operations/discovery-executors.ts +24 -2
- package/src/public/operations/index.ts +5 -0
- package/src/public/operations/legacy-migration-plan.ts +688 -0
- package/src/public/operations/node-ports.ts +109 -0
- package/src/public/operations/repair-plan.ts +562 -0
- package/src/public/operations/runtime-types.ts +8 -0
- package/src/public/operations/specs/check.ts +5 -2
- package/src/public/operations/specs/discovery.ts +1 -1
- package/src/public/operations/specs/modeling.ts +39 -1
- package/src/public/operations/specs/mutation-schemas.ts +77 -1
- package/src/public/operations/specs/reporting.ts +91 -6
- package/src/public/operations/specs/semantic.ts +42 -3
- package/src/public/operations/workspace-snapshot.ts +28 -0
- package/src/public/schemas/entity.ts +340 -1
- package/src/public/schemas/relationship.ts +1 -0
- package/src/public/skills/kibi-traceability/SKILL.md +7 -1
- package/src/public/skills/kibi-usage/SKILL.md +44 -12
- package/src/public/skills/kibi-usage/resources/fact-lanes.md +12 -2
- package/src/public/skills/kibi-usage/resources/logic-ir.md +43 -0
- package/src/public/skills/kibi-usage/resources/operation-access.md +19 -19
- package/src/public/skills/kibi-usage/resources/ui-requirements.md +96 -0
- package/src/public/skills/kibi-usage/resources/workflows.md +40 -8
- package/src/public/symbol-granularity.ts +25 -0
- package/src/public/telemetry-acceptance.ts +859 -0
- package/src/public/telemetry-remediation.ts +462 -0
- package/src/public/verification-receipt.ts +170 -0
- package/src/schemas/entity.schema.json +401 -4
- package/src/schemas/relationship.schema.json +1 -0
package/dist/prolog.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
You should have received a copy of the GNU Affero General Public License
|
|
16
16
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
17
17
|
*/
|
|
18
|
-
import { spawn
|
|
18
|
+
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";
|
|
@@ -26,6 +26,22 @@ const PROLOG_OUTPUT_MAX_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
|
26
26
|
const PROLOG_OUTPUT_OVERFLOW_ERROR = "Query exceeded bounded Prolog output capacity (ENOBUFS); narrow the operation or reduce stored entity size";
|
|
27
27
|
const INTERACTIVE_QUERY_FRAME_END = "__KIBI_QUERY_FRAME_END__";
|
|
28
28
|
const require = createRequire(import.meta.url);
|
|
29
|
+
function packageVersion(packagePath) {
|
|
30
|
+
try {
|
|
31
|
+
const packageJson = require(packagePath);
|
|
32
|
+
return typeof packageJson.version === "string"
|
|
33
|
+
? packageJson.version
|
|
34
|
+
: "unknown";
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return "unknown";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const KIBI_PACKAGE_VERSIONS = [
|
|
41
|
+
`kibi-cli@${packageVersion(path.join(importMetaDir, "..", "package.json"))}`,
|
|
42
|
+
`kibi-core@${packageVersion(path.join(importMetaDir, "..", "..", "core", "package.json"))}`,
|
|
43
|
+
`kibi-mcp@${packageVersion(path.join(importMetaDir, "..", "..", "mcp", "package.json"))}`,
|
|
44
|
+
].join(",");
|
|
29
45
|
export function resolveKbPlPath() {
|
|
30
46
|
// implements REQ-009
|
|
31
47
|
const overrideKbPath = getKbPlPathOverride();
|
|
@@ -74,6 +90,7 @@ export class PrologProcess {
|
|
|
74
90
|
cache = new Map();
|
|
75
91
|
interactiveQueryTail = Promise.resolve();
|
|
76
92
|
terminationPromise = null;
|
|
93
|
+
oneShotProcesses = new Set();
|
|
77
94
|
useOneShotMode = typeof globalThis.Bun !== "undefined";
|
|
78
95
|
attachedKbPath = null;
|
|
79
96
|
onProcessExit = null;
|
|
@@ -90,7 +107,15 @@ export class PrologProcess {
|
|
|
90
107
|
"-g",
|
|
91
108
|
`use_module('${kbPath}'), use_module(library(semweb/rdf_db)), set_prolog_flag(answer_write_options, [max_depth(0), quoted(true)])`,
|
|
92
109
|
"--quiet",
|
|
93
|
-
]
|
|
110
|
+
], {
|
|
111
|
+
detached: process.platform !== "win32",
|
|
112
|
+
env: {
|
|
113
|
+
...process.env,
|
|
114
|
+
KIBI_RUNTIME_NAME: process.versions.bun !== undefined ? "bun" : "node",
|
|
115
|
+
KIBI_RUNTIME_VERSION: process.versions.bun ?? process.versions.node ?? "unknown",
|
|
116
|
+
KIBI_PACKAGE_VERSIONS: process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS,
|
|
117
|
+
},
|
|
118
|
+
});
|
|
94
119
|
this.clearQueryBuffers();
|
|
95
120
|
if (!this.process.stdout || !this.process.stderr || !this.process.stdin) {
|
|
96
121
|
throw new Error("Failed to spawn Prolog process");
|
|
@@ -150,10 +175,15 @@ export class PrologProcess {
|
|
|
150
175
|
}
|
|
151
176
|
if (this.useOneShotMode) {
|
|
152
177
|
const oneShotResult = await this.queryOneShot(goal);
|
|
153
|
-
|
|
178
|
+
// Compound goals are read-after-write sensitive, so keep them out of the
|
|
179
|
+
// cache to mirror the interactive path and let later reads observe
|
|
180
|
+
// same-session writes (e.g. kb_status freshness after a file lands).
|
|
181
|
+
const isBatchGoal = isSingleGoal && /^\s*\(/.test(this.normalizeGoal(goal));
|
|
182
|
+
const shouldCache = cacheable && !isBatchGoal;
|
|
183
|
+
if (!shouldCache && oneShotResult.success) {
|
|
154
184
|
this.invalidateCache();
|
|
155
185
|
}
|
|
156
|
-
if (
|
|
186
|
+
if (shouldCache && oneShotResult.success) {
|
|
157
187
|
this.cache.set(goalKey, oneShotResult);
|
|
158
188
|
}
|
|
159
189
|
return oneShotResult;
|
|
@@ -169,24 +199,26 @@ export class PrologProcess {
|
|
|
169
199
|
this.clearQueryBuffers();
|
|
170
200
|
const debug = isPrologDebugEnabled();
|
|
171
201
|
const normalizedGoal = this.normalizeGoal(goal);
|
|
202
|
+
const goalLabel = this.goalLabel(normalizedGoal);
|
|
172
203
|
const wrappedGoal = /^once\s*\(/.test(normalizedGoal)
|
|
173
204
|
? `catch(${normalizedGoal}, _E, (print_message(error, _E), fail))`
|
|
174
205
|
: `catch(once((${normalizedGoal})), _E, (print_message(error, _E), fail))`;
|
|
175
206
|
const start = Date.now();
|
|
176
207
|
if (debug) {
|
|
177
|
-
console.error(`[prolog debug] start query: ${
|
|
208
|
+
console.error(`[prolog debug] start query: ${goalLabel}`);
|
|
178
209
|
}
|
|
179
210
|
this.process?.stdin?.write(`${wrappedGoal}.\nwriteln('${INTERACTIVE_QUERY_FRAME_END}').\n`);
|
|
180
211
|
return new Promise((resolve, reject) => {
|
|
181
212
|
let settled = false;
|
|
182
213
|
const timeoutId = setTimeout(() => {
|
|
183
|
-
const
|
|
214
|
+
const stage = this.lastDiagnosticStage(this.errorBuffer) ?? "unknown";
|
|
215
|
+
const msg = `Query timeout after ${this.timeout / 1000}s (stage=${stage}, pid=${this.process?.pid ?? 0}, killed=${this.process?.killed ? "yes" : "no"}, exitCode=${this.process?.exitCode ?? "null"}, goal=${goalLabel})`;
|
|
184
216
|
if (debug) {
|
|
185
|
-
const tailOut = this.outputBuffer.slice(-2048);
|
|
186
|
-
const tailErr = this.errorBuffer.slice(-2048);
|
|
187
217
|
console.error(`[prolog debug] timeout: ${msg}`);
|
|
188
|
-
|
|
189
|
-
|
|
218
|
+
const runtime = this.errorBuffer
|
|
219
|
+
.split("\n")
|
|
220
|
+
.find((line) => line.includes("__KIBI_RUNTIME__"));
|
|
221
|
+
console.error(`[prolog debug] runtime=${runtime ?? "unknown"} packages=${process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS}`);
|
|
190
222
|
}
|
|
191
223
|
settled = true;
|
|
192
224
|
void this.terminate().finally(() => {
|
|
@@ -224,12 +256,12 @@ export class PrologProcess {
|
|
|
224
256
|
clearTimeout(timeoutId);
|
|
225
257
|
settled = true;
|
|
226
258
|
if (debug) {
|
|
227
|
-
console.error(`[prolog debug] query error: ${
|
|
259
|
+
console.error(`[prolog debug] query error: ${goalLabel} error=${this.errorBuffer.split("\n")[0]}`);
|
|
228
260
|
}
|
|
229
261
|
resolve({
|
|
230
262
|
success: false,
|
|
231
263
|
bindings: {},
|
|
232
|
-
error: this.translateError(this.errorBuffer),
|
|
264
|
+
error: this.addDiagnosticStage(this.translateError(this.errorBuffer), normalizedGoal, this.errorBuffer),
|
|
233
265
|
});
|
|
234
266
|
return;
|
|
235
267
|
}
|
|
@@ -263,7 +295,7 @@ export class PrologProcess {
|
|
|
263
295
|
this.cache.set(goalKey, result);
|
|
264
296
|
}
|
|
265
297
|
if (debug) {
|
|
266
|
-
console.error(`[prolog debug] query success: ${
|
|
298
|
+
console.error(`[prolog debug] query success: ${goalLabel} elapsed=${(Date.now() - start) / 1000}s`);
|
|
267
299
|
}
|
|
268
300
|
resolve(result);
|
|
269
301
|
return;
|
|
@@ -297,6 +329,7 @@ export class PrologProcess {
|
|
|
297
329
|
return !(trimmed.startsWith("kb_attach(") ||
|
|
298
330
|
trimmed.startsWith("kb_detach") ||
|
|
299
331
|
trimmed.startsWith("kb_save") ||
|
|
332
|
+
trimmed.startsWith("kb_commit_upsert(") ||
|
|
300
333
|
trimmed.startsWith("kb_assert_") ||
|
|
301
334
|
trimmed.startsWith("kb_delete_") ||
|
|
302
335
|
trimmed.startsWith("kb_retract_"));
|
|
@@ -339,7 +372,7 @@ export class PrologProcess {
|
|
|
339
372
|
error: "KB already attached",
|
|
340
373
|
};
|
|
341
374
|
}
|
|
342
|
-
const attachResult = this.execOneShot(trimmedGoal, null);
|
|
375
|
+
const attachResult = await this.execOneShot(trimmedGoal, null);
|
|
343
376
|
if (attachResult.success) {
|
|
344
377
|
this.attachedKbPath = attachPath;
|
|
345
378
|
}
|
|
@@ -347,7 +380,7 @@ export class PrologProcess {
|
|
|
347
380
|
}
|
|
348
381
|
return this.execOneShot(trimmedGoal, this.attachedKbPath);
|
|
349
382
|
}
|
|
350
|
-
execOneShot(goal, kbPath) {
|
|
383
|
+
async execOneShot(goal, kbPath) {
|
|
351
384
|
const originalGoalList = Array.isArray(goal)
|
|
352
385
|
? goal.map((item) => this.normalizeGoal(item))
|
|
353
386
|
: [this.normalizeGoal(goal)];
|
|
@@ -381,37 +414,121 @@ export class PrologProcess {
|
|
|
381
414
|
kbPath ? "kb_detach" : "true",
|
|
382
415
|
"(QuerySucceeded == true -> (forall(member(Name=Value, Vars), (write(Name), write('='), write_term(Value, [quoted(true), max_depth(0)]), writeln('.'))), writeln('__KIBI_TRUE__.')) ; writeln('__KIBI_FALSE__.'))",
|
|
383
416
|
].join(", ");
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
417
|
+
const runtimeName = process.versions.bun !== undefined ? "bun" : "node";
|
|
418
|
+
const runtimeVersion = process.versions.bun ?? process.versions.node ?? "unknown";
|
|
419
|
+
const packageVersions = process.env.KIBI_PACKAGE_VERSIONS ?? KIBI_PACKAGE_VERSIONS;
|
|
420
|
+
let child;
|
|
421
|
+
try {
|
|
422
|
+
child = spawn(this.swiplPath, ["-q", "-g", prologGoal, "-t", "halt"], {
|
|
423
|
+
detached: process.platform !== "win32",
|
|
424
|
+
env: {
|
|
425
|
+
...process.env,
|
|
426
|
+
KIBI_GOAL: combinedGoal,
|
|
427
|
+
KIBI_RUNTIME_NAME: runtimeName,
|
|
428
|
+
KIBI_RUNTIME_VERSION: runtimeVersion,
|
|
429
|
+
KIBI_PACKAGE_VERSIONS: packageVersions,
|
|
430
|
+
...(kbPath ? { KIBI_KB_PATH: kbPath } : {}),
|
|
431
|
+
},
|
|
432
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
return {
|
|
437
|
+
success: false,
|
|
438
|
+
bindings: {},
|
|
439
|
+
error: error instanceof Error ? error.message : String(error),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
this.oneShotProcesses.add(child);
|
|
443
|
+
let stdout = "";
|
|
444
|
+
let stderr = "";
|
|
445
|
+
let outputBytes = 0;
|
|
446
|
+
let errorBytes = 0;
|
|
447
|
+
let outputOverflowed = false;
|
|
448
|
+
let timedOut = false;
|
|
449
|
+
let settled = false;
|
|
450
|
+
let closed = false;
|
|
451
|
+
let termination = null;
|
|
452
|
+
const appendChunk = (chunk, target) => {
|
|
453
|
+
if (outputOverflowed)
|
|
454
|
+
return;
|
|
455
|
+
const bytes = target === "stdout" ? outputBytes : errorBytes;
|
|
456
|
+
const remaining = PROLOG_OUTPUT_MAX_BUFFER_BYTES - bytes;
|
|
457
|
+
if (chunk.byteLength > remaining) {
|
|
458
|
+
const clipped = chunk.subarray(0, Math.max(0, remaining)).toString();
|
|
459
|
+
if (target === "stdout")
|
|
460
|
+
stdout += clipped;
|
|
461
|
+
else
|
|
462
|
+
stderr += clipped;
|
|
463
|
+
outputOverflowed = true;
|
|
464
|
+
termination ??= this.terminateProcessTree(child);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (target === "stdout") {
|
|
468
|
+
stdout += chunk.toString();
|
|
469
|
+
outputBytes += chunk.byteLength;
|
|
470
|
+
}
|
|
471
|
+
else {
|
|
472
|
+
stderr += chunk.toString();
|
|
473
|
+
errorBytes += chunk.byteLength;
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
child.stdout?.on("data", (chunk) => appendChunk(chunk, "stdout"));
|
|
477
|
+
child.stderr?.on("data", (chunk) => appendChunk(chunk, "stderr"));
|
|
478
|
+
const completion = new Promise((resolve) => {
|
|
479
|
+
child.once("error", (error) => {
|
|
480
|
+
closed = true;
|
|
481
|
+
resolve({ code: null, signal: null, error });
|
|
482
|
+
});
|
|
483
|
+
child.once("close", (code, signal) => {
|
|
484
|
+
closed = true;
|
|
485
|
+
resolve({ code, signal });
|
|
486
|
+
});
|
|
394
487
|
});
|
|
395
|
-
|
|
396
|
-
(
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
488
|
+
const timeoutId = setTimeout(() => {
|
|
489
|
+
if (settled || closed)
|
|
490
|
+
return;
|
|
491
|
+
timedOut = true;
|
|
492
|
+
termination ??= this.terminateProcessTree(child);
|
|
493
|
+
}, this.timeout);
|
|
494
|
+
const result = await completion;
|
|
495
|
+
settled = true;
|
|
496
|
+
clearTimeout(timeoutId);
|
|
497
|
+
if (termination !== null)
|
|
498
|
+
await termination;
|
|
499
|
+
this.oneShotProcesses.delete(child);
|
|
500
|
+
const stage = this.lastDiagnosticStage(stderr) ?? "unknown";
|
|
501
|
+
if (isPrologDebugEnabled()) {
|
|
502
|
+
const runtime = stderr
|
|
503
|
+
.split("\n")
|
|
504
|
+
.find((line) => line.includes("__KIBI_RUNTIME__"));
|
|
505
|
+
if (runtime !== undefined) {
|
|
506
|
+
console.error(`[prolog debug] ${runtime}`);
|
|
507
|
+
}
|
|
508
|
+
console.error(`[prolog debug] child pid=${child.pid ?? 0} runtime=${runtimeName}@${runtimeVersion} packages=${packageVersions} stage=${stage}`);
|
|
509
|
+
}
|
|
510
|
+
if (timedOut) {
|
|
511
|
+
throw new Error(`Query timeout after ${this.timeout / 1000}s (stage=${stage}, pid=${child.pid ?? 0}, goal=${this.goalLabel(combinedGoal ?? "true")})`);
|
|
512
|
+
}
|
|
513
|
+
if (outputOverflowed) {
|
|
514
|
+
return {
|
|
515
|
+
success: false,
|
|
516
|
+
bindings: {},
|
|
517
|
+
error: this.addDiagnosticStage(PROLOG_OUTPUT_OVERFLOW_ERROR, combinedGoal ?? "true", stderr),
|
|
518
|
+
};
|
|
400
519
|
}
|
|
401
|
-
if (result.error
|
|
520
|
+
if (result.error !== undefined) {
|
|
402
521
|
return {
|
|
403
522
|
success: false,
|
|
404
523
|
bindings: {},
|
|
405
|
-
error:
|
|
524
|
+
error: this.addDiagnosticStage(result.error.message, combinedGoal ?? "true", stderr),
|
|
406
525
|
};
|
|
407
526
|
}
|
|
408
|
-
const stdout = result.stdout ?? "";
|
|
409
|
-
const stderr = result.stderr ?? "";
|
|
410
527
|
if (stderr.includes("ERROR")) {
|
|
411
528
|
return {
|
|
412
529
|
success: false,
|
|
413
530
|
bindings: {},
|
|
414
|
-
error: this.translateError(stderr),
|
|
531
|
+
error: this.addDiagnosticStage(this.translateError(stderr), combinedGoal ?? "true", stderr),
|
|
415
532
|
};
|
|
416
533
|
}
|
|
417
534
|
if (stdout.includes("__KIBI_TRUE__")) {
|
|
@@ -434,7 +551,7 @@ export class PrologProcess {
|
|
|
434
551
|
return {
|
|
435
552
|
success: false,
|
|
436
553
|
bindings: {},
|
|
437
|
-
error: `Query failed - stdout: ${stdout.substring(0, 200)}, stderr: ${stderr.substring(0, 200)}`,
|
|
554
|
+
error: this.addDiagnosticStage(`Query failed - stdout: ${stdout.substring(0, 200)}, stderr: ${stderr.substring(0, 200)}`, combinedGoal ?? "true", stderr),
|
|
438
555
|
};
|
|
439
556
|
}
|
|
440
557
|
normalizeGoal(goal) {
|
|
@@ -473,6 +590,66 @@ export class PrologProcess {
|
|
|
473
590
|
this.errorBuffer += chunk.toString();
|
|
474
591
|
this.errorBufferBytes += chunk.byteLength;
|
|
475
592
|
}
|
|
593
|
+
goalLabel(goal) {
|
|
594
|
+
const match = goal
|
|
595
|
+
.trim()
|
|
596
|
+
.match(/^([a-z][A-Za-z0-9_]*(?::[a-z][A-Za-z0-9_]*)?)/);
|
|
597
|
+
return match?.[1] ?? "anonymous";
|
|
598
|
+
}
|
|
599
|
+
addDiagnosticStage(message, goal, diagnostics) {
|
|
600
|
+
if (this.goalLabel(goal) !== "kb_commit_upsert")
|
|
601
|
+
return message;
|
|
602
|
+
const stage = this.lastDiagnosticStage(diagnostics);
|
|
603
|
+
return stage === null ? message : `${message} (stage=${stage})`;
|
|
604
|
+
}
|
|
605
|
+
lastDiagnosticStage(text) {
|
|
606
|
+
const matches = [...text.matchAll(/__KIBI_STAGE__:([^\s\r\n]+)/g)];
|
|
607
|
+
return matches.at(-1)?.[1] ?? null;
|
|
608
|
+
}
|
|
609
|
+
signalProcessTree(child, signal) {
|
|
610
|
+
const pid = child.pid;
|
|
611
|
+
if (pid === undefined || pid <= 0)
|
|
612
|
+
return;
|
|
613
|
+
if (process.platform !== "win32") {
|
|
614
|
+
try {
|
|
615
|
+
process.kill(-pid, signal);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
// Fall back to the direct child when a process group is unavailable.
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
try {
|
|
623
|
+
child.kill(signal);
|
|
624
|
+
}
|
|
625
|
+
catch {
|
|
626
|
+
// The child may have exited between the group and direct kill attempts.
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
async terminateProcessTree(child) {
|
|
630
|
+
let closed = false;
|
|
631
|
+
const exited = new Promise((resolve) => {
|
|
632
|
+
const markExited = () => {
|
|
633
|
+
closed = true;
|
|
634
|
+
resolve();
|
|
635
|
+
};
|
|
636
|
+
child.once("close", markExited);
|
|
637
|
+
child.once("error", markExited);
|
|
638
|
+
});
|
|
639
|
+
if (child.exitCode === null)
|
|
640
|
+
this.signalProcessTree(child, "SIGTERM");
|
|
641
|
+
await Promise.race([
|
|
642
|
+
exited,
|
|
643
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
644
|
+
]);
|
|
645
|
+
if (!closed) {
|
|
646
|
+
this.signalProcessTree(child, "SIGKILL");
|
|
647
|
+
await Promise.race([
|
|
648
|
+
exited,
|
|
649
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
650
|
+
]);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
476
653
|
// implements REQ-009
|
|
477
654
|
isProcessUsable() {
|
|
478
655
|
return Boolean(this.process?.stdin &&
|
|
@@ -495,26 +672,39 @@ export class PrologProcess {
|
|
|
495
672
|
}
|
|
496
673
|
// implements REQ-core-prolog-process-management
|
|
497
674
|
translateError(errorText) {
|
|
675
|
+
// Diagnostic markers intentionally contain words such as `lock` and the
|
|
676
|
+
// audit path. Remove them before classifying the actual Prolog error so a
|
|
677
|
+
// contradiction at the check stage is not mistaken for an audit lock.
|
|
678
|
+
const cleanError = errorText.replace(/^__KIBI_(?:STAGE|RUNTIME)__:[^\r\n]*\r?\n?/gm, "");
|
|
679
|
+
if (cleanError.includes("stale_snapshot")) {
|
|
680
|
+
return "KB snapshot is stale; reattach or refresh the runtime before retrying (stale_snapshot)";
|
|
681
|
+
}
|
|
682
|
+
if ((cleanError.includes("audit_log") ||
|
|
683
|
+
cleanError.includes("audit.log") ||
|
|
684
|
+
cleanError.includes("Resource temporarily unavailable")) &&
|
|
685
|
+
(cleanError.includes("lock") || cleanError.includes("permission"))) {
|
|
686
|
+
return "Audit journal is locked by another Kibi runtime; restart the stale MCP/CLI session before retrying";
|
|
687
|
+
}
|
|
498
688
|
// SWI-Prolog print_message/2 formats errors as human-readable messages,
|
|
499
689
|
// not raw Prolog terms. Match the actual output format.
|
|
500
|
-
if (
|
|
501
|
-
/entity [`'"].+?[`'"]/.test(
|
|
690
|
+
if (cleanError.includes("does not exist") &&
|
|
691
|
+
/entity [`'"].+?[`'"]/.test(cleanError)) {
|
|
502
692
|
// SWI-Prolog doubles single quotes in formatted messages: ''REQ-TEST''
|
|
503
|
-
const entityIdMatch =
|
|
693
|
+
const entityIdMatch = cleanError.match(/entity [`'"]+(.+?)[`'"]+ does not exist/);
|
|
504
694
|
const matchedEntityId = entityIdMatch?.[1];
|
|
505
695
|
const entityId = matchedEntityId !== undefined
|
|
506
696
|
? matchedEntityId.replace(/^`?'+|'+`?$/g, "")
|
|
507
697
|
: "unknown";
|
|
508
|
-
if (
|
|
698
|
+
if (cleanError.includes("Target entity does not exist")) {
|
|
509
699
|
return `Target entity does not exist: ${entityId}`;
|
|
510
700
|
}
|
|
511
|
-
if (
|
|
701
|
+
if (cleanError.includes("Source entity does not exist")) {
|
|
512
702
|
return `Source entity does not exist: ${entityId}`;
|
|
513
703
|
}
|
|
514
704
|
return `Entity does not exist: ${entityId}`;
|
|
515
705
|
}
|
|
516
|
-
if (
|
|
517
|
-
const relMatch =
|
|
706
|
+
if (cleanError.includes("Type error: `relationship' expected")) {
|
|
707
|
+
const relMatch = cleanError.match(/\(Invalid relationship: ([^)]+)\)/);
|
|
518
708
|
const invalidRelationship = relMatch?.[1];
|
|
519
709
|
if (invalidRelationship !== undefined) {
|
|
520
710
|
return `Invalid relationship: ${invalidRelationship.trim()}`;
|
|
@@ -522,21 +712,21 @@ export class PrologProcess {
|
|
|
522
712
|
return "Invalid relationship type or direction";
|
|
523
713
|
}
|
|
524
714
|
// Fallback: check for raw Prolog error terms (used by other tools)
|
|
525
|
-
if (
|
|
526
|
-
|
|
715
|
+
if (cleanError.includes("existence_error") ||
|
|
716
|
+
cleanError.includes("Unknown procedure")) {
|
|
527
717
|
return "Predicate or file not found";
|
|
528
718
|
}
|
|
529
|
-
if (
|
|
719
|
+
if (cleanError.includes("permission_error")) {
|
|
530
720
|
return "Access denied or KB locked";
|
|
531
721
|
}
|
|
532
|
-
if (
|
|
533
|
-
|
|
722
|
+
if (cleanError.includes("syntax_error") ||
|
|
723
|
+
cleanError.includes("Operator expected")) {
|
|
534
724
|
return "Invalid query syntax";
|
|
535
725
|
}
|
|
536
|
-
if (
|
|
726
|
+
if (cleanError.includes("timeout_error")) {
|
|
537
727
|
return `Operation exceeded ${this.timeout / 1000}s timeout`;
|
|
538
728
|
}
|
|
539
|
-
const simpleError = (
|
|
729
|
+
const simpleError = (cleanError
|
|
540
730
|
.replace(/ERROR:\s*/g, "")
|
|
541
731
|
.replace(/^\*\*.*\*\*$/gm, "")
|
|
542
732
|
.replace(/^\s+/gm, "")
|
|
@@ -559,22 +749,16 @@ export class PrologProcess {
|
|
|
559
749
|
this.onProcessExit = null;
|
|
560
750
|
}
|
|
561
751
|
const current = this.process;
|
|
752
|
+
const oneShots = [...this.oneShotProcesses];
|
|
562
753
|
this.process = null;
|
|
563
754
|
this.clearQueryBuffers();
|
|
564
|
-
if (current) {
|
|
755
|
+
if (current || oneShots.length > 0) {
|
|
565
756
|
this.terminationPromise = (async () => {
|
|
566
|
-
current
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
resolve(undefined);
|
|
572
|
-
}, 1000);
|
|
573
|
-
current.on("exit", () => {
|
|
574
|
-
clearTimeout(timeout);
|
|
575
|
-
resolve(undefined);
|
|
576
|
-
});
|
|
577
|
-
});
|
|
757
|
+
if (current) {
|
|
758
|
+
current.stdin?.end();
|
|
759
|
+
await this.terminateProcessTree(current);
|
|
760
|
+
}
|
|
761
|
+
await Promise.all(oneShots.map((child) => this.terminateProcessTree(child)));
|
|
578
762
|
})();
|
|
579
763
|
try {
|
|
580
764
|
await this.terminationPromise;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface DiagnosticUsageInput {
|
|
2
|
+
readonly workspaceRoot: string;
|
|
3
|
+
readonly tool: string;
|
|
4
|
+
readonly businessArgs: Readonly<Record<string, unknown>>;
|
|
5
|
+
readonly telemetry: Readonly<Record<string, unknown>> | null;
|
|
6
|
+
readonly startedAt: Date;
|
|
7
|
+
readonly finishedAt?: Date;
|
|
8
|
+
readonly status: "success" | "error";
|
|
9
|
+
readonly result?: unknown;
|
|
10
|
+
readonly error?: string;
|
|
11
|
+
readonly requestId?: string;
|
|
12
|
+
readonly logPath?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function diagnosticMutationFingerprint(args: Readonly<Record<string, unknown>>): string;
|
|
15
|
+
export declare function deriveDiagnosticUsageFields(tool: string, args: Readonly<Record<string, unknown>>, telemetry: Readonly<Record<string, unknown>> | null, result: unknown): Record<string, unknown>;
|
|
16
|
+
export declare function appendCliDiagnosticUsage(input: DiagnosticUsageInput): void;
|
|
17
|
+
//# sourceMappingURL=diagnostic-usage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostic-usage.d.ts","sourceRoot":"","sources":["../../src/public/diagnostic-usage.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACzD,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7D,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,IAAI,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AA4BD,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GACtC,MAAM,CAIR;AAiED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACvC,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI,EACnD,MAAM,EAAE,OAAO,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmDzB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAgC1E"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
function resultRecord(result) {
|
|
8
|
+
if (!isRecord(result))
|
|
9
|
+
return undefined;
|
|
10
|
+
return isRecord(result.structuredContent) ? result.structuredContent : result;
|
|
11
|
+
}
|
|
12
|
+
function stringArray(value) {
|
|
13
|
+
return Array.isArray(value)
|
|
14
|
+
? value.filter((item) => typeof item === "string")
|
|
15
|
+
: [];
|
|
16
|
+
}
|
|
17
|
+
function canonicalValue(value) {
|
|
18
|
+
if (Array.isArray(value))
|
|
19
|
+
return value.map(canonicalValue);
|
|
20
|
+
if (!isRecord(value))
|
|
21
|
+
return value;
|
|
22
|
+
return Object.fromEntries(Object.entries(value)
|
|
23
|
+
.filter(([key]) => key !== "_diagnostic_telemetry")
|
|
24
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
25
|
+
.map(([key, child]) => [key, canonicalValue(child)]));
|
|
26
|
+
}
|
|
27
|
+
export function diagnosticMutationFingerprint(args) {
|
|
28
|
+
return createHash("sha256")
|
|
29
|
+
.update(JSON.stringify(canonicalValue(args)))
|
|
30
|
+
.digest("hex");
|
|
31
|
+
}
|
|
32
|
+
function appendMutationFields(fields, args) {
|
|
33
|
+
fields.mutation_fingerprint = diagnosticMutationFingerprint(args);
|
|
34
|
+
const type = typeof args.type === "string" ? args.type : "unknown";
|
|
35
|
+
const id = typeof args.id === "string" ? args.id : "unknown";
|
|
36
|
+
fields.mutation_target = `${type}:${id}`;
|
|
37
|
+
}
|
|
38
|
+
function appendSemanticFields(fields, receipt) {
|
|
39
|
+
if (!isRecord(receipt))
|
|
40
|
+
return;
|
|
41
|
+
const inventory = isRecord(receipt.inventory_contract)
|
|
42
|
+
? receipt.inventory_contract
|
|
43
|
+
: undefined;
|
|
44
|
+
if (typeof inventory?.source_hash === "string") {
|
|
45
|
+
fields.semantic_source_hash = inventory.source_hash;
|
|
46
|
+
}
|
|
47
|
+
if (typeof receipt.logic_readiness === "string") {
|
|
48
|
+
fields.semantic_logic_readiness = receipt.logic_readiness;
|
|
49
|
+
}
|
|
50
|
+
if (typeof receipt.candidate_lane === "string") {
|
|
51
|
+
fields.semantic_candidate_lane = receipt.candidate_lane;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const RECEIPT_GAP_CODES = new Set([
|
|
55
|
+
"missing_verification_receipt",
|
|
56
|
+
"stale_verification_receipt",
|
|
57
|
+
"failed_verification_receipt",
|
|
58
|
+
"invalid_verification_receipt",
|
|
59
|
+
"verification_snapshot_unavailable",
|
|
60
|
+
]);
|
|
61
|
+
function appendCoverageFields(fields, args, result) {
|
|
62
|
+
const rows = Array.isArray(result.rows) ? result.rows.filter(isRecord) : [];
|
|
63
|
+
const summary = isRecord(result.summary) ? result.summary : {};
|
|
64
|
+
const gapCodes = rows.flatMap((row) => stringArray(row.proofGaps));
|
|
65
|
+
const repairPlan = isRecord(result.repairPlan)
|
|
66
|
+
? result.repairPlan
|
|
67
|
+
: undefined;
|
|
68
|
+
const scope = isRecord(repairPlan?.scope) ? repairPlan.scope : undefined;
|
|
69
|
+
fields.coverage_by = typeof args.by === "string" ? args.by : "req";
|
|
70
|
+
fields.coverage_requirement_count = Number(summary.total ?? rows.length);
|
|
71
|
+
fields.coverage_proven_count = Number(summary.proofProven ?? 0);
|
|
72
|
+
fields.coverage_proof_missing_count = Number(summary.proofMissing ?? 0);
|
|
73
|
+
fields.coverage_proof_gap_count = gapCodes.length;
|
|
74
|
+
fields.coverage_gap_codes = [...new Set(gapCodes)].sort();
|
|
75
|
+
fields.coverage_receipt_gap_count = gapCodes.filter((code) => RECEIPT_GAP_CODES.has(code)).length;
|
|
76
|
+
if (typeof scope?.complete === "boolean") {
|
|
77
|
+
fields.coverage_scope_complete = scope.complete;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function deriveDiagnosticUsageFields(tool, args, telemetry, result) {
|
|
81
|
+
const fields = {
|
|
82
|
+
telemetry_status: telemetry ? "provided" : "missing",
|
|
83
|
+
};
|
|
84
|
+
if (telemetry) {
|
|
85
|
+
fields.telemetry_is_autonomous = telemetry.is_autonomous ?? null;
|
|
86
|
+
fields.telemetry_confidence_score = telemetry.confidence_score ?? null;
|
|
87
|
+
fields.telemetry_attempt_number = telemetry.attempt_number ?? null;
|
|
88
|
+
if (typeof telemetry.session_id === "string") {
|
|
89
|
+
fields.session_id = telemetry.session_id;
|
|
90
|
+
}
|
|
91
|
+
if (typeof telemetry.actor_id === "string") {
|
|
92
|
+
fields.actor_id = telemetry.actor_id;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const structured = resultRecord(result);
|
|
96
|
+
if (tool === "kb_query" || tool === "kb_search") {
|
|
97
|
+
const resultCount = Number(structured?.count ?? 0);
|
|
98
|
+
fields.result_count = resultCount;
|
|
99
|
+
fields.zero_results = resultCount === 0;
|
|
100
|
+
fields.result_summary =
|
|
101
|
+
resultCount === 0 ? "0 results" : `${resultCount} results`;
|
|
102
|
+
}
|
|
103
|
+
if (tool === "kb_check") {
|
|
104
|
+
const count = Number(structured?.count ?? 0);
|
|
105
|
+
fields.violation_count = count;
|
|
106
|
+
fields.requested_rules = Array.isArray(args.rules) ? args.rules : [];
|
|
107
|
+
fields.result_summary =
|
|
108
|
+
count === 0 ? "0 violations" : `${count} violations`;
|
|
109
|
+
}
|
|
110
|
+
if (tool === "kb_coverage" && structured) {
|
|
111
|
+
appendCoverageFields(fields, args, structured);
|
|
112
|
+
fields.result_summary = `${String(fields.coverage_proven_count)} proven; ${String(fields.coverage_proof_gap_count)} proof gaps`;
|
|
113
|
+
}
|
|
114
|
+
if (tool === "kb_semantic_advisor" && structured) {
|
|
115
|
+
appendSemanticFields(fields, structured.receipt);
|
|
116
|
+
}
|
|
117
|
+
if (tool === "kb_validate_upsert") {
|
|
118
|
+
appendMutationFields(fields, args);
|
|
119
|
+
fields.validation_valid = structured?.valid === true;
|
|
120
|
+
appendSemanticFields(fields, structured?.semanticAdvisor);
|
|
121
|
+
}
|
|
122
|
+
if (tool === "kb_upsert") {
|
|
123
|
+
appendMutationFields(fields, args);
|
|
124
|
+
appendSemanticFields(fields, structured?.semanticAdvisor);
|
|
125
|
+
}
|
|
126
|
+
if (!fields.result_summary)
|
|
127
|
+
fields.result_summary = `${tool} completed`;
|
|
128
|
+
return fields;
|
|
129
|
+
}
|
|
130
|
+
export function appendCliDiagnosticUsage(input) {
|
|
131
|
+
const finishedAt = input.finishedAt ?? new Date();
|
|
132
|
+
const requestId = input.requestId ?? `cli-${randomUUID()}`;
|
|
133
|
+
const logPath = input.logPath ??
|
|
134
|
+
process.env.KIBI_CLI_DIAGNOSTIC_USAGE_LOG_PATH ??
|
|
135
|
+
path.join(input.workspaceRoot, ".kb", "usage.log");
|
|
136
|
+
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
137
|
+
appendFileSync(logPath, `${JSON.stringify({
|
|
138
|
+
timestamp: finishedAt.toISOString(),
|
|
139
|
+
request_id: requestId,
|
|
140
|
+
tool: input.tool,
|
|
141
|
+
interface: "cli_json",
|
|
142
|
+
telemetry: input.telemetry,
|
|
143
|
+
business_args: input.businessArgs,
|
|
144
|
+
status: input.status,
|
|
145
|
+
started_at: input.startedAt.toISOString(),
|
|
146
|
+
finished_at: finishedAt.toISOString(),
|
|
147
|
+
duration_ms: finishedAt.getTime() - input.startedAt.getTime(),
|
|
148
|
+
active_branch: process.env.KIBI_BRANCH ?? null,
|
|
149
|
+
...(input.error ? { error_message: input.error } : {}),
|
|
150
|
+
...deriveDiagnosticUsageFields(input.tool, input.businessArgs, input.telemetry, input.result),
|
|
151
|
+
})}\n`, "utf8");
|
|
152
|
+
}
|