speclogician 0.0.0b1__py3-none-any.whl → 0.0.0.dev1__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 (153) hide show
  1. speclogician/agent/funcs.py +29 -0
  2. speclogician/cmd/agent_cmd.py +89 -0
  3. speclogician/cmd/data_cmd.py +24 -0
  4. speclogician/cmd/model_cmd.py +42 -0
  5. speclogician/cmd/overlay_cmd.py +30 -0
  6. speclogician/cmd/scenario_cmd.py +61 -0
  7. speclogician/cmd/state_cmd.py +52 -0
  8. speclogician/data/artifact.py +8 -50
  9. speclogician/data/container.py +18 -384
  10. speclogician/data/mapping.py +18 -17
  11. speclogician/data/refs.py +12 -11
  12. speclogician/data/reports.py +11 -0
  13. speclogician/data/traces.py +15 -6
  14. speclogician/llms/llmtools.py +102 -0
  15. speclogician/llms/overlay.py +264 -0
  16. speclogician/main.py +36 -102
  17. speclogician/modeling/__init__.py +0 -31
  18. speclogician/modeling/component.py +4 -60
  19. speclogician/modeling/conflict.py +5 -19
  20. speclogician/modeling/domain.py +93 -280
  21. speclogician/modeling/model.py +206 -0
  22. speclogician/modeling/predicates.py +20 -22
  23. speclogician/modeling/report.py +33 -0
  24. speclogician/modeling/scenario.py +119 -87
  25. speclogician/sl_cmd.py +76 -0
  26. speclogician/state/change.py +98 -378
  27. speclogician/state/state.py +183 -399
  28. speclogician/tui/box.tcss +10 -0
  29. speclogician/tui/tui.py +131 -0
  30. speclogician/utils/__init__.py +1 -70
  31. speclogician/utils/imx.py +195 -0
  32. speclogician/utils/load.py +25 -147
  33. speclogician/utils/prompt.md +1 -325
  34. speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
  35. speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
  36. speclogician/commands/__init__.py +0 -15
  37. speclogician/commands/cmd_ch.py +0 -616
  38. speclogician/commands/cmd_find.py +0 -256
  39. speclogician/commands/cmd_view.py +0 -202
  40. speclogician/commands/runner.py +0 -149
  41. speclogician/commands/utils.py +0 -101
  42. speclogician/demos/.DS_Store +0 -0
  43. speclogician/demos/cmd_demo.py +0 -278
  44. speclogician/demos/loader.py +0 -135
  45. speclogician/demos/model.py +0 -27
  46. speclogician/demos/runner.py +0 -51
  47. speclogician/logic/__init__.py +0 -11
  48. speclogician/logic/api/__init__.py +0 -29
  49. speclogician/logic/api/client.py +0 -606
  50. speclogician/logic/api/decomp.py +0 -67
  51. speclogician/logic/api/scenario.py +0 -102
  52. speclogician/logic/api/traces.py +0 -59
  53. speclogician/logic/lib/__init__.py +0 -19
  54. speclogician/logic/lib/complement.py +0 -107
  55. speclogician/logic/lib/domain_model.py +0 -59
  56. speclogician/logic/lib/predicates.py +0 -151
  57. speclogician/logic/lib/scenarios.py +0 -369
  58. speclogician/logic/lib/traces.py +0 -114
  59. speclogician/logic/lib/transitions.py +0 -104
  60. speclogician/logic/main.py +0 -246
  61. speclogician/logic/strings.py +0 -194
  62. speclogician/logic/utils.py +0 -135
  63. speclogician/modeling/complement.py +0 -104
  64. speclogician/modeling/spec.py +0 -306
  65. speclogician/modeling/spec_stats.py +0 -39
  66. speclogician/presentation/api.py +0 -244
  67. speclogician/presentation/builders/_links.py +0 -44
  68. speclogician/presentation/builders/container.py +0 -53
  69. speclogician/presentation/builders/data_artifact.py +0 -42
  70. speclogician/presentation/builders/domain.py +0 -54
  71. speclogician/presentation/builders/instances_list.py +0 -38
  72. speclogician/presentation/builders/predicate.py +0 -51
  73. speclogician/presentation/builders/recommendations.py +0 -41
  74. speclogician/presentation/builders/scenario.py +0 -41
  75. speclogician/presentation/builders/scenario_complement.py +0 -82
  76. speclogician/presentation/builders/smart_find.py +0 -39
  77. speclogician/presentation/builders/spec.py +0 -39
  78. speclogician/presentation/builders/state_diff.py +0 -150
  79. speclogician/presentation/builders/state_instance.py +0 -42
  80. speclogician/presentation/builders/state_instance_summary.py +0 -84
  81. speclogician/presentation/builders/trace.py +0 -58
  82. speclogician/presentation/ctx.py +0 -38
  83. speclogician/presentation/models/container.py +0 -44
  84. speclogician/presentation/models/data_artifact.py +0 -33
  85. speclogician/presentation/models/domain.py +0 -50
  86. speclogician/presentation/models/instances_list.py +0 -23
  87. speclogician/presentation/models/predicate.py +0 -60
  88. speclogician/presentation/models/recommendations.py +0 -34
  89. speclogician/presentation/models/scenario.py +0 -31
  90. speclogician/presentation/models/scenario_complement.py +0 -40
  91. speclogician/presentation/models/smart_find.py +0 -34
  92. speclogician/presentation/models/spec.py +0 -32
  93. speclogician/presentation/models/state_diff.py +0 -34
  94. speclogician/presentation/models/state_instance.py +0 -31
  95. speclogician/presentation/models/state_instance_summary.py +0 -102
  96. speclogician/presentation/models/trace.py +0 -42
  97. speclogician/presentation/preview/__init__.py +0 -13
  98. speclogician/presentation/preview/cli.py +0 -50
  99. speclogician/presentation/preview/fixtures/__init__.py +0 -205
  100. speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
  101. speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
  102. speclogician/presentation/preview/fixtures/domain_model.py +0 -162
  103. speclogician/presentation/preview/fixtures/instances_list.py +0 -162
  104. speclogician/presentation/preview/fixtures/predicate.py +0 -184
  105. speclogician/presentation/preview/fixtures/scenario.py +0 -84
  106. speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
  107. speclogician/presentation/preview/fixtures/smart_find.py +0 -140
  108. speclogician/presentation/preview/fixtures/spec.py +0 -95
  109. speclogician/presentation/preview/fixtures/state_diff.py +0 -158
  110. speclogician/presentation/preview/fixtures/state_instance.py +0 -128
  111. speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
  112. speclogician/presentation/preview/fixtures/trace.py +0 -206
  113. speclogician/presentation/preview/registry.py +0 -42
  114. speclogician/presentation/renderers/__init__.py +0 -24
  115. speclogician/presentation/renderers/container.py +0 -136
  116. speclogician/presentation/renderers/data_artifact.py +0 -144
  117. speclogician/presentation/renderers/domain.py +0 -123
  118. speclogician/presentation/renderers/instances_list.py +0 -120
  119. speclogician/presentation/renderers/predicate.py +0 -180
  120. speclogician/presentation/renderers/recommendations.py +0 -90
  121. speclogician/presentation/renderers/scenario.py +0 -94
  122. speclogician/presentation/renderers/scenario_complement.py +0 -59
  123. speclogician/presentation/renderers/smart_find.py +0 -307
  124. speclogician/presentation/renderers/spec.py +0 -105
  125. speclogician/presentation/renderers/state_diff.py +0 -102
  126. speclogician/presentation/renderers/state_instance.py +0 -82
  127. speclogician/presentation/renderers/state_instance_summary.py +0 -143
  128. speclogician/presentation/renderers/trace.py +0 -122
  129. speclogician/shell/app.py +0 -170
  130. speclogician/shell/shell_ch.py +0 -263
  131. speclogician/shell/shell_view.py +0 -153
  132. speclogician/state/change_result.py +0 -32
  133. speclogician/state/diff.py +0 -191
  134. speclogician/state/inst.py +0 -574
  135. speclogician/state/recommendation.py +0 -13
  136. speclogician/state/recommender.py +0 -577
  137. speclogician/state/state_stats.py +0 -133
  138. speclogician/tui/__init__.py +0 -0
  139. speclogician/tui/app.py +0 -257
  140. speclogician/tui/app.tcss +0 -160
  141. speclogician/tui/demo.py +0 -45
  142. speclogician/tui/images/speclogician-full.png +0 -0
  143. speclogician/tui/images/speclogician-minimal.png +0 -0
  144. speclogician/tui/main_screen.py +0 -454
  145. speclogician/tui/splash_screen.py +0 -51
  146. speclogician/tui/stats_screen.py +0 -125
  147. speclogician/utils/testing.py +0 -151
  148. speclogician-0.0.0b1.dist-info/METADATA +0 -116
  149. speclogician-0.0.0b1.dist-info/RECORD +0 -139
  150. /speclogician/{presentation → agent}/__init__.py +0 -0
  151. /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
  152. /speclogician/{presentation/models → llms}/__init__.py +0 -0
  153. {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
@@ -1,402 +1,36 @@
1
1
  #
2
2
  # Imandra Inc.
3
3
  #
4
- # speclogician/data/container.py
4
+ # container.py
5
5
  #
6
6
 
7
- from typing import Any
8
- from pydantic import BaseModel, Field
7
+ from pydantic import BaseModel
9
8
 
10
9
  from .traces import TestTrace, LogTrace
11
10
  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
11
 
12
+ class ArtifactSummaryInfo(BaseModel):
13
+ """ """
14
+ pass
28
15
 
29
16
  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__}")
