kibi-cli 2.39.0 → 2.40.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 (163) hide show
  1. package/dist/cli-register-foundation.js +2 -2
  2. package/dist/commands/check.d.ts.map +1 -1
  3. package/dist/commands/check.js +33 -9
  4. package/dist/commands/doctor.d.ts.map +1 -1
  5. package/dist/commands/doctor.js +15 -0
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +4 -0
  8. package/dist/commands/migrate-plan-record.d.ts +18 -0
  9. package/dist/commands/migrate-plan-record.d.ts.map +1 -0
  10. package/dist/commands/migrate-plan-record.js +112 -0
  11. package/dist/commands/migrate.d.ts +11 -2
  12. package/dist/commands/migrate.d.ts.map +1 -1
  13. package/dist/commands/migrate.js +53 -7
  14. package/dist/commands/prove.d.ts.map +1 -1
  15. package/dist/commands/prove.js +18 -3
  16. package/dist/commands/sync/manifest.d.ts.map +1 -1
  17. package/dist/commands/sync/manifest.js +17 -4
  18. package/dist/commands/sync.d.ts.map +1 -1
  19. package/dist/commands/sync.js +8 -0
  20. package/dist/commands/telemetry.d.ts.map +1 -1
  21. package/dist/commands/telemetry.js +12 -2
  22. package/dist/extractors/symbol-title-match.d.ts +31 -0
  23. package/dist/extractors/symbol-title-match.d.ts.map +1 -0
  24. package/dist/extractors/symbol-title-match.js +133 -0
  25. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  26. package/dist/extractors/symbols-coordinator.js +81 -12
  27. package/dist/operations/bootstrap/discovery-evidence.js +2 -2
  28. package/dist/operations/bootstrap/guidance-actions.d.ts.map +1 -1
  29. package/dist/operations/bootstrap/guidance-actions.js +12 -1
  30. package/dist/operations/bootstrap/plugin-offers.d.ts +7 -1
  31. package/dist/operations/bootstrap/plugin-offers.d.ts.map +1 -1
  32. package/dist/operations/bootstrap/plugin-offers.js +73 -3
  33. package/dist/operations/modeling/logic-modeling.d.ts.map +1 -1
  34. package/dist/operations/modeling/logic-modeling.js +2 -1
  35. package/dist/operations/modeling/model-requirement.d.ts.map +1 -1
  36. package/dist/operations/modeling/model-requirement.js +2 -1
  37. package/dist/operations/mutation/symbol-refresh.d.ts +2 -0
  38. package/dist/operations/mutation/symbol-refresh.d.ts.map +1 -1
  39. package/dist/operations/mutation/symbol-refresh.js +6 -1
  40. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  41. package/dist/operations/mutation/upsert.js +7 -0
  42. package/dist/operations/planning/compile-intent.d.ts.map +1 -1
  43. package/dist/operations/planning/compile-intent.js +2 -1
  44. package/dist/operations/proof/claim-bindings.d.ts +11 -0
  45. package/dist/operations/proof/claim-bindings.d.ts.map +1 -1
  46. package/dist/operations/proof/claim-bindings.js +144 -1
  47. package/dist/operations/proof/code-certificates.d.ts +10 -3
  48. package/dist/operations/proof/code-certificates.d.ts.map +1 -1
  49. package/dist/operations/proof/code-certificates.js +65 -13
  50. package/dist/operations/proof/code-languages.d.ts +104 -0
  51. package/dist/operations/proof/code-languages.d.ts.map +1 -0
  52. package/dist/operations/proof/code-languages.js +119 -0
  53. package/dist/operations/proof/code-models.d.ts +2 -0
  54. package/dist/operations/proof/code-models.d.ts.map +1 -1
  55. package/dist/operations/proof/code-models.js +11 -5
  56. package/dist/operations/proof/code-runner.d.ts +12 -9
  57. package/dist/operations/proof/code-runner.d.ts.map +1 -1
  58. package/dist/operations/proof/code-runner.js +81 -38
  59. package/dist/operations/proof/code-scope.d.ts +8 -0
  60. package/dist/operations/proof/code-scope.d.ts.map +1 -1
  61. package/dist/operations/proof/code-scope.js +30 -1
  62. package/dist/operations/proof/explain.d.ts +13 -0
  63. package/dist/operations/proof/explain.d.ts.map +1 -1
  64. package/dist/operations/proof/explain.js +66 -0
  65. package/dist/operations/proof/ingest-proof.d.ts +2 -0
  66. package/dist/operations/proof/ingest-proof.d.ts.map +1 -1
  67. package/dist/operations/proof/ingest-proof.js +1 -0
  68. package/dist/operations/proof/java-package-shadow.d.ts +14 -0
  69. package/dist/operations/proof/java-package-shadow.d.ts.map +1 -0
  70. package/dist/operations/proof/java-package-shadow.js +389 -0
  71. package/dist/operations/proof/prolog-evaluator.d.ts +14 -0
  72. package/dist/operations/proof/prolog-evaluator.d.ts.map +1 -0
  73. package/dist/operations/proof/prolog-evaluator.js +247 -0
  74. package/dist/operations/proof/prolog-source-reader.d.ts +20 -0
  75. package/dist/operations/proof/prolog-source-reader.d.ts.map +1 -0
  76. package/dist/operations/proof/prolog-source-reader.js +1123 -0
  77. package/dist/operations/proof/source-code-readers.d.ts +6 -0
  78. package/dist/operations/proof/source-code-readers.d.ts.map +1 -1
  79. package/dist/operations/proof/source-code-readers.js +879 -75
  80. package/dist/plugins/approved-source-analyzers.generated.d.ts +13 -35
  81. package/dist/plugins/approved-source-analyzers.generated.d.ts.map +1 -1
  82. package/dist/plugins/approved-source-analyzers.generated.js +13 -35
  83. package/dist/plugins/plugin-install-check.d.ts +21 -0
  84. package/dist/plugins/plugin-install-check.d.ts.map +1 -0
  85. package/dist/plugins/plugin-install-check.js +62 -0
  86. package/dist/plugins/proof-reference-extractors.d.ts +20 -0
  87. package/dist/plugins/proof-reference-extractors.d.ts.map +1 -0
  88. package/dist/plugins/proof-reference-extractors.js +104 -0
  89. package/dist/plugins/reference-language-suggestion.d.ts +51 -0
  90. package/dist/plugins/reference-language-suggestion.d.ts.map +1 -0
  91. package/dist/plugins/reference-language-suggestion.js +277 -0
  92. package/dist/plugins/registry.d.ts.map +1 -1
  93. package/dist/plugins/registry.js +3 -1
  94. package/dist/plugins/source-analysis-service.d.ts.map +1 -1
  95. package/dist/plugins/source-analysis-service.js +4 -0
  96. package/dist/plugins/source-change-analysis.d.ts +26 -1
  97. package/dist/plugins/source-change-analysis.d.ts.map +1 -1
  98. package/dist/plugins/source-change-analysis.js +77 -5
  99. package/dist/plugins/source-classification.d.ts.map +1 -1
  100. package/dist/plugins/source-classification.js +23 -3
  101. package/dist/public/operations/catalog.d.ts +32 -7
  102. package/dist/public/operations/catalog.d.ts.map +1 -1
  103. package/dist/public/operations/check-executor.d.ts.map +1 -1
  104. package/dist/public/operations/check-executor.js +3 -0
  105. package/dist/public/operations/evidence-floor.d.ts +8 -0
  106. package/dist/public/operations/evidence-floor.d.ts.map +1 -1
  107. package/dist/public/operations/evidence-floor.js +19 -1
  108. package/dist/public/operations/specs/check-rules.generated.d.ts +1 -1
  109. package/dist/public/operations/specs/check-rules.generated.d.ts.map +1 -1
  110. package/dist/public/operations/specs/check-rules.generated.js +2 -1
  111. package/dist/public/operations/specs/check.d.ts +1 -1
  112. package/dist/public/operations/specs/mutation-schemas.d.ts +31 -6
  113. package/dist/public/operations/specs/mutation-schemas.d.ts.map +1 -1
  114. package/dist/public/operations/specs/reporting.d.ts +8 -0
  115. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  116. package/dist/public/operations/specs/reporting.js +26 -3
  117. package/dist/public/operations/specs/semantic.d.ts.map +1 -1
  118. package/dist/public/operations/specs/semantic.js +2 -1
  119. package/dist/public/operations/specs/upsert.d.ts +31 -6
  120. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  121. package/dist/public/operations/symbol-coordinates-check.d.ts +11 -0
  122. package/dist/public/operations/symbol-coordinates-check.d.ts.map +1 -0
  123. package/dist/public/operations/symbol-coordinates-check.js +66 -0
  124. package/dist/public/proof-protocol.d.ts +31 -6
  125. package/dist/public/proof-protocol.d.ts.map +1 -1
  126. package/dist/public/proof-protocol.js +45 -15
  127. package/dist/public/proof-sources.d.ts +4 -2
  128. package/dist/public/proof-sources.d.ts.map +1 -1
  129. package/dist/public/proof-sources.js +43 -4
  130. package/dist/public/skills/kibi-usage/SKILL.md +6 -3
  131. package/dist/public/skills/kibi-usage/resources/prolog-proofs.md +207 -0
  132. package/dist/public/skills/kibi-usage/resources/proof.md +44 -3
  133. package/dist/schemas/entity.schema.json +34 -6
  134. package/dist/traceability/analysis-gate.d.ts +3 -1
  135. package/dist/traceability/analysis-gate.d.ts.map +1 -1
  136. package/dist/traceability/analysis-gate.js +9 -1
  137. package/dist/traceability/staged-file-coverage.d.ts.map +1 -1
  138. package/dist/traceability/staged-file-coverage.js +7 -2
  139. package/dist/traceability/symbol-extract.d.ts +17 -0
  140. package/dist/traceability/symbol-extract.d.ts.map +1 -1
  141. package/dist/traceability/symbol-extract.js +69 -15
  142. package/dist/traceability/validate.d.ts.map +1 -1
  143. package/dist/traceability/validate.js +5 -7
  144. package/dist/utils/first-sentence.d.ts +9 -0
  145. package/dist/utils/first-sentence.d.ts.map +1 -0
  146. package/dist/utils/first-sentence.js +52 -0
  147. package/dist/utils/rule-registry.generated.d.ts +6 -0
  148. package/dist/utils/rule-registry.generated.d.ts.map +1 -1
  149. package/dist/utils/rule-registry.generated.js +7 -0
  150. package/package.json +5 -5
  151. package/src/public/operations/check-executor.ts +8 -0
  152. package/src/public/operations/evidence-floor.ts +29 -1
  153. package/src/public/operations/specs/check-rules.generated.ts +2 -1
  154. package/src/public/operations/specs/reporting.ts +38 -3
  155. package/src/public/operations/specs/semantic.ts +2 -1
  156. package/src/public/operations/symbol-coordinates-check.ts +79 -0
  157. package/src/public/proof-protocol-types.d.ts +22 -5
  158. package/src/public/proof-protocol.ts +78 -19
  159. package/src/public/proof-sources.ts +51 -3
  160. package/src/public/skills/kibi-usage/SKILL.md +6 -3
  161. package/src/public/skills/kibi-usage/resources/prolog-proofs.md +207 -0
  162. package/src/public/skills/kibi-usage/resources/proof.md +44 -3
  163. package/src/schemas/entity.schema.json +34 -6
