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.
Files changed (229) hide show
  1. package/dist/cli-json-command.d.ts.map +1 -1
  2. package/dist/cli-json-command.js +87 -11
  3. package/dist/cli-register-maintenance.d.ts.map +1 -1
  4. package/dist/cli-register-maintenance.js +8 -0
  5. package/dist/cli-register-reporting.d.ts.map +1 -1
  6. package/dist/cli-register-reporting.js +5 -0
  7. package/dist/cli.d.ts.map +1 -1
  8. package/dist/cli.js +2 -1
  9. package/dist/commands/aggregated-checks.d.ts.map +1 -1
  10. package/dist/commands/aggregated-checks.js +1 -0
  11. package/dist/commands/check.d.ts.map +1 -1
  12. package/dist/commands/check.js +1 -1
  13. package/dist/commands/coverage.d.ts +5 -0
  14. package/dist/commands/coverage.d.ts.map +1 -1
  15. package/dist/commands/coverage.js +5 -0
  16. package/dist/commands/discovery-table.d.ts.map +1 -1
  17. package/dist/commands/discovery-table.js +96 -5
  18. package/dist/commands/sync/cache.d.ts +2 -0
  19. package/dist/commands/sync/cache.d.ts.map +1 -1
  20. package/dist/commands/sync/cache.js +8 -0
  21. package/dist/commands/sync/manifest.d.ts +12 -1
  22. package/dist/commands/sync/manifest.d.ts.map +1 -1
  23. package/dist/commands/sync/manifest.js +22 -5
  24. package/dist/commands/sync/persistence.d.ts.map +1 -1
  25. package/dist/commands/sync/persistence.js +76 -2
  26. package/dist/commands/sync.d.ts.map +1 -1
  27. package/dist/commands/sync.js +72 -15
  28. package/dist/commands/usage-metrics.d.ts +66 -2
  29. package/dist/commands/usage-metrics.d.ts.map +1 -1
  30. package/dist/commands/usage-metrics.js +34 -24
  31. package/dist/commands/usage-remediation.d.ts +9 -0
  32. package/dist/commands/usage-remediation.d.ts.map +1 -0
  33. package/dist/commands/usage-remediation.js +52 -0
  34. package/dist/extractors/markdown.d.ts +17 -1
  35. package/dist/extractors/markdown.d.ts.map +1 -1
  36. package/dist/extractors/markdown.js +81 -2
  37. package/dist/logic/ir.d.ts +112 -0
  38. package/dist/logic/ir.d.ts.map +1 -0
  39. package/dist/logic/ir.js +729 -0
  40. package/dist/operations/modeling/logic-modeling.d.ts +22 -0
  41. package/dist/operations/modeling/logic-modeling.d.ts.map +1 -0
  42. package/dist/operations/modeling/logic-modeling.js +120 -0
  43. package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
  44. package/dist/operations/modeling/model-requirement.js +83 -2
  45. package/dist/operations/modeling/predicate-applyplan.d.ts +1 -1
  46. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  47. package/dist/operations/modeling/predicate-applyplan.js +29 -4
  48. package/dist/operations/modeling/predicate-inference.d.ts.map +1 -1
  49. package/dist/operations/modeling/predicate-inference.js +3 -0
  50. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  51. package/dist/operations/modeling/predicate-loader.js +7 -1
  52. package/dist/operations/modeling/predicate-ranker.d.ts.map +1 -1
  53. package/dist/operations/modeling/predicate-ranker.js +40 -2
  54. package/dist/operations/modeling/predicate-types.d.ts +12 -1
  55. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  56. package/dist/operations/modeling/requirement-types.d.ts +12 -0
  57. package/dist/operations/modeling/requirement-types.d.ts.map +1 -1
  58. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  59. package/dist/operations/modeling/suggest-predicates.js +35 -11
  60. package/dist/operations/mutation/audit.js +1 -1
  61. package/dist/operations/mutation/contradictions.d.ts +1 -1
  62. package/dist/operations/mutation/contradictions.d.ts.map +1 -1
  63. package/dist/operations/mutation/contradictions.js +23 -18
  64. package/dist/operations/mutation/relationships.d.ts.map +1 -1
  65. package/dist/operations/mutation/relationships.js +8 -0
  66. package/dist/operations/mutation/serialization.d.ts.map +1 -1
  67. package/dist/operations/mutation/serialization.js +6 -0
  68. package/dist/operations/mutation/strict-fact.d.ts.map +1 -1
  69. package/dist/operations/mutation/strict-fact.js +68 -3
  70. package/dist/operations/mutation/upsert.d.ts +3 -1
  71. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  72. package/dist/operations/mutation/upsert.js +51 -12
  73. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  74. package/dist/operations/mutation/validate-upsert.js +18 -2
  75. package/dist/operations/mutation/validation.d.ts.map +1 -1
  76. package/dist/operations/mutation/validation.js +8 -0
  77. package/dist/operations/semantic-advisor/analysis-receipt.d.ts +2 -2
  78. package/dist/operations/semantic-advisor/analysis-receipt.d.ts.map +1 -1
  79. package/dist/operations/semantic-advisor/analysis-receipt.js +30 -16
  80. package/dist/operations/semantic-advisor/analyze-prose.d.ts +1 -1
  81. package/dist/operations/semantic-advisor/analyze-prose.d.ts.map +1 -1
  82. package/dist/operations/semantic-advisor/analyze-prose.js +464 -27
  83. package/dist/operations/semantic-advisor/clauses.d.ts +1 -0
  84. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  85. package/dist/operations/semantic-advisor/clauses.js +7 -7
  86. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts +18 -0
  87. package/dist/operations/semantic-advisor/ingestion-boundary.d.ts.map +1 -0
  88. package/dist/operations/semantic-advisor/ingestion-boundary.js +167 -0
  89. package/dist/operations/semantic-advisor/observation-plan.d.ts.map +1 -1
  90. package/dist/operations/semantic-advisor/observation-plan.js +15 -3
  91. package/dist/operations/semantic-advisor/shared.d.ts +9 -0
  92. package/dist/operations/semantic-advisor/shared.d.ts.map +1 -1
  93. package/dist/operations/semantic-advisor/shared.js +38 -4
  94. package/dist/operations/semantic-advisor/types.d.ts +66 -1
  95. package/dist/operations/semantic-advisor/types.d.ts.map +1 -1
  96. package/dist/prolog/codec.d.ts.map +1 -1
  97. package/dist/prolog/codec.js +19 -2
  98. package/dist/prolog.d.ts +6 -0
  99. package/dist/prolog.d.ts.map +1 -1
  100. package/dist/prolog.js +247 -63
  101. package/dist/public/diagnostic-usage.d.ts +17 -0
  102. package/dist/public/diagnostic-usage.d.ts.map +1 -0
  103. package/dist/public/diagnostic-usage.js +152 -0
  104. package/dist/public/distribution-parity.d.ts +90 -0
  105. package/dist/public/distribution-parity.d.ts.map +1 -0
  106. package/dist/public/distribution-parity.js +367 -0
  107. package/dist/public/impact/full-kb-quality.d.ts +2 -0
  108. package/dist/public/impact/full-kb-quality.d.ts.map +1 -1
  109. package/dist/public/impact/full-kb-quality.js +53 -1
  110. package/dist/public/impact-diagnostics.d.ts +2 -0
  111. package/dist/public/impact-diagnostics.d.ts.map +1 -1
  112. package/dist/public/impact-diagnostics.js +1 -0
  113. package/dist/public/operations/catalog.d.ts +235 -11
  114. package/dist/public/operations/catalog.d.ts.map +1 -1
  115. package/dist/public/operations/check-executor.d.ts.map +1 -1
  116. package/dist/public/operations/check-executor.js +4 -0
  117. package/dist/public/operations/discovery-executors.d.ts +6 -0
  118. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  119. package/dist/public/operations/discovery-executors.js +18 -2
  120. package/dist/public/operations/index.d.ts +5 -0
  121. package/dist/public/operations/index.d.ts.map +1 -1
  122. package/dist/public/operations/index.js +5 -0
  123. package/dist/public/operations/legacy-migration-plan.d.ts +113 -0
  124. package/dist/public/operations/legacy-migration-plan.d.ts.map +1 -0
  125. package/dist/public/operations/legacy-migration-plan.js +409 -0
  126. package/dist/public/operations/node-ports.d.ts.map +1 -1
  127. package/dist/public/operations/node-ports.js +90 -0
  128. package/dist/public/operations/repair-plan.d.ts +184 -0
  129. package/dist/public/operations/repair-plan.d.ts.map +1 -0
  130. package/dist/public/operations/repair-plan.js +397 -0
  131. package/dist/public/operations/runtime-types.d.ts +7 -0
  132. package/dist/public/operations/runtime-types.d.ts.map +1 -1
  133. package/dist/public/operations/specs/check.d.ts +3 -3
  134. package/dist/public/operations/specs/check.d.ts.map +1 -1
  135. package/dist/public/operations/specs/check.js +5 -2
  136. package/dist/public/operations/specs/discovery.d.ts +1 -1
  137. package/dist/public/operations/specs/discovery.js +1 -1
  138. package/dist/public/operations/specs/modeling.d.ts +35 -1
  139. package/dist/public/operations/specs/modeling.d.ts.map +1 -1
  140. package/dist/public/operations/specs/modeling.js +32 -1
  141. package/dist/public/operations/specs/mutation-schemas.d.ts +133 -3
  142. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  143. package/dist/public/operations/specs/mutation-schemas.js +67 -1
  144. package/dist/public/operations/specs/reporting.d.ts +42 -1
  145. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  146. package/dist/public/operations/specs/reporting.js +70 -6
  147. package/dist/public/operations/specs/semantic.d.ts +30 -2
  148. package/dist/public/operations/specs/semantic.d.ts.map +1 -1
  149. package/dist/public/operations/specs/semantic.js +35 -3
  150. package/dist/public/operations/specs/upsert.d.ts +133 -3
  151. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  152. package/dist/public/operations/workspace-snapshot.d.ts +10 -0
  153. package/dist/public/operations/workspace-snapshot.d.ts.map +1 -0
  154. package/dist/public/operations/workspace-snapshot.js +20 -0
  155. package/dist/public/schemas/entity.d.ts.map +1 -1
  156. package/dist/public/schemas/entity.js +332 -0
  157. package/dist/public/schemas/relationship.d.ts.map +1 -1
  158. package/dist/public/schemas/relationship.js +1 -0
  159. package/dist/public/skills/kibi-traceability/SKILL.md +7 -1
  160. package/dist/public/skills/kibi-usage/SKILL.md +44 -12
  161. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +12 -2
  162. package/dist/public/skills/kibi-usage/resources/logic-ir.md +43 -0
  163. package/dist/public/skills/kibi-usage/resources/operation-access.md +19 -19
  164. package/dist/public/skills/kibi-usage/resources/ui-requirements.md +96 -0
  165. package/dist/public/skills/kibi-usage/resources/workflows.md +40 -8
  166. package/dist/public/symbol-granularity.d.ts +9 -0
  167. package/dist/public/symbol-granularity.d.ts.map +1 -1
  168. package/dist/public/symbol-granularity.js +16 -0
  169. package/dist/public/telemetry-acceptance.d.ts +89 -0
  170. package/dist/public/telemetry-acceptance.d.ts.map +1 -0
  171. package/dist/public/telemetry-acceptance.js +597 -0
  172. package/dist/public/telemetry-remediation.d.ts +33 -0
  173. package/dist/public/telemetry-remediation.d.ts.map +1 -0
  174. package/dist/public/telemetry-remediation.js +257 -0
  175. package/dist/public/verification-receipt.d.ts +75 -0
  176. package/dist/public/verification-receipt.d.ts.map +1 -0
  177. package/dist/public/verification-receipt.js +129 -0
  178. package/dist/schemas/entity.schema.json +401 -4
  179. package/dist/traceability/staged-diagnostics.d.ts +1 -1
  180. package/dist/traceability/staged-diagnostics.d.ts.map +1 -1
  181. package/dist/traceability/staged-diagnostics.js +17 -10
  182. package/dist/traceability/staged-impact-contract.js +1 -1
  183. package/dist/traceability/staged-symbols-manifest.d.ts.map +1 -1
  184. package/dist/traceability/staged-symbols-manifest.js +28 -10
  185. package/dist/traceability/temp-kb.d.ts.map +1 -1
  186. package/dist/traceability/temp-kb.js +49 -3
  187. package/dist/types/entities.d.ts +1 -0
  188. package/dist/types/entities.d.ts.map +1 -1
  189. package/dist/types/relationships.d.ts +1 -1
  190. package/dist/types/relationships.d.ts.map +1 -1
  191. package/dist/utils/rule-registry.d.ts +2 -0
  192. package/dist/utils/rule-registry.d.ts.map +1 -1
  193. package/dist/utils/rule-registry.js +18 -0
  194. package/dist/utils/schema-version.d.ts +1 -1
  195. package/dist/utils/schema-version.js +1 -1
  196. package/package.json +34 -2
  197. package/src/public/diagnostic-usage.ts +206 -0
  198. package/src/public/distribution-parity.ts +580 -0
  199. package/src/public/impact/full-kb-quality.ts +74 -1
  200. package/src/public/impact-diagnostics.ts +16 -0
  201. package/src/public/operations/check-executor.ts +4 -0
  202. package/src/public/operations/discovery-executors.ts +24 -2
  203. package/src/public/operations/index.ts +5 -0
  204. package/src/public/operations/legacy-migration-plan.ts +688 -0
  205. package/src/public/operations/node-ports.ts +109 -0
  206. package/src/public/operations/repair-plan.ts +562 -0
  207. package/src/public/operations/runtime-types.ts +8 -0
  208. package/src/public/operations/specs/check.ts +5 -2
  209. package/src/public/operations/specs/discovery.ts +1 -1
  210. package/src/public/operations/specs/modeling.ts +39 -1
  211. package/src/public/operations/specs/mutation-schemas.ts +77 -1
  212. package/src/public/operations/specs/reporting.ts +91 -6
  213. package/src/public/operations/specs/semantic.ts +42 -3
  214. package/src/public/operations/workspace-snapshot.ts +28 -0
  215. package/src/public/schemas/entity.ts +340 -1
  216. package/src/public/schemas/relationship.ts +1 -0
  217. package/src/public/skills/kibi-traceability/SKILL.md +7 -1
  218. package/src/public/skills/kibi-usage/SKILL.md +44 -12
  219. package/src/public/skills/kibi-usage/resources/fact-lanes.md +12 -2
  220. package/src/public/skills/kibi-usage/resources/logic-ir.md +43 -0
  221. package/src/public/skills/kibi-usage/resources/operation-access.md +19 -19
  222. package/src/public/skills/kibi-usage/resources/ui-requirements.md +96 -0
  223. package/src/public/skills/kibi-usage/resources/workflows.md +40 -8
  224. package/src/public/symbol-granularity.ts +25 -0
  225. package/src/public/telemetry-acceptance.ts +859 -0
  226. package/src/public/telemetry-remediation.ts +462 -0
  227. package/src/public/verification-receipt.ts +170 -0
  228. package/src/schemas/entity.schema.json +401 -4
  229. 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, spawnSync } from "node:child_process";
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
- if (!cacheable && oneShotResult.success) {
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 (cacheable && oneShotResult.success) {
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: ${normalizedGoal}`);
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 msg = `Query timeout after ${this.timeout / 1000}s (pid=${this.process?.pid ?? 0}, killed=${this.process?.killed ? "yes" : "no"}, exitCode=${this.process?.exitCode ?? "null"}, goal=${JSON.stringify(normalizedGoal.slice(0, 120))})`;
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
- console.error(`[prolog debug] last stdout: ---\n${tailOut}\n---`);
189
- console.error(`[prolog debug] last stderr: ---\n${tailErr}\n---`);
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: ${normalizedGoal} error=${this.errorBuffer.split("\n")[0]}`);
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: ${normalizedGoal} elapsed=${(Date.now() - start) / 1000}s`);
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 result = spawnSync(this.swiplPath, ["-q", "-g", prologGoal, "-t", "halt"], {
385
- encoding: "utf8",
386
- timeout: this.timeout,
387
- maxBuffer: PROLOG_OUTPUT_MAX_BUFFER_BYTES,
388
- env: {
389
- ...process.env,
390
- KIBI_GOAL: combinedGoal,
391
- ...(kbPath ? { KIBI_KB_PATH: kbPath } : {}),
392
- },
393
- stdio: ["ignore", "pipe", "pipe"],
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
- if (result.error &&
396
- (result.error.message.includes("timed out") ||
397
- // Bun/Node differ here; keep a conservative timeout detection.
398
- result.error.message.includes("ETIMEDOUT"))) {
399
- throw new Error(`Query timeout after ${this.timeout / 1000}s`);
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?.message.includes("ENOBUFS")) {
520
+ if (result.error !== undefined) {
402
521
  return {
403
522
  success: false,
404
523
  bindings: {},
405
- error: PROLOG_OUTPUT_OVERFLOW_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 (errorText.includes("does not exist") &&
501
- /entity [`'"].+?[`'"]/.test(errorText)) {
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 = errorText.match(/entity [`'"]+(.+?)[`'"]+ does not exist/);
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 (errorText.includes("Target entity does not exist")) {
698
+ if (cleanError.includes("Target entity does not exist")) {
509
699
  return `Target entity does not exist: ${entityId}`;
510
700
  }
511
- if (errorText.includes("Source entity does not exist")) {
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 (errorText.includes("Type error: `relationship' expected")) {
517
- const relMatch = errorText.match(/\(Invalid relationship: ([^)]+)\)/);
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 (errorText.includes("existence_error") ||
526
- errorText.includes("Unknown procedure")) {
715
+ if (cleanError.includes("existence_error") ||
716
+ cleanError.includes("Unknown procedure")) {
527
717
  return "Predicate or file not found";
528
718
  }
529
- if (errorText.includes("permission_error")) {
719
+ if (cleanError.includes("permission_error")) {
530
720
  return "Access denied or KB locked";
531
721
  }
532
- if (errorText.includes("syntax_error") ||
533
- errorText.includes("Operator expected")) {
722
+ if (cleanError.includes("syntax_error") ||
723
+ cleanError.includes("Operator expected")) {
534
724
  return "Invalid query syntax";
535
725
  }
536
- if (errorText.includes("timeout_error")) {
726
+ if (cleanError.includes("timeout_error")) {
537
727
  return `Operation exceeded ${this.timeout / 1000}s timeout`;
538
728
  }
539
- const simpleError = (errorText
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.stdin?.end();
567
- current.kill("SIGTERM");
568
- await new Promise((resolve) => {
569
- const timeout = setTimeout(() => {
570
- current.kill("SIGKILL");
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
+ }