kibi-core 0.6.3 → 0.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-core",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "private": false,
5
5
  "description": "Core Prolog modules and RDF graph logic for Kibi",
6
6
  "type": "module",
@@ -9,10 +9,7 @@
9
9
  "bun": ">=1.0"
10
10
  },
11
11
  "main": "./src/kb.pl",
12
- "files": [
13
- "src/**/*.pl",
14
- "schema/**/*.pl"
15
- ],
12
+ "files": ["src/**/*.pl", "schema/**/*.pl"],
16
13
  "license": "AGPL-3.0-or-later",
17
14
  "author": "Piotr Franczyk",
18
15
  "repository": {
@@ -57,6 +57,10 @@ entity_property(fact, aliases, list).
57
57
  entity_property(fact, examples, list).
58
58
  entity_property(fact, predicate_args, list).
59
59
 
60
+ % Typed symbol metadata fields - only valid for symbol entities
61
+ entity_property(symbol, symbol_role, atom).
62
+ entity_property(symbol, granularity_reason, atom).
63
+
60
64
  % Typed test verification fields - only valid for test entities
61
65
  entity_property(test, verification_scope, atom).
62
66
  entity_property(test, verification_perspective, atom).
package/src/checks.pl CHANGED
@@ -17,6 +17,7 @@
17
17
  check_strict_fact_shape/1, % Returns list of malformed strict fact violations
18
18
  check_strict_req_fact_pairing/1,% Returns list of malformed strict req/fact pairing violations
19
19
  check_strict_readiness/1, % Returns list of strict readiness audit violations
20
+ check_predicate_verifiability/1,% Returns list of unverifiable predicate-lane requirement links
20
21
  run_checks_json/0, % Entry point for JSON output
21
22
  violation_id_text/2 % Extract text from entity ID term (exported for testing)
22
23
  ]).
@@ -52,6 +53,7 @@ check_all(ViolationsDict) :-
52
53
  check_strict_fact_shape(StrictFactShape),
53
54
  check_strict_req_fact_pairing(StrictReqFactPairing),
54
55
  check_strict_readiness(StrictReadiness),
56
+ check_predicate_verifiability(PredicateVerifiability),
55
57
  ViolationsDict = _{
56
58
  must_priority_coverage: MustPriority,
57
59
  symbol_coverage: SymbolCoverage,
@@ -63,7 +65,8 @@ check_all(ViolationsDict) :-
63
65
  domain_contradictions: Contradictions,
64
66
  strict_fact_shape: StrictFactShape,
65
67
  strict_req_fact_pairing: StrictReqFactPairing,
66
- strict_readiness: StrictReadiness
68
+ strict_readiness: StrictReadiness,
69
+ predicate_verifiability: PredicateVerifiability
67
70
  }.
68
71
 
69
72
  %% check_must_priority_coverage(-Violations)
@@ -132,7 +135,6 @@ symbol_traceability_violation(RequireAdr, violation(
132
135
  Source
133
136
  )) :-
134
137
  kb_entity(SymbolId, symbol, _),
135
- \+ executable_test_symbol(SymbolId),
136
138
  % Check if symbol has direct requirement ownership
