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.
- speclogician/agent/funcs.py +29 -0
- speclogician/cmd/agent_cmd.py +89 -0
- speclogician/cmd/data_cmd.py +24 -0
- speclogician/cmd/model_cmd.py +42 -0
- speclogician/cmd/overlay_cmd.py +30 -0
- speclogician/cmd/scenario_cmd.py +61 -0
- speclogician/cmd/state_cmd.py +52 -0
- speclogician/data/artifact.py +8 -50
- speclogician/data/container.py +18 -384
- speclogician/data/mapping.py +18 -17
- speclogician/data/refs.py +12 -11
- speclogician/data/reports.py +11 -0
- speclogician/data/traces.py +15 -6
- speclogician/llms/llmtools.py +102 -0
- speclogician/llms/overlay.py +264 -0
- speclogician/main.py +36 -102
- speclogician/modeling/__init__.py +0 -31
- speclogician/modeling/component.py +4 -60
- speclogician/modeling/conflict.py +5 -19
- speclogician/modeling/domain.py +93 -280
- speclogician/modeling/model.py +206 -0
- speclogician/modeling/predicates.py +20 -22
- speclogician/modeling/report.py +33 -0
- speclogician/modeling/scenario.py +119 -87
- speclogician/sl_cmd.py +76 -0
- speclogician/state/change.py +98 -378
- speclogician/state/state.py +183 -399
- speclogician/tui/box.tcss +10 -0
- speclogician/tui/tui.py +131 -0
- speclogician/utils/__init__.py +1 -70
- speclogician/utils/imx.py +195 -0
- speclogician/utils/load.py +25 -147
- speclogician/utils/prompt.md +1 -325
- speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
- speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
- speclogician/commands/__init__.py +0 -15
- speclogician/commands/cmd_ch.py +0 -616
- speclogician/commands/cmd_find.py +0 -256
- speclogician/commands/cmd_view.py +0 -202
- speclogician/commands/runner.py +0 -149
- speclogician/commands/utils.py +0 -101
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +0 -278
- speclogician/demos/loader.py +0 -135
- speclogician/demos/model.py +0 -27
- speclogician/demos/runner.py +0 -51
- speclogician/logic/__init__.py +0 -11
- speclogician/logic/api/__init__.py +0 -29
- speclogician/logic/api/client.py +0 -606
- speclogician/logic/api/decomp.py +0 -67
- speclogician/logic/api/scenario.py +0 -102
- speclogician/logic/api/traces.py +0 -59
- speclogician/logic/lib/__init__.py +0 -19
- speclogician/logic/lib/complement.py +0 -107
- speclogician/logic/lib/domain_model.py +0 -59
- speclogician/logic/lib/predicates.py +0 -151
- speclogician/logic/lib/scenarios.py +0 -369
- speclogician/logic/lib/traces.py +0 -114
- speclogician/logic/lib/transitions.py +0 -104
- speclogician/logic/main.py +0 -246
- speclogician/logic/strings.py +0 -194
- speclogician/logic/utils.py +0 -135
- speclogician/modeling/complement.py +0 -104
- speclogician/modeling/spec.py +0 -306
- speclogician/modeling/spec_stats.py +0 -39
- speclogician/presentation/api.py +0 -244
- speclogician/presentation/builders/_links.py +0 -44
- speclogician/presentation/builders/container.py +0 -53
- speclogician/presentation/builders/data_artifact.py +0 -42
- speclogician/presentation/builders/domain.py +0 -54
- speclogician/presentation/builders/instances_list.py +0 -38
- speclogician/presentation/builders/predicate.py +0 -51
- speclogician/presentation/builders/recommendations.py +0 -41
- speclogician/presentation/builders/scenario.py +0 -41
- speclogician/presentation/builders/scenario_complement.py +0 -82
- speclogician/presentation/builders/smart_find.py +0 -39
- speclogician/presentation/builders/spec.py +0 -39
- speclogician/presentation/builders/state_diff.py +0 -150
- speclogician/presentation/builders/state_instance.py +0 -42
- speclogician/presentation/builders/state_instance_summary.py +0 -84
- speclogician/presentation/builders/trace.py +0 -58
- speclogician/presentation/ctx.py +0 -38
- speclogician/presentation/models/container.py +0 -44
- speclogician/presentation/models/data_artifact.py +0 -33
- speclogician/presentation/models/domain.py +0 -50
- speclogician/presentation/models/instances_list.py +0 -23
- speclogician/presentation/models/predicate.py +0 -60
- speclogician/presentation/models/recommendations.py +0 -34
- speclogician/presentation/models/scenario.py +0 -31
- speclogician/presentation/models/scenario_complement.py +0 -40
- speclogician/presentation/models/smart_find.py +0 -34
- speclogician/presentation/models/spec.py +0 -32
- speclogician/presentation/models/state_diff.py +0 -34
- speclogician/presentation/models/state_instance.py +0 -31
- speclogician/presentation/models/state_instance_summary.py +0 -102
- speclogician/presentation/models/trace.py +0 -42
- speclogician/presentation/preview/__init__.py +0 -13
- speclogician/presentation/preview/cli.py +0 -50
- speclogician/presentation/preview/fixtures/__init__.py +0 -205
- speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
- speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
- speclogician/presentation/preview/fixtures/domain_model.py +0 -162
- speclogician/presentation/preview/fixtures/instances_list.py +0 -162
- speclogician/presentation/preview/fixtures/predicate.py +0 -184
- speclogician/presentation/preview/fixtures/scenario.py +0 -84
- speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
- speclogician/presentation/preview/fixtures/smart_find.py +0 -140
- speclogician/presentation/preview/fixtures/spec.py +0 -95
- speclogician/presentation/preview/fixtures/state_diff.py +0 -158
- speclogician/presentation/preview/fixtures/state_instance.py +0 -128
- speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
- speclogician/presentation/preview/fixtures/trace.py +0 -206
- speclogician/presentation/preview/registry.py +0 -42
- speclogician/presentation/renderers/__init__.py +0 -24
- speclogician/presentation/renderers/container.py +0 -136
- speclogician/presentation/renderers/data_artifact.py +0 -144
- speclogician/presentation/renderers/domain.py +0 -123
- speclogician/presentation/renderers/instances_list.py +0 -120
- speclogician/presentation/renderers/predicate.py +0 -180
- speclogician/presentation/renderers/recommendations.py +0 -90
- speclogician/presentation/renderers/scenario.py +0 -94
- speclogician/presentation/renderers/scenario_complement.py +0 -59
- speclogician/presentation/renderers/smart_find.py +0 -307
- speclogician/presentation/renderers/spec.py +0 -105
- speclogician/presentation/renderers/state_diff.py +0 -102
- speclogician/presentation/renderers/state_instance.py +0 -82
- speclogician/presentation/renderers/state_instance_summary.py +0 -143
- speclogician/presentation/renderers/trace.py +0 -122
- speclogician/shell/app.py +0 -170
- speclogician/shell/shell_ch.py +0 -263
- speclogician/shell/shell_view.py +0 -153
- speclogician/state/change_result.py +0 -32
- speclogician/state/diff.py +0 -191
- speclogician/state/inst.py +0 -574
- speclogician/state/recommendation.py +0 -13
- speclogician/state/recommender.py +0 -577
- speclogician/state/state_stats.py +0 -133
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +0 -257
- speclogician/tui/app.tcss +0 -160
- speclogician/tui/demo.py +0 -45
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +0 -454
- speclogician/tui/splash_screen.py +0 -51
- speclogician/tui/stats_screen.py +0 -125
- speclogician/utils/testing.py +0 -151
- speclogician-0.0.0b1.dist-info/METADATA +0 -116
- speclogician-0.0.0b1.dist-info/RECORD +0 -139
- /speclogician/{presentation → agent}/__init__.py +0 -0
- /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
- /speclogician/{presentation/models → llms}/__init__.py +0 -0
- {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
speclogician/data/container.py
CHANGED
|
@@ -1,402 +1,36 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# container.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
from
|
|
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
|
-
"""
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
33
|
+
def add(self):
|
|
34
|
+
""" """
|
|
35
|
+
pass
|
|
36
|
+
|
speclogician/data/mapping.py
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# mapping.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
from pydantic import BaseModel
|
|
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:
|
|
22
|
-
comp_to_art_map:
|
|
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,
|
|
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
|
|
32
|
-
self.art_to_comp_map[art_id].append(
|
|
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
|
|
35
|
-
self.comp_to_art_map[
|
|
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[
|
|
38
|
-
self.comp_to_art_map[
|
|
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,
|
|
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
|
|
47
|
-
self.art_to_comp_map[artifact_id].remove(
|
|
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
|
|
52
|
-
if artifact_id in self.comp_to_art_map[
|
|
53
|
-
self.comp_to_art_map[
|
|
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
|
-
#
|
|
4
|
+
# refs.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
from .artifact import
|
|
7
|
+
from .artifact import Artifact
|
|
8
8
|
|
|
9
|
-
class DocRef(
|
|
9
|
+
class DocRef(Artifact):
|
|
10
10
|
"""
|
|
11
11
|
Documentation reference
|
|
12
12
|
"""
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
meta : str
|
|
14
|
+
text : str
|
|
15
15
|
|
|
16
|
-
class SrcCodeRef(
|
|
16
|
+
class SrcCodeRef(Artifact):
|
|
17
17
|
"""
|
|
18
18
|
Source code reference
|
|
19
19
|
"""
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
|
|
21
|
+
meta : str #
|
|
22
|
+
language : str #
|
|
23
|
+
file_path : str #
|
|
24
|
+
src_code : str #
|
|
25
|
+
iml_code : str #
|
speclogician/data/traces.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
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
|
|
17
|
-
language : str
|
|
18
|
-
contents : str
|
|
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
|
|
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))
|