@@ -279,21 +279,29 @@ export const PROOF_RUN_ARTIFACT_SCHEMA = {
279
279
  additionalProperties: false,
280
280
  } as const;
281
281
 
282
+ const ARG_SCHEMA = {
283
+ type: "integer",
284
+ minimum: 1,
285
+ description:
286
+ "A SWI-Prolog predicate's argument (1-based) whose solutions Kibi reads, when the predicate takes more than one argument (operator/2 with arg 1 lists each operator's name).",
287
+ } as const;
288
+
282
289
  const SOURCE_REFERENCE_SCHEMA = {
283
290
  type: "object",
284
291
  required: ["symbol_id"],
285
292
  properties: {
286
293
  symbol_id: { type: "string", minLength: 1 },
287
294
  export: { type: "string", minLength: 1 },
295
+ arg: ARG_SCHEMA,
288
296
  },
289
297
  additionalProperties: false,
290
298
  } as const;
291
299
 
292
- // implements REQ-kibi-source-proof-kinds
300
+ // implements REQ-kibi-source-proof-kinds, REQ-kibi-prolog-source-readers
293
301
  const SOURCE_SPEC_SCHEMA = {
294
302
  type: "object",
295
303
  description:
296
- "A project source Kibi reads by itself. reader json or yaml: {file, path?, select?: keys|values|items}. glob: {pattern, capture?, ignore?} (capture true reads what each * matched). openapi: {file, select?: operations|unsecured_operations|security_schemes|responses}. references: {files, extract: env|imports|calls|string_args|thrown|property, callee?, key?, ignore?}. schema: {format: drizzle|prisma|sql, file (drizzle, prisma) or files (sql migrations), exports?, table?, select?: tables|columns|required_columns|nullable_columns|foreign_keys}. routes: {file, export, framework?: hono|express, path_prefix?, names_from?, select?: routes|middleware}. validator: {file, export, select?: fields|required_fields|optional_fields}. Paths are repository-relative. A member Kibi cannot read statically fails the read with its location.",
304
+ "A project source Kibi reads by itself. reader json or yaml: {file, path?, select?: keys|values|items}. glob: {pattern, capture?, ignore?} (capture true reads what each * matched). openapi: {file, select?: operations|unsecured_operations|security_schemes|responses}. references: {files, extract: env|imports|calls|string_args|thrown|property, callee?, key?, ignore?} over TypeScript, JavaScript, Python and SWI-Prolog files. schema: {format: drizzle|prisma|sql, file (drizzle, prisma) or files (sql migrations), exports?, table?, select?: tables|columns|required_columns|nullable_columns|foreign_keys}. routes: {file, export, framework?: hono|express, path_prefix?, names_from?, select?: routes|middleware}. validator: {file, export, select?: fields|required_fields|optional_fields}. prolog: {file, functor: Name/Arity, arg?} reads one argument of every fact with that functor in a SWI-Prolog file (requires/1 in pack.pl), without running it. Paths are repository-relative. A member Kibi cannot read statically fails the read with its location.",
297
305
  required: ["reader"],
298
306
  properties: {
299
307
  reader: { type: "string", enum: [...SOURCE_READERS] },
@@ -382,6 +390,7 @@ export const PROOF_CONTRACT_SCHEMA = {
382
390
  description:
383
391
  "exhaustive: when the export is a list of records, the field of each record that names a domain member (a catalog's name).",
384
392
  },
393
+ arg: ARG_SCHEMA,
385
394
  },
386
395
  additionalProperties: false,
387
396
  },