17
+ """
18
+ Artifact container
19
+ """
167
20
 
168
- self.refresh_stats()
21
+ test_traces : list[TestTrace] = []
22
+ log_traces : list[LogTrace] = []
23
+ doc_ref : list[DocRef] = []
24
+ src_code : list[SrcCodeRef] = []
169
25
 
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
26
 
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:
27
+ def info(self):
319
28
  """
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
29
  """
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
30
 
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)
31
+ return {'hello': 123}
401
32
 
402
- return out
33
+ def add(self):
34
+ """ """
35
+ pass
36
+
@@ -1,10 +1,11 @@
1
1
  #
2
2
  # Imandra Inc.
3
3
  #
4
- # speclogician/data/mapping.py
4
+ # mapping.py
5
5
  #
6
6
 
7
- from pydantic import BaseModel, Field
7
+ from pydantic import BaseModel
8
+ from typing import Dict
8
9
 
9
10
  from enum import StrEnum
10
11
 
@@ -18,39 +19,39 @@ class ArtifactMap(BaseModel):
18
19
  handle potentially large datasets (e.g. massive logs)
19
20
  """
20
21
 
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)
22
+ art_to_comp_map : Dict[str, list[str]] = {}
23
+ comp_to_art_map : Dict[str, list[str]] = {}
23
24
 
24
- def add_connection (self, art_id : str, comp_name : str):
25
+ def add_connection (self, art_id : str, comp_id : str):
25
26
  """