137
139
  ( symbol_owns_requirement(SymbolId, ReqId),
138
140
  kb_entity(ReqId, req, _)
@@ -148,6 +150,7 @@ symbol_traceability_violation(RequireAdr, violation(
148
150
  )
149
151
  ; HasAdr = true % Not required, so pass this check
150
152
  ),
153
+ \+ executable_test_symbol(SymbolId),
151
154
  % Determine what is missing
152
155
  ( HasReq = false, HasAdr = false, RequireAdr = true ->
153
156
  Description = "Symbol has no direct requirement ownership and no ADR constraint.",
@@ -307,15 +310,14 @@ missing_required_field(Required, violation(
307
310
  )) :-
308
311
  kb_entity(EntityId, _Type, Props),
309
312
  member(Field, Required),
310
- \+ memberchk(Field=_, Props),
311
-
312
- format(string(Description), "Missing required field: ~w", [Field]),
313
- format(string(Suggestion), "Add ~w to entity definition", [Field]),
314
-
315
313
  ( memberchk(source=Source, Props)
316
314
  -> true
317
315
  ; Source = ""
318
- ).
316
+ ),
317
+ \+ memberchk(Field=_, Props),
318
+
319
+ format(string(Description), "Missing required field: ~w", [Field]),
320
+ format(string(Suggestion), "Add ~w to entity definition", [Field]).
319
321
 
320
322
  %% check_deprecated_adrs(-Violations)
321
323
  % Finds all deprecated ADRs without successors.
@@ -428,8 +430,7 @@ property_value_shape_error(Props, "Property value fact missing required field: v
428
430
  \+ memberchk(value_type=_, Props).
429
431
  property_value_shape_error(Props, "Property value fact missing value field (value_string, value_int, value_number, or value_bool)") :-
430
432
  memberchk(value_type=_, Props),
431
- \+ (memberchk(value_string=_, Props); memberchk(value_int=_, Props);
432
- memberchk(value_number=_, Props); memberchk(value_bool=_, Props)).
433
+ \+ has_value_field(Props).
433
434
  property_value_shape_error(Props, "Property value fact has multiple value fields (should have exactly one)") :-
434
435
  findall(F, (member(F=_, Props), is_value_field(F)), Fields),
435
436
  length(Fields, Count),
@@ -445,6 +446,11 @@ is_value_field(value_int).
445
446
  is_value_field(value_number).
446
447
  is_value_field(value_bool).
447
448
 
449
+ has_value_field(Props) :- memberchk(value_string=_, Props), !.
450
+ has_value_field(Props) :- memberchk(value_int=_, Props), !.
451
+ has_value_field(Props) :- memberchk(value_number=_, Props), !.
452
+ has_value_field(Props) :- memberchk(value_bool=_, Props), !.
453
+
448
454
  % value_type_matches_field(+ValueType, +Props)
449
455
  value_type_matches_field(string, Props) :- memberchk(value_string=_, Props), !.
450
456
  value_type_matches_field(int, Props) :- memberchk(value_int=_, Props), !.
@@ -752,6 +758,48 @@ strict_readiness_level(ReqId, traceable) :-
752
758
  !.
753
759
  strict_readiness_level(_ReqId, prose_only).
754
760
 
761
+ %% check_predicate_verifiability(-Violations)
762
+ % Finds requirements whose requires_predicate links do not target ground
763
+ % predicate facts. This keeps ontology-lane prose from looking modeled when the
764
+ % target is still a legacy, observation, meta, schema, or strict-property fact.
765
+ check_predicate_verifiability(Violations) :-
766
+ findall(
767
+ Violation,
768
+ predicate_verifiability_violation(Violation),
769
+ Violations0
770
+ ),
771
+ sort(Violations0, Violations).
772
+
773
+ predicate_verifiability_violation(violation(
774
+ 'predicate-verifiability',
775
+ ReqId,
776
+ Description,
777
+ "Apply kb_suggest_predicates and link requires_predicate to a fact_kind: predicate fact, or record a review:ontology-gap observation when no predicate fits",
778
+ Source
779
+ )) :-
780
+ kb:current_req(ReqId),
781
+ kb_relationship(requires_predicate, ReqId, FactId),
782
+ kb_entity(FactId, fact, Props),
783
+ predicate_verifiability_fact_kind(Props, Kind),
784
+ Kind \= predicate,
785
+ predicate_verifiability_kind_label(Kind, KindLabel),
786
+ format(
787
+ string(Description),
788
+ "Requirement uses requires_predicate ~w but target is ~w, not fact_kind=predicate",
789
+ [FactId, KindLabel]
790
+ ),
791
+ violation_source(ReqId, req, Source).
792
+
793
+ predicate_verifiability_fact_kind(Props, Kind) :-
794
+ ( memberchk(fact_kind=RawKind, Props)
795
+ -> normalize_term_atom(RawKind, Kind)
796
+ ; Kind = legacy
797
+ ).
798
+
799
+ predicate_verifiability_kind_label(legacy, "a legacy fact without fact_kind").
800
+ predicate_verifiability_kind_label(Kind, Label) :-
801
+ format(string(Label), "fact_kind=~w", [Kind]).
802
+
755
803
  strict_readiness_has_fact_link(ReqId) :-
756
804
  kb_relationship(constrains, ReqId, FactId),
757
805
  kb_entity(FactId, fact, _),
@@ -824,6 +872,7 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
824
872
  check_strict_fact_shape(StrictFactShape),
825
873
  check_strict_req_fact_pairing(StrictReqFactPairing),
826
874
  check_strict_readiness(StrictReadiness),
875
+ check_predicate_verifiability(PredicateVerifiability),
827
876
  ViolationsDict = _{
828
877
  must_priority_coverage: MustPriority,
829
878
  symbol_coverage: SymbolCoverage,
@@ -835,7 +884,8 @@ check_all_with_options(ViolationsDict, RequireAdr) :-
835
884
  domain_contradictions: Contradictions,
836
885
  strict_fact_shape: StrictFactShape,
837
886
  strict_req_fact_pairing: StrictReqFactPairing,
838
- strict_readiness: StrictReadiness
887
+ strict_readiness: StrictReadiness,
888
+ predicate_verifiability: PredicateVerifiability
839
889
  }.
840
890
 
841
891
  %% violations_dict_to_json(+ViolationsDict, -JsonDict)
package/src/discovery.pl CHANGED
@@ -131,6 +131,7 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
131
131
  count_direct_symbols(Id, DirectSymbolCount),
132
132
  count_transitive_symbols(Id, IncludeTransitive, TransitiveSymbolCount),
133
133
  requirement_coverage_state(Id, Props, Gaps, Evaluated, CoverageStatus),
134
+ requirement_coverage_depth(Id, ScenarioCount, CoverageDepth, CoverageEvidence),
134
135
  Row = _{
135
136
  id: Id,
136
137
  type: req,
@@ -143,7 +144,13 @@ requirement_coverage_row(Tags, IncludeTransitive, Row) :-
143
144
  transitiveSymbolCount: TransitiveSymbolCount,
144
145
  gaps: Gaps,
145
146
  evaluated: Evaluated,
146
- coverageStatus: CoverageStatus
147
+ coverageStatus: CoverageStatus,
148
+ coverageDepth: CoverageDepth,
149
+ coverage_depth: CoverageDepth,
150
+ directTests: CoverageEvidence.directTests,
151
+ scenarioTests: CoverageEvidence.scenarioTests,
152
+ testStatuses: CoverageEvidence.testStatuses,
153
+ verificationScopes: CoverageEvidence.verificationScopes
147
154
  }.
148
155
 
149
156
  symbol_coverage_row(Tags, Row) :-
@@ -232,6 +239,125 @@ requirement_test_count(Id, Count) :-
232
239
  sort(Combined0, Combined),
233
240
  length(Combined, Count).
234
241
 
242
+ requirement_coverage_depth(Id, ScenarioCount, CoverageDepth, Evidence) :-
243
+ requirement_direct_tests(Id, DirectTests),
244
+ requirement_scenario_tests(Id, ScenarioTests),
245
+ append(DirectTests, ScenarioTests, AllTests0),
246
+ sort(AllTests0, AllTests),
247
+ passing_tests(DirectTests, PassingDirectTests),
248
+ passing_tests(ScenarioTests, PassingScenarioTests),
249
+ passing_tests(AllTests, PassingTests),
250
+ test_statuses(AllTests, TestStatuses),
251
+ test_scopes(AllTests, VerificationScopes),
252
+ Evidence = _{
253
+ directTests: DirectTests,
254
+ scenarioTests: ScenarioTests,
255
+ testStatuses: TestStatuses,
256
+ verificationScopes: VerificationScopes
257
+ },
258
+ classify_coverage_depth(
259
+ ScenarioCount,
260
+ AllTests,
261
+ PassingTests,
262
+ PassingDirectTests,
263
+ PassingScenarioTests,
264
+ CoverageDepth
265
+ ).
266
+
267
+ requirement_direct_tests(Id, Tests) :-
268
+ findall(TestId, direct_requirement_test(Id, TestId), Tests0),
269
+ sort(Tests0, Tests).
270
+
271
+ direct_requirement_test(Id, TestId) :-
272
+ kb_relationship(verified_by, Id, TestId),
273
+ kb_entity(TestId, test, _).
274
+ direct_requirement_test(Id, TestId) :-
275
+ kb_relationship(validates, TestId, Id),
276
+ kb_entity(TestId, test, _).
277
+ direct_requirement_test(Id, TestId) :-
278
+ kb_relationship(covered_by, Id, TestId),
279
+ kb_entity(TestId, test, _).
280
+
281
+ requirement_scenario_tests(Id, Tests) :-
282
+ findall(TestId, scenario_requirement_test(Id, TestId), Tests0),
283
+ sort(Tests0, Tests).
284
+
285
+ scenario_requirement_test(Id, TestId) :-
286
+ kb_relationship(specified_by, Id, ScenarioId),
287
+ scenario_test(ScenarioId, TestId).
288
+
289
+ scenario_test(ScenarioId, TestId) :-
290
+ kb_relationship(validates, TestId, ScenarioId),
291
+ kb_entity(TestId, test, _).
292
+ scenario_test(ScenarioId, TestId) :-
293
+ kb_relationship(verified_by, ScenarioId, TestId),
294
+ kb_entity(TestId, test, _).
295
+
296
+ passing_tests(Tests, PassingTests) :-
297
+ include(passing_test, Tests, PassingTests).
298
+
299
+ passing_test(TestId) :-
300
+ kb_entity(TestId, test, Props),
301
+ memberchk(status=StatusRaw, Props),
302
+ source_value_atom(StatusRaw, passing).
303
+
304
+ test_statuses(Tests, Statuses) :-
305
+ findall(Status, (member(TestId, Tests), test_status(TestId, Status)), Statuses0),
306
+ sort(Statuses0, Statuses).
307
+
308
+ test_status(TestId, Status) :-
309
+ kb_entity(TestId, test, Props),
310
+ memberchk(status=StatusRaw, Props),
311
+ source_value_atom(StatusRaw, Status).
312
+
313
+ test_scopes(Tests, Scopes) :-
314
+ findall(Scope, (member(TestId, Tests), test_scope(TestId, Scope)), Scopes0),
315
+ sort(Scopes0, Scopes).
316
+
317
+ test_scope(TestId, Scope) :-
318
+ kb_entity(TestId, test, Props),
319
+ ( memberchk(verification_scope=ScopeRaw, Props)
320
+ -> source_value_atom(ScopeRaw, Scope)
321
+ ; legacy_e2e_test(Props)
322
+ -> Scope = end_to_end
323
+ ; Scope = unknown
324
+ ).
325
+
326
+ legacy_e2e_test(Props) :-
327
+ legacy_e2e_tag(Props),
328
+ !.
329
+ legacy_e2e_test(Props) :-
330
+ memberchk(source=SourceRaw, Props),
331
+ source_value_atom(SourceRaw, Source),
332
+ downcase_atom(Source, LowercaseSource),
333
+ sub_atom(LowercaseSource, _, _, _, 'e2e').
334
+
335
+ legacy_e2e_tag(Props) :-
336
+ memberchk(tags=Tags, Props),
337
+ member(TagRaw, Tags),
338
+ source_value_atom(TagRaw, Tag),
339
+ downcase_atom(Tag, e2e).
340
+
341
+ classify_coverage_depth(_ScenarioCount, _AllTests, _PassingTests, PassingDirectTests, _PassingScenarioTests, direct_passing_e2e) :-
342
+ member(TestId, PassingDirectTests),
343
+ test_scope(TestId, end_to_end),
344
+ !.
345
+ classify_coverage_depth(_ScenarioCount, _AllTests, _PassingTests, _PassingDirectTests, PassingScenarioTests, scenario_passing_e2e) :-
346
+ member(TestId, PassingScenarioTests),
347
+ test_scope(TestId, end_to_end),
348
+ !.
349
+ classify_coverage_depth(_ScenarioCount, _AllTests, PassingTests, _PassingDirectTests, _PassingScenarioTests, unit_only) :-
350
+ PassingTests \= [],
351
+ forall(member(TestId, PassingTests), test_scope(TestId, unit)),
352
+ !.
353
+ classify_coverage_depth(_ScenarioCount, AllTests, _PassingTests, _PassingDirectTests, _PassingScenarioTests, open_or_nonpassing_tests_only) :-
354
+ AllTests \= [],
355
+ !.
356
+ classify_coverage_depth(ScenarioCount, _AllTests, _PassingTests, _PassingDirectTests, _PassingScenarioTests, scenario_only_no_test) :-
357
+ ScenarioCount > 0,
358
+ !.
359
+ classify_coverage_depth(_ScenarioCount, _AllTests, _PassingTests, _PassingDirectTests, _PassingScenarioTests, no_test_evidence).
360
+
235
361
  count_direct_symbols(Id, Count) :-
236
362
  findall(SymbolId, kb_relationship(implements, SymbolId, Id), Symbols0),
237
363
  sort(Symbols0, Symbols),
package/src/status.pl CHANGED
@@ -117,6 +117,7 @@ documentation_markdown_untracked(WorkspaceRoot) :-
117
117
  documentation_markdown_file(Path, Path) :-
118
118
  exists_file(Path),
119
119
  file_name_extension(_, md, Path),
120
+ \+ ignored_documentation_file(Path),
120
121
  !.
121
122
  documentation_markdown_file(Path, FilePath) :-
122
123
  exists_directory(Path),
@@ -138,6 +139,8 @@ known_source_path(RelativePath) :-
138
139
  repo_relative_source(SourceAtom, RelativePath).
139
140
 
140
141
  directory_tree_newer(Path, SnapshotTime) :-
142
+ exists_file(Path),
143
+ \+ ignored_documentation_file(Path),
141
144
  time_file(Path, EntryTime),
142
145
  EntryTime > SnapshotTime,
143
146
  !.
@@ -212,5 +215,8 @@ source_value_atom(Val, Atom) :-
212
215
  term_string(Val, Str),
213
216
  atom_string(Atom, Str).
214
217
 
218
+ ignored_documentation_file(Path) :-
219
+ file_base_name(Path, 'README.md').
220
+
215
221
  dict_json_string(Dict, JsonString) :-
216
222
  with_output_to(string(JsonString), json_write_dict(current_output, Dict, [])).