@@ -411,6 +420,7 @@ export const PROOF_CONTRACT_SCHEMA = {
411
420
  properties: {
412
421
  symbol_id: { type: "string", minLength: 1 },
413
422
  export: { type: "string", minLength: 1 },
423
+ arg: ARG_SCHEMA,
414
424
  source: SOURCE_SPEC_SCHEMA,
415
425
  measure: {
416
426
  type: "string",
@@ -819,17 +829,30 @@ function proofContractKindErrors(contract: Record<string, unknown>): string[] {
819
829
  !nonEmptyString(row.symbol_id) ||
820
830
  (row.export !== undefined && !nonEmptyString(row.export)) ||
821
831
  (row.field !== undefined && !nonEmptyString(row.field)) ||
832
+ (row.arg !== undefined && !isArgIndex(row.arg)) ||
822
833
  Object.keys(row).some(
823
- (key) => key !== "symbol_id" && key !== "export" && key !== "field",
834
+ (key) =>
835
+ key !== "symbol_id" &&
836
+ key !== "export" &&
837
+ key !== "field" &&
838
+ key !== "arg",
824
839
  )
825
840
  )
826
841
  errors.push(
827
- "proof_contract.domain_source must be {symbol_id, export?, field?} naming the symbol that defines the domain, the export to read when its title is not the export name, and the record field that names each member",
842
+ "proof_contract.domain_source must be {symbol_id, export?, field?, arg?} naming the symbol that defines the domain, the export to read when its title is not the export name, the record field that names each member, and for a SWI-Prolog predicate the argument (1-based) that names it",
843
+ );
844
+ else if (row.field !== undefined && row.arg !== undefined)
845
+ errors.push(
846
+ "proof_contract.domain_source takes field (a record field) or arg (a Prolog argument), not both",
828
847
  );
829
848
  else if (row.field !== undefined && kind !== "exhaustive")
830
849
  errors.push(
831
850
  `proof_contract.domain_source.field applies only to kind exhaustive, not ${String(kind ?? "e2e")}`,
832
851
  );
852
+ else if (row.arg !== undefined && kind !== "exhaustive")
853
+ errors.push(
854
+ `proof_contract.domain_source.arg applies only to kind exhaustive, not ${String(kind ?? "e2e")}`,
855
+ );
833
856
  } else if (kind === "exhaustive" || kind === "typecheck")
834
857
  errors.push(
835
858
  `proof_contract.domain_source is required for kind ${String(kind)}: name the symbol the proof reads its domain from`,
@@ -878,9 +901,13 @@ function proofContractKindErrors(contract: Record<string, unknown>): string[] {
878
901
  `proof_contract.value_source[${index}].source`,
879
902
  ),
880
903
  );
881
- if (row.symbol_id !== undefined || row.export !== undefined)
904
+ if (
905
+ row.symbol_id !== undefined ||
906
+ row.export !== undefined ||
907
+ row.arg !== undefined
908
+ )
882
909
  errors.push(
883
- `proof_contract.value_source[${index}] names a source, so it takes no symbol_id or export`,
910
+ `proof_contract.value_source[${index}] names a source, so it takes no symbol_id, export or arg`,
884
911
  );
885
912
  if (row.measure !== undefined && row.measure !== "count")
886
913
  errors.push(
@@ -897,18 +924,22 @@ function proofContractKindErrors(contract: Record<string, unknown>): string[] {
897
924
  row !== null &&
898
925
  nonEmptyString(row.symbol_id) &&
899
926
  (row.export === undefined || nonEmptyString(row.export)) &&
927
+ (row.arg === undefined || isArgIndex(row.arg)) &&
900
928
  (row.measure === undefined ||
901
929
  row.measure === "value" ||
902
930
  row.measure === "count") &&
903
931
  Object.keys(row).every(
904
932
  (key) =>
905
- key === "symbol_id" || key === "export" || key === "measure",
933
+ key === "symbol_id" ||
934
+ key === "export" ||
935
+ key === "arg" ||
936
+ key === "measure",
906
937
  )
907
938
  );
908
939
  })
909
940
  )
