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.
- speclogician/__init__.py +0 -0
- speclogician/commands/__init__.py +15 -0
- speclogician/commands/cmd_ch.py +616 -0
- speclogician/commands/cmd_find.py +256 -0
- speclogician/commands/cmd_view.py +202 -0
- speclogician/commands/runner.py +149 -0
- speclogician/commands/utils.py +101 -0
- speclogician/data/__init__.py +0 -0
- speclogician/data/artifact.py +63 -0
- speclogician/data/container.py +402 -0
- speclogician/data/mapping.py +88 -0
- speclogician/data/refs.py +24 -0
- speclogician/data/traces.py +26 -0
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +278 -0
- speclogician/demos/loader.py +135 -0
- speclogician/demos/model.py +27 -0
- speclogician/demos/runner.py +51 -0
- speclogician/logic/__init__.py +11 -0
- speclogician/logic/api/__init__.py +29 -0
- speclogician/logic/api/client.py +606 -0
- speclogician/logic/api/decomp.py +67 -0
- speclogician/logic/api/scenario.py +102 -0
- speclogician/logic/api/traces.py +59 -0
- speclogician/logic/lib/__init__.py +19 -0
- speclogician/logic/lib/complement.py +107 -0
- speclogician/logic/lib/domain_model.py +59 -0
- speclogician/logic/lib/predicates.py +151 -0
- speclogician/logic/lib/scenarios.py +369 -0
- speclogician/logic/lib/traces.py +114 -0
- speclogician/logic/lib/transitions.py +104 -0
- speclogician/logic/main.py +246 -0
- speclogician/logic/strings.py +194 -0
- speclogician/logic/utils.py +135 -0
- speclogician/main.py +139 -0
- speclogician/modeling/__init__.py +31 -0
- speclogician/modeling/complement.py +104 -0
- speclogician/modeling/component.py +71 -0
- speclogician/modeling/conflict.py +26 -0
- speclogician/modeling/domain.py +349 -0
- speclogician/modeling/predicates.py +59 -0
- speclogician/modeling/scenario.py +162 -0
- speclogician/modeling/spec.py +306 -0
- speclogician/modeling/spec_stats.py +39 -0
- speclogician/presentation/__init__.py +0 -0
- speclogician/presentation/api.py +244 -0
- speclogician/presentation/builders/__init__.py +0 -0
- speclogician/presentation/builders/_links.py +44 -0
- speclogician/presentation/builders/container.py +53 -0
- speclogician/presentation/builders/data_artifact.py +42 -0
- speclogician/presentation/builders/domain.py +54 -0
- speclogician/presentation/builders/instances_list.py +38 -0
- speclogician/presentation/builders/predicate.py +51 -0
- speclogician/presentation/builders/recommendations.py +41 -0
- speclogician/presentation/builders/scenario.py +41 -0
- speclogician/presentation/builders/scenario_complement.py +82 -0
- speclogician/presentation/builders/smart_find.py +39 -0
- speclogician/presentation/builders/spec.py +39 -0
- speclogician/presentation/builders/state_diff.py +150 -0
- speclogician/presentation/builders/state_instance.py +42 -0
- speclogician/presentation/builders/state_instance_summary.py +84 -0
- speclogician/presentation/builders/trace.py +58 -0
- speclogician/presentation/ctx.py +38 -0
- speclogician/presentation/models/__init__.py +0 -0
- speclogician/presentation/models/container.py +44 -0
- speclogician/presentation/models/data_artifact.py +33 -0
- speclogician/presentation/models/domain.py +50 -0
- speclogician/presentation/models/instances_list.py +23 -0
- speclogician/presentation/models/predicate.py +60 -0
- speclogician/presentation/models/recommendations.py +34 -0
- speclogician/presentation/models/scenario.py +31 -0
- speclogician/presentation/models/scenario_complement.py +40 -0
- speclogician/presentation/models/smart_find.py +34 -0
- speclogician/presentation/models/spec.py +32 -0
- speclogician/presentation/models/state_diff.py +34 -0
- speclogician/presentation/models/state_instance.py +31 -0
- speclogician/presentation/models/state_instance_summary.py +102 -0
- speclogician/presentation/models/trace.py +42 -0
- speclogician/presentation/preview/__init__.py +13 -0
- speclogician/presentation/preview/cli.py +50 -0
- speclogician/presentation/preview/fixtures/__init__.py +205 -0
- speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
- speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
- speclogician/presentation/preview/fixtures/domain_model.py +162 -0
- speclogician/presentation/preview/fixtures/instances_list.py +162 -0
- speclogician/presentation/preview/fixtures/predicate.py +184 -0
- speclogician/presentation/preview/fixtures/scenario.py +84 -0
- speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
- speclogician/presentation/preview/fixtures/smart_find.py +140 -0
- speclogician/presentation/preview/fixtures/spec.py +95 -0
- speclogician/presentation/preview/fixtures/state_diff.py +158 -0
- speclogician/presentation/preview/fixtures/state_instance.py +128 -0
- speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
- speclogician/presentation/preview/fixtures/trace.py +206 -0
- speclogician/presentation/preview/registry.py +42 -0
- speclogician/presentation/renderers/__init__.py +24 -0
- speclogician/presentation/renderers/container.py +136 -0
- speclogician/presentation/renderers/data_artifact.py +144 -0
- speclogician/presentation/renderers/domain.py +123 -0
- speclogician/presentation/renderers/instances_list.py +120 -0
- speclogician/presentation/renderers/predicate.py +180 -0
- speclogician/presentation/renderers/recommendations.py +90 -0
- speclogician/presentation/renderers/scenario.py +94 -0
- speclogician/presentation/renderers/scenario_complement.py +59 -0
- speclogician/presentation/renderers/smart_find.py +307 -0
- speclogician/presentation/renderers/spec.py +105 -0
- speclogician/presentation/renderers/state_diff.py +102 -0
- speclogician/presentation/renderers/state_instance.py +82 -0
- speclogician/presentation/renderers/state_instance_summary.py +143 -0
- speclogician/presentation/renderers/trace.py +122 -0
- speclogician/py.typed +0 -0
- speclogician/shell/app.py +170 -0
- speclogician/shell/shell_ch.py +263 -0
- speclogician/shell/shell_view.py +153 -0
- speclogician/state/__init__.py +0 -0
- speclogician/state/change.py +428 -0
- speclogician/state/change_result.py +32 -0
- speclogician/state/diff.py +191 -0
- speclogician/state/inst.py +574 -0
- speclogician/state/recommendation.py +13 -0
- speclogician/state/recommender.py +577 -0
- speclogician/state/state.py +465 -0
- speclogician/state/state_stats.py +133 -0
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +257 -0
- speclogician/tui/app.tcss +160 -0
- speclogician/tui/demo.py +45 -0
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +454 -0
- speclogician/tui/splash_screen.py +51 -0
- speclogician/tui/stats_screen.py +125 -0
- speclogician/utils/__init__.py +78 -0
- speclogician/utils/load.py +166 -0
- speclogician/utils/prompt.md +325 -0
- speclogician/utils/testing.py +151 -0
- speclogician-0.0.0b1.dist-info/METADATA +116 -0
- speclogician-0.0.0b1.dist-info/RECORD +139 -0
- 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
|