speclogician 0.0.0b1__py3-none-any.whl

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 (139) hide show
  1. speclogician/__init__.py +0 -0
  2. speclogician/commands/__init__.py +15 -0
  3. speclogician/commands/cmd_ch.py +616 -0
  4. speclogician/commands/cmd_find.py +256 -0
  5. speclogician/commands/cmd_view.py +202 -0
  6. speclogician/commands/runner.py +149 -0
  7. speclogician/commands/utils.py +101 -0
  8. speclogician/data/__init__.py +0 -0
  9. speclogician/data/artifact.py +63 -0
  10. speclogician/data/container.py +402 -0
  11. speclogician/data/mapping.py +88 -0
  12. speclogician/data/refs.py +24 -0
  13. speclogician/data/traces.py +26 -0
  14. speclogician/demos/.DS_Store +0 -0
  15. speclogician/demos/cmd_demo.py +278 -0
  16. speclogician/demos/loader.py +135 -0
  17. speclogician/demos/model.py +27 -0
  18. speclogician/demos/runner.py +51 -0
  19. speclogician/logic/__init__.py +11 -0
  20. speclogician/logic/api/__init__.py +29 -0
  21. speclogician/logic/api/client.py +606 -0
  22. speclogician/logic/api/decomp.py +67 -0
  23. speclogician/logic/api/scenario.py +102 -0
  24. speclogician/logic/api/traces.py +59 -0
  25. speclogician/logic/lib/__init__.py +19 -0
  26. speclogician/logic/lib/complement.py +107 -0
  27. speclogician/logic/lib/domain_model.py +59 -0
  28. speclogician/logic/lib/predicates.py +151 -0
  29. speclogician/logic/lib/scenarios.py +369 -0
  30. speclogician/logic/lib/traces.py +114 -0
  31. speclogician/logic/lib/transitions.py +104 -0
  32. speclogician/logic/main.py +246 -0
  33. speclogician/logic/strings.py +194 -0
  34. speclogician/logic/utils.py +135 -0
  35. speclogician/main.py +139 -0
  36. speclogician/modeling/__init__.py +31 -0
  37. speclogician/modeling/complement.py +104 -0
  38. speclogician/modeling/component.py +71 -0
  39. speclogician/modeling/conflict.py +26 -0
  40. speclogician/modeling/domain.py +349 -0
  41. speclogician/modeling/predicates.py +59 -0
  42. speclogician/modeling/scenario.py +162 -0
  43. speclogician/modeling/spec.py +306 -0
  44. speclogician/modeling/spec_stats.py +39 -0
  45. speclogician/presentation/__init__.py +0 -0
  46. speclogician/presentation/api.py +244 -0
  47. speclogician/presentation/builders/__init__.py +0 -0
  48. speclogician/presentation/builders/_links.py +44 -0
  49. speclogician/presentation/builders/container.py +53 -0
  50. speclogician/presentation/builders/data_artifact.py +42 -0
  51. speclogician/presentation/builders/domain.py +54 -0
  52. speclogician/presentation/builders/instances_list.py +38 -0
  53. speclogician/presentation/builders/predicate.py +51 -0
  54. speclogician/presentation/builders/recommendations.py +41 -0
  55. speclogician/presentation/builders/scenario.py +41 -0
  56. speclogician/presentation/builders/scenario_complement.py +82 -0
  57. speclogician/presentation/builders/smart_find.py +39 -0
  58. speclogician/presentation/builders/spec.py +39 -0
  59. speclogician/presentation/builders/state_diff.py +150 -0
  60. speclogician/presentation/builders/state_instance.py +42 -0
  61. speclogician/presentation/builders/state_instance_summary.py +84 -0
  62. speclogician/presentation/builders/trace.py +58 -0
  63. speclogician/presentation/ctx.py +38 -0
  64. speclogician/presentation/models/__init__.py +0 -0
  65. speclogician/presentation/models/container.py +44 -0
  66. speclogician/presentation/models/data_artifact.py +33 -0
  67. speclogician/presentation/models/domain.py +50 -0
  68. speclogician/presentation/models/instances_list.py +23 -0
  69. speclogician/presentation/models/predicate.py +60 -0
  70. speclogician/presentation/models/recommendations.py +34 -0
  71. speclogician/presentation/models/scenario.py +31 -0
  72. speclogician/presentation/models/scenario_complement.py +40 -0
  73. speclogician/presentation/models/smart_find.py +34 -0
  74. speclogician/presentation/models/spec.py +32 -0
  75. speclogician/presentation/models/state_diff.py +34 -0
  76. speclogician/presentation/models/state_instance.py +31 -0
  77. speclogician/presentation/models/state_instance_summary.py +102 -0
  78. speclogician/presentation/models/trace.py +42 -0
  79. speclogician/presentation/preview/__init__.py +13 -0
  80. speclogician/presentation/preview/cli.py +50 -0
  81. speclogician/presentation/preview/fixtures/__init__.py +205 -0
  82. speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
  83. speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
  84. speclogician/presentation/preview/fixtures/domain_model.py +162 -0
  85. speclogician/presentation/preview/fixtures/instances_list.py +162 -0
  86. speclogician/presentation/preview/fixtures/predicate.py +184 -0
  87. speclogician/presentation/preview/fixtures/scenario.py +84 -0
  88. speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
  89. speclogician/presentation/preview/fixtures/smart_find.py +140 -0
  90. speclogician/presentation/preview/fixtures/spec.py +95 -0
  91. speclogician/presentation/preview/fixtures/state_diff.py +158 -0
  92. speclogician/presentation/preview/fixtures/state_instance.py +128 -0
  93. speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
  94. speclogician/presentation/preview/fixtures/trace.py +206 -0
  95. speclogician/presentation/preview/registry.py +42 -0
  96. speclogician/presentation/renderers/__init__.py +24 -0
  97. speclogician/presentation/renderers/container.py +136 -0
  98. speclogician/presentation/renderers/data_artifact.py +144 -0
  99. speclogician/presentation/renderers/domain.py +123 -0
  100. speclogician/presentation/renderers/instances_list.py +120 -0
  101. speclogician/presentation/renderers/predicate.py +180 -0
  102. speclogician/presentation/renderers/recommendations.py +90 -0
  103. speclogician/presentation/renderers/scenario.py +94 -0
  104. speclogician/presentation/renderers/scenario_complement.py +59 -0
  105. speclogician/presentation/renderers/smart_find.py +307 -0
  106. speclogician/presentation/renderers/spec.py +105 -0
  107. speclogician/presentation/renderers/state_diff.py +102 -0
  108. speclogician/presentation/renderers/state_instance.py +82 -0
  109. speclogician/presentation/renderers/state_instance_summary.py +143 -0
  110. speclogician/presentation/renderers/trace.py +122 -0
  111. speclogician/py.typed +0 -0
  112. speclogician/shell/app.py +170 -0
  113. speclogician/shell/shell_ch.py +263 -0
  114. speclogician/shell/shell_view.py +153 -0
  115. speclogician/state/__init__.py +0 -0
  116. speclogician/state/change.py +428 -0
  117. speclogician/state/change_result.py +32 -0
  118. speclogician/state/diff.py +191 -0
  119. speclogician/state/inst.py +574 -0
  120. speclogician/state/recommendation.py +13 -0
  121. speclogician/state/recommender.py +577 -0
  122. speclogician/state/state.py +465 -0
  123. speclogician/state/state_stats.py +133 -0
  124. speclogician/tui/__init__.py +0 -0
  125. speclogician/tui/app.py +257 -0
  126. speclogician/tui/app.tcss +160 -0
  127. speclogician/tui/demo.py +45 -0
  128. speclogician/tui/images/speclogician-full.png +0 -0
  129. speclogician/tui/images/speclogician-minimal.png +0 -0
  130. speclogician/tui/main_screen.py +454 -0
  131. speclogician/tui/splash_screen.py +51 -0
  132. speclogician/tui/stats_screen.py +125 -0
  133. speclogician/utils/__init__.py +78 -0
  134. speclogician/utils/load.py +166 -0
  135. speclogician/utils/prompt.md +325 -0
  136. speclogician/utils/testing.py +151 -0
  137. speclogician-0.0.0b1.dist-info/METADATA +116 -0
  138. speclogician-0.0.0b1.dist-info/RECORD +139 -0
  139. speclogician-0.0.0b1.dist-info/WHEEL +4 -0