910
941
  errors.push(
911
- "proof_contract.value_source must be [{symbol_id, export?, measure?: value|count} or {source, measure?: count}] naming the code constants or project sources the assumptions read",
942
+ "proof_contract.value_source must be [{symbol_id, export?, arg?, measure?: value|count} or {source, measure?: count}] naming the code constants or project sources the assumptions read",
912
943
  );
913
944
  else if (kind !== "arithmetic" && kind !== "smt")
914
945
  errors.push(
@@ -938,13 +969,23 @@ function proofContractKindErrors(contract: Record<string, unknown>): string[] {
938
969
  return errors;
939
970
  }
940
971
 
941
- function isSourceReference(value: unknown): boolean {
972
+ function isArgIndex(value: unknown): boolean {
973
+ return typeof value === "number" && Number.isInteger(value) && value >= 1;
974
+ }
975
+
976
+ function isSourceReference(
977
+ value: unknown,
978
+ options: Readonly<{ arg?: boolean }> = { arg: true },
979
+ ): boolean {
942
980
  const row = record(value);
943
981
  return (
944
982
  row !== null &&
945
983
  nonEmptyString(row.symbol_id) &&
946
984
  (row.export === undefined || nonEmptyString(row.export)) &&
947
- Object.keys(row).every((key) => key === "symbol_id" || key === "export")
985
+ (row.arg === undefined || (options.arg === true && isArgIndex(row.arg))) &&
986
+ Object.keys(row).every(
987
+ (key) => key === "symbol_id" || key === "export" || key === "arg",
988
+ )
948
989
  );
949
990
  }
950
991
 
@@ -964,11 +1005,12 @@ function isSourceMap(
964
1005
 
965
1006
  function isPredicateReference(value: unknown): boolean {
966
1007
  const row = record(value);
1008
+ // A predicate is called with every argument, so it projects none.
967
1009
  if (row === null || row.harness_for === undefined)
968
- return isSourceReference(value);
1010
+ return isSourceReference(value, { arg: false });
969
1011
  const { harness_for: harness, ...reference } = row;
970
1012
  return (
971
- isSourceReference(reference) &&
1013
+ isSourceReference(reference, { arg: false }) &&
972
1014
  Array.isArray(harness) &&
973
1015
  harness.length > 0 &&
974
1016
  harness.every(nonEmptyString)
@@ -998,7 +1040,7 @@ function sourceContractErrors(contract: Record<string, unknown>): string[] {
998
1040
  );
999
1041
  } else if (kind === "source")
1000
1042
  errors.push(
1001
- "proof_contract.source is required for kind source: name the project source Kibi reads (reader json, yaml, glob, openapi, references, schema, routes or validator)",
1043
+ "proof_contract.source is required for kind source: name the project source Kibi reads (reader json, yaml, glob, openapi, references, schema, routes, validator or prolog)",
1002
1044
  );
1003
1045
  const domains = record(contract.domains);
1004
1046
  for (const [type, entry] of Object.entries(domains ?? {}))
@@ -1046,12 +1088,12 @@ function nonNumericContractErrors(contract: Record<string, unknown>): string[] {
1046
1088
  )
1047
1089
  )
1048
1090
  errors.push(
1049
- "proof_contract.domains must map each argument type to {symbol_id, export?} naming the export that lists its values, or {source} naming the project source Kibi reads them from",
1091
+ "proof_contract.domains must map each argument type to {symbol_id, export?, arg?} naming the export (or SWI-Prolog predicate and argument) that lists its values, or {source} naming the project source Kibi reads them from",
1050
1092
  );
1051
1093
  for (const field of ["initial_source", "pattern_source"])
1052
1094
  if (contract[field] !== undefined && !isSourceReference(contract[field]))
1053
1095
  errors.push(
1054
- `proof_contract.${field} must be {symbol_id, export?} naming the export Kibi reads`,
1096
+ `proof_contract.${field} must be {symbol_id, export?, arg?} naming the export (or SWI-Prolog predicate and argument) Kibi reads`,
1055
1097
  );
1056
1098
  only("predicate_source", ["rule"]);
1057
1099
  only("domains", ["rule", "state_machine"]);
@@ -1113,7 +1155,12 @@ export function optionalProofContractFields(
1113
1155
  > {
1114
1156
  const fields: {
1115
1157
  kind?: ProofKind;
1116
- domain_source?: { symbol_id: string; export?: string; field?: string };
1158
+ domain_source?: {
1159
+ symbol_id: string;
1160
+ export?: string;
1161
+ field?: string;
1162
+ arg?: number;
1163
+ };
1117
1164
  witness?: { path: string };
1118
1165
  constraint_source?: { fact_ids: string[] };
1119
1166
  value_source?: ValueSourceEntry[];
@@ -1132,6 +1179,7 @@ export function optionalProofContractFields(
1132
1179
  symbol_id: domain.symbol_id,
1133
1180
  ...(typeof domain.export === "string" ? { export: domain.export } : {}),
1134
1181
  ...(typeof domain.field === "string" ? { field: domain.field } : {}),
1182
+ ...(isArgIndex(domain.arg) ? { arg: domain.arg as number } : {}),
1135
1183
  };
1136
1184
  const witness = record(contract.witness);
1137
1185
  if (witness && typeof witness.path === "string")
@@ -1154,6 +1202,7 @@ export function optionalProofContractFields(
1154
1202
  return {
1155
1203
  symbol_id: row.symbol_id,
1156
1204
  ...(typeof row.export === "string" ? { export: row.export } : {}),
1205
+ ...(isArgIndex(row.arg) ? { arg: row.arg as number } : {}),
1157
1206
  ...(row.measure === "value" || row.measure === "count"
1158
1207
  ? { measure: row.measure }
1159
1208
  : {}),
@@ -1177,10 +1226,19 @@ export function optionalProofContractFields(
1177
1226
  return fields;
1178
1227
  }
1179
1228
 
1180
- type SourceReference = { symbol_id: string; export?: string };
1181
- type PredicateReference = SourceReference & { harness_for?: string[] };
1229
+ type SourceReference = { symbol_id: string; export?: string; arg?: number };
1230
+ type PredicateReference = {
1231
+ symbol_id: string;
1232
+ export?: string;
1233
+ harness_for?: string[];
1234
+ };
1182
1235
  type ValueSourceEntry =
1183
- | { symbol_id: string; export?: string; measure?: "value" | "count" }
1236
+ | {
1237
+ symbol_id: string;
1238
+ export?: string;
1239
+ arg?: number;
1240
+ measure?: "value" | "count";
1241
+ }
1184
1242
  | { source: SourceSpec; measure: "count" };
1185
1243
 
1186
1244
  function predicateMap(
@@ -1229,6 +1287,7 @@ function sourceReference(value: unknown): SourceReference | undefined {
1229
1287
  return {
1230
1288
  symbol_id: row.symbol_id,
1231
1289
  ...(typeof row.export === "string" ? { export: row.export } : {}),
1290
+ ...(isArgIndex(row.arg) ? { arg: row.arg as number } : {}),
1232
1291
  };
1233
1292
  }
1234
1293
 
@@ -22,8 +22,9 @@
22
22
  * JSON or YAML file, the files a glob matches, an OpenAPI document, the
23
23
  * references the code makes (environment variables, imports, calls, string
24
24
  * arguments, thrown error codes, property values), a data schema (drizzle,
25
- * Prisma or SQL migrations), a web app's route table (Hono, Express) and a
26
- * validator object (zod and anything with safeParse).
25
+ * Prisma or SQL migrations), a web app's route table (Hono, Express), a
26
+ * validator object (zod and anything with safeParse) and the facts of a
27
+ * SWI-Prolog file (a pack's requires/1).
27
28
  *
28
29
  * Every reader is exact or refuses: a member Kibi cannot read statically (a
29
30
  * computed environment variable name, a dynamic import, an ALTER TABLE form
@@ -47,6 +48,7 @@ export const SOURCE_READERS = [
47
48
  "schema",
48
49
  "routes",
49
50
  "validator",
51
+ "prolog",
50
52
  ] as const;
51
53
 
52
54
  export type SourceReader = (typeof SOURCE_READERS)[number];
@@ -148,6 +150,7 @@ const KEYS: Readonly<Record<SourceReader, readonly string[]>> = {
148
150
  "select",
149
151
  ],
150
152
  validator: ["reader", "file", "export", "select"],
153
+ prolog: ["reader", "file", "functor", "arg"],
151
154
  };
152
155
 
153
156
  function record(value: unknown): Record<string, unknown> | null {
@@ -322,6 +325,23 @@ export function sourceSpecErrors(value: unknown, label: string): string[] {
322
325
  `${label}.export is required for reader validator: name the object schema`,
323
326
  );
324
327
  break;
328
+ case "prolog":
329
+ file("file", true);
330
+ if (
331
+ typeof row.functor !== "string" ||
332
+ !/^[a-z][A-Za-z0-9_]*\/(?:0|[1-9][0-9]*)$/.test(row.functor)
333
+ )
334
+ errors.push(
335
+ `${label}.functor is required for reader prolog: name the facts read as Name/Arity (requires/1)`,
336
+ );
337
+ if (
338
+ row.arg !== undefined &&
339
+ !(Number.isInteger(row.arg) && (row.arg as number) >= 1)
340
+ )
341
+ errors.push(
342
+ `${label}.arg must be the 1-based argument of each fact that holds the member`,
343
+ );
344
+ break;
325
345
  }
326
346
  return errors;
327
347
  }
@@ -352,15 +372,28 @@ export function matchWorkspaceFiles(
352
372
  workspaceRoot: string,
353
373
  patterns: readonly string[],
354
374
  ignore: readonly string[] = [],
375
+ caseSensitive = true,
355
376
  ): string[] {
377
+ // A symbolic link to a file is read as that file (git and compilers read
378
+ // it so); links to folders are not walked.
356
379
  return fg
357
380
  .sync([...patterns], {
358
381
  cwd: workspaceRoot,
359
382
  ignore: [...DEFAULT_IGNORE, ...ignore],
383
+ caseSensitiveMatch: caseSensitive,
360
384
  dot: true,
361
- onlyFiles: true,
385
+ onlyFiles: false,
386
+ markDirectories: true,
362
387
  followSymbolicLinks: false,
363
388
  })
389
+ .filter((file) => {
390
+ if (file.endsWith("/")) return false;
391
+ try {
392
+ return statSync(path.join(workspaceRoot, file)).isFile();
393
+ } catch {
394
+ return false;
395
+ }
396
+ })
364
397
  .map((file) => file.split(path.sep).join("/"))
365
398
  .sort();
366
399
  }
@@ -451,6 +484,9 @@ export function sourceDependencies(
451
484
  addFile(String(spec.file));
452
485
  for (const file of globList(spec.names_from)) addFile(file);
453
486
  break;
487
+ case "prolog":
488
+ addFile(String(spec.file));
489
+ break;
454
490
  }
455
491
  if (
456
492
  spec.reader === "routes" ||
@@ -1617,6 +1653,14 @@ export async function readSource(
1617
1653
  ok: true,
1618
1654
  model: { members: sortedUnique(evaluated.members ?? []) },
1619
1655
  };
1656
+ if (spec.reader === "prolog")
1657
+ return {
1658
+ ok: true,
1659
+ model: {
1660
+ members: sortedUnique(evaluated.members ?? []),
1661
+ sequence: evaluated.sequence ?? [],
1662
+ },
1663
+ };
1620
1664
  if (spec.reader === "routes") {
1621
1665
  const routes = selectedRoutes(spec, evaluated.routes ?? []).sort((a, b) =>
1622
1666
  `${a.path} ${a.method}` < `${b.path} ${b.method}` ? -1 : 1,
@@ -1644,6 +1688,8 @@ export type SourceCodeEvaluation =
1644
1688
  | {
1645
1689
  ok: true;
1646
1690
  members?: string[];
1691
+ /** The prolog reader's members in file order, duplicates kept. */
1692
+ sequence?: string[];
1647
1693
  entities?: SourceEntity[];
1648
1694
  routes?: SourceRoute[];
1649
1695
  }
@@ -1671,6 +1717,8 @@ export function describeSource(spec: SourceSpec): string {
1671
1717
  return `routes of ${spec.export} in ${spec.file}${spec.path_prefix ? ` under ${spec.path_prefix}` : ""}`;
1672
1718
  case "validator":
1673
1719
  return `validator ${spec.export} in ${spec.file}`;
1720
+ case "prolog":
1721
+ return `${spec.functor} facts in ${spec.file}${spec.arg ? ` (argument ${spec.arg})` : ""}`;
1674
1722
  }
1675
1723
  }
1676
1724
 
@@ -2,7 +2,7 @@
2
2
  id: kibi-usage
3
3
  name: Kibi Usage
4
4
  description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
5
- version: 2.37.0
5
+ version: 2.40.1
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -20,6 +20,7 @@ resources:
20
20
  - resources/fact-lanes.md
21
21
  - resources/workflows.md
22
22
  - resources/proof.md
23
+ - resources/prolog-proofs.md
23
24
  - resources/ui-requirements.md
24
25
  - resources/logic-ir.md
25
26
  - resources/kb-improvement.md
@@ -94,8 +95,10 @@ it is a pointer to the knowledge, never proof. Each line answers one question:
94
95
  requirement first. Read the lead requirement with `kb_query({id})` before
95
96
  changing behavior; add `includeBody: true` to see its authored prose, and
96
97
  fix a stale sentence there with `kb_upsert` `document.body`.
97
- - `REQ-… must keep true: FACT-…` (edit only): the ground facts the lead
98
- requirement requires. An edit that breaks one of them is a requirement
98
+ - `REQ-… must keep true: <claim>` (edit only): what the lead requirement
99
+ requires, as the claims its facts model (with the operator and members,
100
+ such as "The greet function must not call console.log") or, without
101
+ them, its own statement. An edit that breaks one of them is a requirement
99
102
  change: ask before changing the rule (see Decision firmness), and never
100
103
  code around the fact.
101
104
  - `Decision: ADR-…` (edit only): the decision behind the lead requirement.
@@ -0,0 +1,207 @@
1
+ # SWI-Prolog Code Proofs
2
+
3
+ Kibi reads code models from SWI-Prolog files (`.pl`, `.plt`) with the
4
+ SWI-Prolog it runs on (KIBI_SWIPL, the bundled runtime, then `swipl` on
5
+ PATH). It loads the file a symbol names (a module, or a plain file into
6
+ `user`) and reads a predicate named by its indicator, the symbol's title
7
+ (`operator/2`, `lists:append/3`, `greeting//1`) or `export`.
8
+
9
+ - A domain (`exhaustive`, `domains`, `value_source` with `measure: count`)
10
+ is the distinct solutions of one argument. A one-argument predicate needs
11
+ nothing more; for more arguments set `arg` (1-based) to the one that names
12
+ each member: `domain_source: {symbol_id: SYM-operator, arg: 1}` lists every
13
+ `Name` of `operator(Name, Description)`. A solution that leaves that
14
+ argument unbound refuses the read.
15
+ - A value (`value_source`) is the single number a constant holds:
16
+ `hard_limit_seconds(10).` Zero or several solutions refuse the read.
17
+ - A `rule` predicate is called once per row of its argument domains; success
18
+ is true, failure false, an exception an error row (which rejects).
19
+ - A `state_machine` transition table is a `Name/2` (from, to) or `Name/3`
20
+ (from, event, to) fact table: every solution is an edge. With `domains:
21
+ {state, event}` a `Name/3` next-state predicate is called on every state
22
+ and event. `initial_source` names an `initial/1`-style fact.
23
+ - A `regex` reads an atom or string constant as a `type: string` pattern;
24
+ Kibi decides string facts on it. Patterns for `library(pcre)` are not read.
25
+
26
+ Loading a module runs its directives, the same trust a JavaScript import
27
+ gives. The proof file must name the predicate and load its file
28
+ (`use_module`, `ensure_loaded`, `consult`, a `:- [..]` load list, or
29
+ `library(X)` for a pack's `prolog/X.pl`), or be the plunit `.plt` file
30
+ beside it; otherwise Kibi treats the domain as hand-listed. `arg` on a
31
+ TypeScript, JavaScript or Python export is refused, and so is `field` on a
32
+ Prolog predicate.
33
+
34
+ ## Writing the certificate
35
+
36
+ A Prolog test writes the `kibi.code-certificate.v1` JSON into the directory
37
+ `kibi prove` sets in `KIBI_PROOF_CERTIFICATES`. Copy this module into the
38
+ project's test directory (for example `test/kibi_certificate.pl`) and call it
39
+ from the proof. Read members from the code with `findall/3` over the domain
40
+ predicate; never list them by hand.
41
+
42
+ ```prolog
43
+ :- module(kibi_certificate,
44
+ [ kibi_exhaustive_certificate/5, % +TestId, +SymbolId, +Members, :Check, -Certificate
45
+ kibi_rule_certificate/4, % +TestId, +Domains, +Predicates, -Certificate
46
+ kibi_machine_certificate/5, % +TestId, +SymbolId, +Edges, +Initial, -Certificate
47
+ kibi_pattern_certificate/4, % +TestId, +SymbolId, +Text, -Certificate
48
+ kibi_write_certificate/1 % +Certificate
49
+ ]).
50
+ :- use_module(library(http/json)).
51
+ :- use_module(library(apply)).
52
+ :- use_module(library(lists)).
53
+
54
+ :- meta_predicate
55
+ kibi_exhaustive_certificate(+, +, +, 1, -).
56
+
57
+ % Members as Kibi reads them: atoms and strings as text, numbers as written,
58
+ % other terms quoted.
59
+ kibi_text(Value, Text) :- string(Value), !, Text = Value.
60
+ kibi_text(Value, Text) :- atom(Value), !, atom_string(Value, Text).
61
+ kibi_text(Value, Text) :- number(Value), !, number_string(Value, Text).
62
+ kibi_text(Value, Text) :- format(string(Text), "~q", [Value]).
63
+
64
+ %! kibi_exhaustive_certificate(+TestId, +SymbolId, +Members, :Check, -Certificate)
65
+ %
66
+ % Run Check on every member of the domain and record each outcome. Read
67
+ % Members from the code (findall over the domain predicate), never list
68
+ % them by hand: Kibi re-reads the predicate and rejects a different set.
69
+ kibi_exhaustive_certificate(TestId, SymbolId, Members, Check, Certificate) :-
70
+ maplist(kibi_text, Members, Texts),
71
+ maplist(exhaustive_result(Check), Members, Texts, Results),
72
+ Certificate = _{version: "kibi.code-certificate.v1", kind: "exhaustive",
73
+ test_id: TestId,
74
+ domain: _{symbol_id: SymbolId, members: Texts},
75
+ results: Results}.
76
+
77
+ exhaustive_result(Check, Member, Text, Result) :-
78
+ catch(( call(Check, Member)
79
+ -> Result = _{member: Text, outcome: "passed"}
80
+ ; Result = _{member: Text, outcome: "failed", message: "the check failed"}
81
+ ),
82
+ Error,
83
+ ( message_to_text(Error, Message),
84
+ Result = _{member: Text, outcome: "failed", message: Message} )).
85
+
86
+ %! kibi_rule_certificate(+TestId, +Domains, +Predicates, -Certificate)
87
+ %
88
+ % Domains is a list of domain(Type, SymbolId, Members); Predicates a list
89
+ % of predicate(Name, SymbolId, ArgTypes, Goal), where Goal is called with
90
+ % one argument per type appended and decides the row by success or
91
+ % failure.
92
+ kibi_rule_certificate(TestId, Domains, Predicates, Certificate) :-
93
+ findall(Type-_{symbol_id: SymbolId, members: Texts},
94
+ ( member(domain(Type, SymbolId, Members), Domains), maplist(kibi_text, Members, Texts) ),
95
+ DomainPairs),
96
+ dict_pairs(DomainDict, _, DomainPairs),
97
+ findall(Name-_{symbol_id: SymbolId, arg_types: ArgTypes, rows: Rows},
98
+ ( member(predicate(Name, SymbolId, ArgTypes, Goal), Predicates),
99
+ maplist(domain_members(Domains), ArgTypes, Lists),
100
+ findall(Row, (maplist(member, Tuple, Lists), rule_row(Goal, Tuple, Row)), Rows)
101
+ ),
102
+ PredicatePairs),
103
+ dict_pairs(PredicateDict, _, PredicatePairs),
104
+ Certificate = _{version: "kibi.code-certificate.v1", kind: "rule",
105
+ test_id: TestId, domains: DomainDict, predicates: PredicateDict}.
106
+
107
+ domain_members(Domains, Type, Members) :-
108
+ memberchk(domain(Type, _, Members), Domains).
109
+
110
+ rule_row(Goal, Tuple, Row) :-
111
+ maplist(kibi_text, Tuple, Args),
112
+ Call =.. [call, Goal|Tuple],
113
+ catch(( once(Call) -> Row = _{args: Args, value: true} ; Row = _{args: Args, value: false} ),
114
+ Error,
115
+ ( message_to_text(Error, Message), Row = _{args: Args, error: Message} )).
116
+
117
+ %! kibi_machine_certificate(+TestId, +SymbolId, +Edges, +Initial, -Certificate)
118
+ %
119
+ % Edges is the list of From-To pairs of the transition table (findall over
120
+ % it); Initial is the list of initial states, or [] when the contract names
121
+ % no initial_source.
122
+ kibi_machine_certificate(TestId, SymbolId, Edges, Initial, Certificate) :-
123
+ findall([F, T], (member(From-To, Edges), kibi_text(From, F), kibi_text(To, T)), Pairs0),
124
+ sort(Pairs0, Pairs),
125
+ findall(S, (member([A, B], Pairs), member(S, [A, B])), Ends),
126
+ sort(Ends, States),
127
+ Machine0 = _{symbol_id: SymbolId, states: States, transitions: Pairs},
128
+ ( Initial == []
129
+ -> Machine = Machine0
130
+ ; maplist(kibi_text, Initial, InitialTexts0),
131
+ sort(InitialTexts0, InitialTexts),
132
+ Machine = Machine0.put(initial, InitialTexts)
133
+ ),
134
+ Certificate = _{version: "kibi.code-certificate.v1", kind: "state_machine",
135
+ test_id: TestId, machine: Machine}.
136
+
137
+ %! kibi_pattern_certificate(+TestId, +SymbolId, +Text, -Certificate)
138
+ %
139
+ % The string or atom constant a string claim is about.
140
+ kibi_pattern_certificate(TestId, SymbolId, Value, Certificate) :-
141
+ kibi_text(Value, Text),
142
+ Certificate = _{version: "kibi.code-certificate.v1", kind: "regex", test_id: TestId,
143
+ pattern: _{symbol_id: SymbolId, type: "string", value: Text}}.
144
+
145
+ %! kibi_write_certificate(+Certificate)
146
+ %
147
+ % Leave the certificate where kibi prove reads it (the directory in
148
+ % KIBI_PROOF_CERTIFICATES). Does nothing outside a kibi prove run.
149
+ kibi_write_certificate(Certificate) :-
150
+ ( getenv('KIBI_PROOF_CERTIFICATES', Directory), Directory \== ''
151
+ -> make_directory_path(Directory),
152
+ format(atom(Name), "~w.json", [Certificate.test_id]),
153
+ directory_file_path(Directory, Name, File),
154
+ setup_call_cleanup(open(File, write, Out, [encoding(utf8)]),
155
+ json_write_dict(Out, Certificate, [width(0)]),
156
+ close(Out))
157
+ ; true
158
+ ).
159
+
160
+ message_to_text(Error, Text) :-
161
+ catch(message_to_codes(Error, Text), _, fail), !.
162
+ message_to_text(Error, Text) :- format(string(Text), "~q", [Error]).
163
+
164
+ message_to_codes(Error, Text) :-
165
+ '$messages':translate_message(Error, Lines, []),
166
+ with_output_to(string(Raw), print_message_lines(current_output, '', Lines)),
167
+ split_string(Raw, "", "\n ", [Text]).
168
+ ```
169
+
170
+ A proof that the operators are exactly the claimed set:
171
+
172
+ ```prolog
173
+ :- use_module('../prolog/operators').
174
+ :- use_module(kibi_certificate).
175
+ :- initialization(main, main).
176
+
177
+ main :-
178
+ findall(Name, operator(Name, _), Names0),
179
+ list_to_set(Names0, Names),
180
+ kibi_exhaustive_certificate('TEST-operator-set', 'SYM-operator', Names,
181
+ [Name]>>operator(Name, _), Certificate),
182
+ kibi_write_certificate(Certificate).
183
+ ```
184
+
185
+ Its contract: `kind: exhaustive`, `domain_source: {symbol_id: SYM-operator,
186
+ arg: 1}`, `proves: [CLAIM-...]`. A rule proof passes
187
+ `[domain(status, 'SYM-status', Statuses)]` and
188
+ `[predicate(detected, 'SYM-detected', [status], detected)]`; its contract
189
+ names `predicate_source: {detected: {symbol_id: SYM-detected}}` and
190
+ `domains: {status: {symbol_id: SYM-status}}`.
191
+
192
+ ## Source proofs over Prolog code
193
+
194
+ A `source` proof needs no certificate: Kibi reads the Prolog files itself,
195
+ term by term, and never runs them. Use the `references` reader on `.pl` and
196
+ `.plt` files for what modules load (`extract: imports`, members as written:
197
+ `library(lists)`, `helpers`), call (`calls`, `Name/Arity` or
198
+ `Module:Name/Arity`), read from the environment (`env`, `getenv/2`), pass as
199
+ a first argument (`string_args` with `callee`) and throw (`thrown`: the error
200
+ of `error(type_error(...), _)` is `type_error`). Use the `prolog` reader for
201
+ facts: `{reader: prolog, file: pack.pl, functor: requires/1}` lists a pack's
202
+ requirements, and `arg` picks the argument of a fact with several. A goal or
203
+ closure held in a variable (`call(Goal)`, `maplist(G, L)`), a goal with a
204
+ variable module, a clause asserted from a variable and `:- if(...)` fail the
205
+ read with their place: name the predicate instead. The claim cannot be
206
+ decided for a file the read refuses; never drop a file the claim is about
207
+ from `files`.