26
27
  Add a link between artifact and model component
27
28
  """
28
29
  if art_id not in self.art_to_comp_map:
29
30
  self.art_to_comp_map[art_id] = []
30
31
 
31
- if comp_name not in self.art_to_comp_map[art_id]:
32
- self.art_to_comp_map[art_id].append(comp_name)
32
+ if comp_id not in self.art_to_comp_map[art_id]:
33
+ self.art_to_comp_map[art_id].append(comp_id)
33
34
 
34
- if comp_name not in self.comp_to_art_map:
35
- self.comp_to_art_map[comp_name] = []
35
+ if comp_id not in self.comp_to_art_map:
36
+ self.comp_to_art_map[comp_id] = []
36
37
 
37
- if art_id not in self.comp_to_art_map[comp_name]:
38
- self.comp_to_art_map[comp_name].append(art_id)
38
+ if art_id not in self.comp_to_art_map[comp_id]:
39
+ self.comp_to_art_map[comp_id].append(art_id)
39
40
 
40
- def rem_connection (self, artifact_id : str, comp_name : str):
41
+ def rem_connection (self, artifact_id : str, comp_id : str):
41
42
  """
42
43
  Remove a connection
43
44
  """
44
45
 
45
46
  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)
47
+ if comp_id in self.art_to_comp_map[artifact_id]:
48
+ self.art_to_comp_map[artifact_id].remove(comp_id)
48
49
  else:
49
50
  return False
50
51
 
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)
52
+ if comp_id in self.comp_to_art_map:
53
+ if artifact_id in self.comp_to_art_map[comp_id]:
54
+ self.comp_to_art_map[comp_id].remove(artifact_id)
54
55
 
55
56
  def rem_element (self, elem_type : ElementType, str_id : str):
56
57
  """
speclogician/data/refs.py CHANGED
@@ -1,24 +1,25 @@
1
1
  #
2
2
  # Imandra Inc.
3
3
  #
4
- # speclogician/data/refs.py
4
+ # refs.py
5
5
  #
6
6
 
7
- from .artifact import DataArtifact
7
+ from .artifact import Artifact
8
8
 
9
- class DocRef(DataArtifact):
9
+ class DocRef(Artifact):
10
10
  """
11
11
  Documentation reference
12
12
  """
13
- text : str # Actual documentation text
14
- meta : str | None = None # Any additional information
13
+ meta : str
14
+ text : str
15
15
 