@@ -0,0 +1,63 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/data/artifact.py
5
+ #
6
+
7
+ from __future__ import annotations
8
+
9
+ import uuid
10
+ from pydantic import BaseModel, Field, field_validator
11
+ from ..utils import IMLValidity
12
+
13
+ class Artifact(BaseModel):
14
+ """
15
+ Base class for an Artifact
16
+ """
17
+ # Assigned task ID, new one created if not provided
18
+ art_id : str | None = Field(default_factory=lambda: str(uuid.uuid4()))
19
+
20
+ @field_validator("art_id", mode="before")
21
+ @classmethod
22
+ def _coerce_none_to_uuid(cls, v:str|None):
23
+ if v is None:
24
+ return str(uuid.uuid4())
25
+ return v
26
+
27
+ class TraceIMLValidity(BaseModel):
28
+ iml_valid: IMLValidity
29
+ err: str | None = None
30
+
31
+ class TraceScenarioMatchResult(BaseModel):
32
+ scenario_name: str
33
+ iml_valid: bool
34
+ err: str | None = None
35
+ when_matches: bool = False
36
+ given_matches: bool = False
37
+ transition_matches: bool = False
38
+
39
+ class TraceArtifact(Artifact):
40
+ """
41
+ Concrete execution trace:
42
+ - given : concrete state value
43
+ - when : optional concrete action value
44
+ - then : optional resulting state value
45
+ """
46
+
47
+ given: str # state
48
+ when: str | None = None # action (optional)
49
+ then: str | None = None # state (optional)
50
+
51
+ iml_validity: TraceIMLValidity = Field(
52
+ default_factory=lambda: TraceIMLValidity(
53
+ iml_valid=IMLValidity.UNKNOWN,
54
+ err=None,
55
+ )
56
+ )
57
+ time: str = ""
58
+
59
+ class DataArtifact(Artifact):
60
+ """
61
+ Data may be either source code or documentation
62
+ """
63
+ pass
@@ -0,0 +1,402 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/data/container.py
5
+ #
6
+
7
+ from typing import Any
8
+ from pydantic import BaseModel, Field
9
+
10
+ from .traces import TestTrace, LogTrace
11
+ from .refs import DocRef, SrcCodeRef
12
+ from .mapping import ArtifactMap
13
+
14
+ from ..utils import IMLValidity
15
+
16
+ class SmartFindResults(BaseModel):
17
+ """Helper for returning search results"""
18
+ needle: str
19
+
20
+ test_traces: list[TestTrace] = Field(default_factory=list[TestTrace])
21
+ log_traces: list[LogTrace] = Field(default_factory=list[LogTrace])
22
+ doc_refs: list[DocRef] = Field(default_factory=list[DocRef])
23
+ src_code_refs: list[SrcCodeRef] = Field(default_factory=list[SrcCodeRef])
24
+
25
+ def empty(self) -> bool:
26
+ return not (self.test_traces or self.log_traces or self.doc_refs or self.src_code_refs)
27
+
28
+
29
+ class ArtifactContainer(BaseModel):
30
+ """Artifact container"""
31
+
32
+ test_traces: list[TestTrace] = Field(default_factory=list[TestTrace])
33
+ log_traces: list[LogTrace] = Field(default_factory=list[LogTrace])
34
+ doc_ref: list[DocRef] = Field(default_factory=list[DocRef])
35
+ src_code: list[SrcCodeRef] = Field(default_factory=list[SrcCodeRef])
36
+
37
+ num_test_traces_total: int = 0
38
+ num_test_traces_matched: int = 0
39
+ num_test_traces_logic_good: int = 0
40
+
41
+ num_log_traces_total: int = 0
42
+ num_log_traces_matched: int = 0
43
+ num_log_traces_logic_good: int = 0
44
+
45
+ num_src_code_arts_matched: int = 0
46
+ num_src_code_arts_total: int = 0
47
+
48
+ num_doc_arts_matched: int = 0
49
+ num_doc_arts_total: int = 0
50
+
51
+ # ----------------------------
52
+ # Internals
53
+ # ----------------------------
54
+
55
+ def _all_art_ids(self) -> set[str]:
56
+ ids: set[str] = set()
57
+ for xs in (self.test_traces, self.log_traces, self.doc_ref, self.src_code):
58
+ for a in xs:
59
+ if a.art_id is not None:
60
+ ids.add(a.art_id)
61
+ return ids
62
+
63
+ def _ensure_unique_art_id(self, art_id: str) -> None:
64
+ if art_id in self._all_art_ids():
65
+ raise ValueError(f"Artifact with art_id='{art_id}' already exists in container")
66
+
67
+ def refresh_stats(self, art_map:ArtifactMap|None=None) -> None:
68
+ # Totals
69
+ self.num_test_traces_total = len(self.test_traces)
70
+ self.num_log_traces_total = len(self.log_traces)
71
+ self.num_src_code_arts_total = len(self.src_code)
72
+ self.num_doc_arts_total = len(self.doc_ref)
73
+
74
+ # Matched (best effort; 0 unless artifacts have a matched flag)
75
+ if art_map is not None:
76
+ def _is_linked(art_id: str | None) -> bool:
77
+ if not art_id:
78
+ return False
79
+ return len(art_map.get_comp_for_artifact(art_id)) > 0
80
+
81
+ self.num_test_traces_matched = sum(1 for t in self.test_traces if _is_linked(t.art_id))
82
+ self.num_log_traces_matched = sum(1 for t in self.log_traces if _is_linked(t.art_id))
83
+ self.num_src_code_arts_matched = sum(1 for a in self.src_code if _is_linked(a.art_id))
84
+ self.num_doc_arts_matched = sum(1 for a in self.doc_ref if _is_linked(a.art_id))
85
+
86
+ # Logic good: count VALID IML traces
87
+ self.num_test_traces_logic_good = sum(
88
+ 1
89
+ for t in self.test_traces
90
+ if getattr(getattr(t, "iml_validity", None), "iml_valid", IMLValidity.UNKNOWN) == IMLValidity.VALID
91
+ )
92
+ self.num_log_traces_logic_good = sum(
93
+ 1
94
+ for t in self.log_traces
95
+ if getattr(getattr(t, "iml_validity", None), "iml_valid", IMLValidity.UNKNOWN) == IMLValidity.VALID
96
+ )
97
+
98
+ # ----------------------------
99
+ # Traces
100
+ # ----------------------------
101
+
102
+ def add_trace(self, trace: TestTrace | LogTrace) -> None:
103
+ """Add a trace (TestTrace or LogTrace). Uniqueness is by art_id only."""
104
+ if trace.art_id is None:
105
+ raise ValueError("Provided art_id is None")
106
+ self._ensure_unique_art_id(trace.art_id)
107
+
108
+ if isinstance(trace, TestTrace):
109
+ self.test_traces.append(trace)
110
+ elif isinstance(trace, LogTrace):
111
+ self.log_traces.append(trace)
112
+ else:
113
+ raise TypeError(f"Unsupported trace type: {type(trace).__name__}")
114
+
115
+ self.refresh_stats()
116
+
117
+ def rem_trace(self, art_id: str) -> None:
118
+ """Remove exactly one trace by art_id."""
119
+ before = len(self.test_traces)
120
+ self.test_traces = [t for t in self.test_traces if t.art_id != art_id]
121
+ if len(self.test_traces) != before:
122
+ self.refresh_stats()
123
+ return
124
+
125
+ before = len(self.log_traces)
126
+ self.log_traces = [t for t in self.log_traces if t.art_id != art_id]
127
+ if len(self.log_traces) != before:
128
+ self.refresh_stats()
129
+ return
130
+
131
+ raise ValueError(f"Trace not found (art_id='{art_id}')")
132
+
133
+ def edit_trace(self, art_id: str, patch: dict[str, object]) -> None:
134
+ """
135
+ Patch-edit a trace by art_id (Pydantic model_copy(update=...)).
136
+ """
137
+ for i, t in enumerate(self.test_traces):
138
+ if t.art_id == art_id:
139
+ self.test_traces[i] = t.model_copy(update=patch)
140
+ self.refresh_stats()
141
+ return
142
+
143
+ for i, t in enumerate(self.log_traces):
144
+ if t.art_id == art_id:
145
+ self.log_traces[i] = t.model_copy(update=patch)
146
+ self.refresh_stats()
147
+ return
148
+
149
+ raise ValueError(f"Trace not found (art_id='{art_id}')")
150
+
151
+ # ----------------------------
152
+ # Data artifacts
153
+ # ----------------------------
154
+
155
+ def add_data_art(self, art: DocRef | SrcCodeRef) -> None:
156
+ """Add a data artifact (DocRef or SrcCodeRef)."""
157
+ if art.art_id is None:
158
+ raise ValueError("Provided art_id is None")
159
+ self._ensure_unique_art_id(art.art_id)
160
+
161
+ if isinstance(art, DocRef):
162
+ self.doc_ref.append(art)
163
+ elif isinstance(art, SrcCodeRef):
164
+ self.src_code.append(art)
165
+ else:
166
+ raise TypeError(f"Unsupported data artifact type: {type(art).__name__}")
167
+
168
+ self.refresh_stats()
169
+
170
+ def rem_data_art(self, art_id: str) -> None:
171
+ """Remove exactly one data artifact by art_id."""
172
+ before = len(self.doc_ref)
173
+ self.doc_ref = [a for a in self.doc_ref if a.art_id != art_id]
174
+ if len(self.doc_ref) != before:
175
+ self.refresh_stats()
176
+ return
177
+
178
+ before = len(self.src_code)
179
+ self.src_code = [a for a in self.src_code if a.art_id != art_id]
180
+ if len(self.src_code) != before:
181
+ self.refresh_stats()
182
+ return
183
+
184
+ raise ValueError(f"Data artifact not found (art_id='{art_id}')")
185
+
186
+ def edit_data_art(self, art_id: str, patch: dict[str, object]) -> None:
187
+ """Patch-edit a data artifact by art_id."""
188
+ for i, a in enumerate(self.doc_ref):
189
+ if a.art_id == art_id:
190
+ self.doc_ref[i] = a.model_copy(update=patch)
191
+ self.refresh_stats()
192
+ return
193
+
194
+ for i, a in enumerate(self.src_code):
195
+ if a.art_id == art_id:
196
+ self.src_code[i] = a.model_copy(update=patch)
197
+ self.refresh_stats()
198
+ return
199
+
200
+ raise ValueError(f"Data artifact not found (art_id='{art_id}')")
201
+
202
+ # ----------------------------
203
+ # Iterators / basic accessors
204
+ # ----------------------------
205
+
206
+ def all_traces(self) -> list[TestTrace | LogTrace]:
207
+ return list(self.test_traces) + list(self.log_traces)
208
+
209
+ def all_data_arts(self) -> list[DocRef | SrcCodeRef]:
210
+ return list(self.doc_ref) + list(self.src_code)
211
+
212
+ def get_by_art_id(self, art_id: str) -> TestTrace | LogTrace | DocRef | SrcCodeRef | None:
213
+ for a in self.test_traces:
214
+ if a.art_id == art_id:
215
+ return a
216
+ for a in self.log_traces:
217
+ if a.art_id == art_id:
218
+ return a
219
+ for a in self.doc_ref:
220
+ if a.art_id == art_id:
221
+ return a
222
+ for a in self.src_code:
223
+ if a.art_id == art_id:
224
+ return a
225
+ return None
226
+
227
+
228
+ # ----------------------------
229
+ # TestTrace lookups
230
+ # ----------------------------
231
+
232
+ def find_test_traces_by_name(self, name: str) -> list[TestTrace]:
233
+ # NOT unique by design
234
+ return [t for t in self.test_traces if t.name == name]
235
+
236
+ def find_test_traces_by_filepath(self, filepath: str) -> list[TestTrace]:
237
+ return [t for t in self.test_traces if t.filepath == filepath]
238
+
239
+ def find_test_traces_by_language(self, language: str) -> list[TestTrace]:
240
+ return [t for t in self.test_traces if (t.language or "") == language]
241
+
242
+ def search_test_traces_text(self, needle: str) -> list[TestTrace]:
243
+ n = needle.lower()
244
+ out: list[TestTrace] = []
245
+ for t in self.test_traces:
246
+ hay = " ".join(
247
+ [
248
+ t.name or "",
249
+ t.filepath or "",
250
+ t.language or "",
251
+ t.contents or "",
252
+ ]
253
+ ).lower()
254
+ if n in hay:
255
+ out.append(t)
256
+ return out
257
+
258
+
259
+ # ----------------------------
260
+ # LogTrace lookups
261
+ # ----------------------------
262
+
263
+ def find_log_traces_by_filename(self, filename: str) -> list[LogTrace]:
264
+ # NOT unique by design
265
+ return [t for t in self.log_traces if t.filename == filename]
266
+
267
+ def search_log_traces_text(self, needle: str) -> list[LogTrace]:
268
+ n = needle.lower()
269
+ out: list[LogTrace] = []
270
+ for t in self.log_traces:
271
+ hay = " ".join([t.filename or "", t.contents or ""]).lower()
272
+ if n in hay:
273
+ out.append(t)
274
+ return out
275
+
276
+
277
+ # ----------------------------
278
+ # Data artifact lookups
279
+ # ----------------------------
280
+
281
+ def find_doc_refs_containing(self, needle: str) -> list[DocRef]:
282
+ n = needle.lower()
283
+ return [
284
+ d
285
+ for d in self.doc_ref
286
+ if n in (d.text or "").lower() or n in ((d.meta or "").lower())
287
+ ]
288
+
289
+ def find_src_code_by_file_path(self, file_path: str) -> list[SrcCodeRef]:
290
+ return [s for s in self.src_code if (s.file_path or "") == file_path]
291
+
292
+ def find_src_code_by_language(self, language: str) -> list[SrcCodeRef]:
293
+ return [s for s in self.src_code if (s.language or "") == language]
294
+
295
+ def search_src_code_text(self, needle: str) -> list[SrcCodeRef]:
296
+ n = needle.lower()
297
+ out: list[SrcCodeRef] = []
298
+ for s in self.src_code:
299
+ hay = " ".join(
300
+ [
301
+ s.file_path or "",
302
+ s.language or "",
303
+ s.src_code or "",
304
+ s.iml_code or "",
305
+ s.meta or "",
306
+ ]
307
+ ).lower()
308
+ if n in hay:
309
+ out.append(s)
310
+ return out
311
+
312
+ def smart_find(
313
+ self,
314
+ needle: str,
315
+ *,
316
+ include: set[str] | None = None,
317
+ max_per_type: int | None = 50,
318
+ ) -> SmartFindResults:
319
+ """
320
+ Search across *all* artifacts with a single entrypoint.
321
+
322
+ - `include`: optional subset of {"test", "log", "doc", "src"}; default = all.
323
+ - `max_per_type`: cap results per type (None = unlimited).
324
+ """
325
+ inc = include or {"test", "log", "doc", "src"}
326
+ n = (needle or "").lower()
327
+
328
+ def _cap(xs: list[Any]) -> list[Any]:
329
+ if max_per_type is None:
330
+ return xs
331
+ return xs[:max_per_type]
332
+
333
+ out = SmartFindResults(needle=needle)
334
+
335
+ if "test" in inc:
336
+ hits: list[TestTrace] = []
337
+ for t in self.test_traces:
338
+ hay = " ".join(
339
+ [
340
+ t.art_id or "",
341
+ t.name or "",
342
+ t.filepath or "",
343
+ t.language or "",
344
+ t.contents or "",
345
+ t.given or "",
346
+ t.when or "",
347
+ t.then or "",
348
+ ]
349
+ ).lower()
350
+ if n in hay:
351
+ hits.append(t)
352
+ out.test_traces = _cap(hits)
353
+
354
+ if "log" in inc:
355
+ hits: list[LogTrace] = []
356
+ for t in self.log_traces:
357
+ hay = " ".join(
358
+ [
359
+ t.art_id or "",
360
+ t.filename or "",
361
+ t.contents or "",
362
+ t.given or "",
363
+ t.when or "",
364
+ t.then or "",
365
+ ]
366
+ ).lower()
367
+ if n in hay:
368
+ hits.append(t)
369
+ out.log_traces = _cap(hits)
370
+
371
+ if "doc" in inc:
372
+ hits: list[DocRef] = []
373
+ for d in self.doc_ref:
374
+ hay = " ".join(
375
+ [
376
+ d.art_id or "",
377
+ d.text or "",
378
+ d.meta or "",
379
+ ]
380
+ ).lower()
381
+ if n in hay:
382
+ hits.append(d)
383
+ out.doc_refs = _cap(hits)
384
+
385
+ if "src" in inc:
386
+ hits: list[SrcCodeRef] = []
387
+ for s in self.src_code:
388
+ hay = " ".join(
389
+ [
390
+ s.art_id or "",
391
+ s.file_path or "",
392
+ s.language or "",
393
+ s.src_code or "",
394
+ s.iml_code or "",
395
+ s.meta or "",
396
+ ]
397
+ ).lower()
398
+ if n in hay:
399
+ hits.append(s)
400
+ out.src_code_refs = _cap(hits)
401
+
402
+ return out
@@ -0,0 +1,88 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/data/mapping.py
5
+ #
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from enum import StrEnum
10
+
11
+ class ElementType(StrEnum):
12
+ ArtifactType = 'data_artifact'
13
+ ComponentType = 'model_component'
14
+
15
+ class ArtifactMap(BaseModel):
16
+ """
17
+ Map between artifacts and model components. We maintain two structures to
18
+ handle potentially large datasets (e.g. massive logs)
19
+ """
20
+
21
+ art_to_comp_map: dict[str, list[str]] = Field(default_factory=dict)
22
+ comp_to_art_map: dict[str, list[str]] = Field(default_factory=dict)
23
+
24
+ def add_connection (self, art_id : str, comp_name : str):
25
+ """
26
+ Add a link between artifact and model component
27
+ """
28
+ if art_id not in self.art_to_comp_map:
29
+ self.art_to_comp_map[art_id] = []
30
+
31
+ if comp_name not in self.art_to_comp_map[art_id]:
32
+ self.art_to_comp_map[art_id].append(comp_name)
33
+
34
+ if comp_name not in self.comp_to_art_map:
35
+ self.comp_to_art_map[comp_name] = []
36
+
37
+ if art_id not in self.comp_to_art_map[comp_name]:
38
+ self.comp_to_art_map[comp_name].append(art_id)
39
+
40
+ def rem_connection (self, artifact_id : str, comp_name : str):
41
+ """
42
+ Remove a connection
43
+ """
44
+
45
+ if artifact_id in self.art_to_comp_map:
46
+ if comp_name in self.art_to_comp_map[artifact_id]:
47
+ self.art_to_comp_map[artifact_id].remove(comp_name)
48
+ else:
49
+ return False
50
+
51
+ if comp_name in self.comp_to_art_map:
52
+ if artifact_id in self.comp_to_art_map[comp_name]:
53
+ self.comp_to_art_map[comp_name].remove(artifact_id)
54
+
55
+ def rem_element (self, elem_type : ElementType, str_id : str):
56
+ """
57
+ Remove element from the both maps
58
+ """
59
+
60
+ if elem_type == ElementType.ArtifactType:
61
+ if str_id in self.art_to_comp_map:
62
+ del self.art_to_comp_map[str_id]
63
+ for comp_id in self.comp_to_art_map.keys():
64
+ if str_id in self.comp_to_art_map[comp_id]:
65
+ self.comp_to_art_map[comp_id].remove(str_id)
66
+ else:
67
+ if str_id in self.comp_to_art_map:
68
+ del self.comp_to_art_map[str_id]
69
+ for art_id in self.art_to_comp_map.keys():
70
+ if str_id in self.art_to_comp_map[art_id]:
71
+ self.art_to_comp_map[art_id].remove(str_id)
72
+
73
+ def get_arts_for_component(self, comp_id : str) -> list[str]:
74
+ """
75
+ Return linked artifacts for a model component
76
+ """
77
+ if comp_id in self.comp_to_art_map:
78
+ return self.comp_to_art_map[comp_id]
79
+ return []
80
+
81
+ def get_comp_for_artifact(self, art_id : str) -> list[str]:
82
+ """
83
+ Return linked model components for an artifact
84
+ """
85
+
86
+ if art_id in self.art_to_comp_map:
87
+ return self.art_to_comp_map[art_id]
88
+ return []
@@ -0,0 +1,24 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/data/refs.py
5
+ #
6
+
7
+ from .artifact import DataArtifact
8
+
9
+ class DocRef(DataArtifact):
10
+ """
11
+ Documentation reference
12
+ """
13
+ text : str # Actual documentation text
14
+ meta : str | None = None # Any additional information
15
+
16
+ class SrcCodeRef(DataArtifact):
17
+ """
18
+ Source code reference
19
+ """
20
+ src_code : str # The original source code
21
+ language : str | None = None # Programming language of the source code
22
+ file_path : str | None = None # File path of the source file
23
+ iml_code : str | None = None # Formalized version of the source code in IML
24
+ meta : str | None = None # Any additional information about the source code reference
@@ -0,0 +1,26 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/data/trace.py
5
+ #
6
+
7
+ from .artifact import TraceArtifact
8
+
9
+ class TestTrace (TraceArtifact):
10
+ """
11
+ Test trace formalization
12
+ """
13
+ __test__ = False # this will let pytest know it's not something we should attempt to test
14
+
15
+ name : str # Test case name
16
+ filepath : str = "" # Original test filepath
17
+ language : str | None = None # Language of the original source code
18
+ contents : str | None = None # Original test contents
19
+
20
+ class LogTrace(TraceArtifact):
21
+ """
22
+ Log tace formalization
23
+ """
24
+
25
+ filename : str #
26
+ contents : str # Contents of the log entry
Binary file