16
- class SrcCodeRef(DataArtifact):
16
+ class SrcCodeRef(Artifact):
17
17
  """
18
18
  Source code reference
19
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
20
+
21
+ meta : str #
22
+ language : str #
23
+ file_path : str #
24
+ src_code : str #
25
+ iml_code : str #
@@ -0,0 +1,11 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # reports.py
5
+ #
6
+
7
+ from pydantic import BaseModel
8
+
9
+
10
+ class TestFormalization(BaseModel):
11
+ pass
@@ -1,7 +1,7 @@
1
1
  #
2
2
  # Imandra Inc.
3
3
  #
4
- # speclogician/data/trace.py
4
+ # trace.py
5
5
  #
6
6
 
7
7
  from .artifact import TraceArtifact
@@ -10,12 +10,14 @@ class TestTrace (TraceArtifact):
10
10
  """
11
11
  Test trace formalization
12
12
  """
13
- __test__ = False # this will let pytest know it's not something we should attempt to test
14
13
 
15
14
  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
15
+ filepath : str # Original test filepath
16
+ language : str # Language of the original source code
17
+ contents : str # Original test contents
18
+ time : str # Time when this done
19
+
20
+ # Contain
19
21
 
20
22
  class LogTrace(TraceArtifact):
21
23
  """
@@ -23,4 +25,11 @@ class LogTrace(TraceArtifact):
23
25
  """
24
26
 
25
27
  filename : str #
26
- contents : str # Contents of the log entry
28
+ contents : str # Contents of the log entry
29
+
30
+ # Formalized entry
31
+ given : str
32
+ when : str
33
+ then : str
34
+
35
+
@@ -0,0 +1,102 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # llmtools.py
5
+ #
6
+
7
+ import datetime, os, dotenv, sys, yaml, typer
8
+ from typing import Optional
9
+ from pathlib import Path
10
+ from ..data.reports import TestFormalization
11
+ from llms.overlay import Overlay
12
+
13
+ #from langchain_anthropic import ChatAnthropic
14
+ dotenv.load_dotenv("../.env")
15
+
16
+ llm = None
17
+
18
+ #ChatAnthropic (
19
+ # model_name="claude-sonnet-4-20250514",
20
+ # api_key=os.environ["ANTHROPIC_API_KEY"],
21
+ #)
22
+
23
+
24
+ def test_formalizer (
25
+ filepath : str,
26
+ overlay : Overlay,
27
+ domain_model : Optional[str] = "N/A",
28
+ ) -> TestFormalization:
29
+ """
30
+ Returns a tuple of the logic and the type model
31
+ """
32
+
33
+ test_case = Path(filepath).read_text()
34
+ try:
35
+ generic_prompt = Path("../prompts/generic.md").read_text()
36
+ except Exception as e:
37
+ print(f"Failed to load in the generic prompt: {e}")
38
+ sys.exit(0)
39
+
40
+ prompt = f"""
41
+ {generic_prompt}
42
+
43
+ {overlay}
44
+
45
+ Domain model:
46
+ ----
47
+ {domain_model}
48
+ ----
49
+
50
+ Test case:
51
+ ----
52
+ {test_case}
53
+ ----
54
+ """
55
+
56
+ structured_model = llm.with_structured_output(FormalizationResponse)
57
+
58
+ # Let's call the LLM
59
+ try:
60
+ response : FormalizationResponse = structured_model.invoke(prompt)
61
+ except Exception as e:
62
+ typer.secho(f"Failed to make the LLM call: {e}", err=True)
63
+ sys.exit(0)
64
+
65
+ tf = TestFormalization(
66
+ name = response.test_name_str,
67
+ language = overlay.language,
68
+ filepath = filepath,
69
+ contents = test_case,
70
+ time = str(datetime.datetime.now()),
71
+ scenarios = response.scenarios,
72
+ domain_model = response.domain_model,
73
+ )
74
+
75
+ return tf
76
+
77
+ def retry_test_formalization (
78
+ tf : TestFormalization,
79
+ model_response : str,
80
+ ) -> TestFormalization:
81
+ """
82
+ Make the call again to
83
+ """
84
+
85
+ return tf
86
+
87
+ if __name__ == "__main__":
88
+
89
+ base_dir = "../data/gherkin"
90
+ paths = os.listdir(base_dir)
91
+ overlay = Overlay.from_file("../overlays/gherkin.yaml")
92
+ domain_model : str = "N/A"
93
+
94
+ responses : list[LLMResponse] = []
95
+
96
+ for path in paths:
97
+ r = test_formalizer(os.path.join(base_dir, paths[0]), overlay, domain_model)
98
+ responses.append(r)
99
+ domain_model = r.domain_model
100
+
101
+ for r in responses:
102
+ print (yaml.dump(r, default_flow_style=False))