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/state/state.py
CHANGED
|
@@ -1,418 +1,212 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# state.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
7
|
import os
|
|
8
8
|
from pathlib import Path
|
|
9
|
-
from pydantic import BaseModel
|
|
10
|
-
|
|
11
|
-
from ..modeling.spec_stats import refresh_spec_stats
|
|
12
|
-
from ..logic import analyze_change
|
|
13
|
-
from ..data.refs import DocRef, SrcCodeRef
|
|
14
|
-
from ..data.traces import TestTrace, LogTrace
|
|
15
|
-
from ..utils import console
|
|
16
|
-
from .inst import StateInstance, InstancesList, calc_diff
|
|
17
|
-
from .recommender import Recommender, Recommendation, ChangeFailure
|
|
18
|
-
|
|
19
|
-
from .change import (
|
|
20
|
-
StateChange,
|
|
21
|
-
DomainModelBaseEdit,
|
|
22
|
-
PredicateAdd,
|
|
23
|
-
PredicateEdit,
|
|
24
|
-
PredicateRemove,
|
|
25
|
-
TransitionAdd,
|
|
26
|
-
TransitionEdit,
|
|
27
|
-
TransitionRemove,
|
|
28
|
-
ScenarioAdd,
|
|
29
|
-
ScenarioEdit,
|
|
30
|
-
ScenarioRemove,
|
|
31
|
-
AddTestTrace,
|
|
32
|
-
EditTestTrace,
|
|
33
|
-
AddLogTrace,
|
|
34
|
-
EditLogTrace,
|
|
35
|
-
RemoveTraceArtifact,
|
|
36
|
-
AddDocRef,
|
|
37
|
-
EditDocRef,
|
|
38
|
-
AddSrcCodeRef,
|
|
39
|
-
EditSrcCodeRef,
|
|
40
|
-
RemoveDataArtifact,
|
|
41
|
-
LinkArtifactsComponents,
|
|
42
|
-
RemoveArtComponentLink
|
|
43
|
-
)
|
|
44
|
-
from .change_result import (
|
|
45
|
-
ProcessChangeResult,
|
|
46
|
-
ProcessChangeError,
|
|
47
|
-
ProcessChangeSuccess,
|
|
48
|
-
)
|
|
9
|
+
from pydantic import BaseModel
|
|
49
10
|
|
|
50
|
-
|
|
11
|
+
from rich.table import Table, Column
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
from speclogician.modeling.model import Model
|
|
15
|
+
from speclogician.data.mapping import ArtifactMap
|
|
16
|
+
from speclogician.data.container import ArtifactContainer
|
|
17
|
+
from speclogician.state.change import ModelChange
|
|
18
|
+
from speclogician.modeling.report import ArtifactReport
|
|
19
|
+
from speclogician.utils import console
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class StateReport(BaseModel):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class StateInstance(BaseModel):
|
|
51
28
|
"""
|
|
52
|
-
|
|
29
|
+
State Instance class
|
|
53
30
|
"""
|
|
54
|
-
file_location : str | None = Field(default=None)
|
|
55
|
-
instances : list[StateInstance] = [StateInstance()]
|
|
56
|
-
stash : list[StateInstance] = Field(default_factory=list[StateInstance]) # we'll use this to store discarded instances
|
|
57
31
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
32
|
+
state_idx : int
|
|
33
|
+
model : Model = Model() # Latest domain model
|
|
34
|
+
art_container : ArtifactContainer = ArtifactContainer() # This keeps us
|
|
35
|
+
art_map : ArtifactMap = ArtifactMap() # Artifact map
|
|
61
36
|
|
|
62
|
-
|
|
63
|
-
""" Return a InstancesList (for pretty printing) """
|
|
64
|
-
return InstancesList(states=self.instances)
|
|
65
|
-
|
|
66
|
-
def process_change(
|
|
67
|
-
self,
|
|
68
|
-
change: StateChange,
|
|
69
|
-
json_only: bool = False,
|
|
70
|
-
) -> ProcessChangeResult:
|
|
71
|
-
"""
|
|
72
|
-
Apply a change and return a structured result.
|
|
37
|
+
changes : list[ModelChange] = []
|
|
73
38
|
|
|
74
|
-
|
|
75
|
-
|
|
39
|
+
def get_matched_stats (self) -> dict[str, int]:
|
|
40
|
+
"""
|
|
41
|
+
State has the info about mappings from data artifacts to model components and vice versa.
|
|
42
|
+
We need to compute the stats here
|
|
76
43
|
"""
|
|
77
44
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
45
|
+
stats : dict[str, int] = {}
|
|
46
|
+
stats['scenarios_matched'] = 0
|
|
47
|
+
stats['predicates_matched'] = 0
|
|
48
|
+
stats['test_traces_matched'] = 0
|
|
49
|
+
stats['log_traces_matched'] = 0
|
|
50
|
+
stats['src_code_arts_matched'] = 0
|
|
51
|
+
stats['doc_arts_matched'] = 0
|
|
52
|
+
|
|
53
|
+
for s in self.model.scenarios:
|
|
54
|
+
if s.comp_id in self.art_map.comp_to_art_map:
|
|
55
|
+
stats['scenarios_matched'] += 1
|
|
56
|
+
|
|
57
|
+
for p in self.model.domain_model.action_preds + self.model.domain_model.state_preds:
|
|
58
|
+
if p.comp_id in self.art_map.comp_to_art_map:
|
|
59
|
+
stats['predicates_matched'] += 1
|
|
60
|
+
|
|
61
|
+
for tt in self.art_container.test_traces:
|
|
62
|
+
if tt.art_id in self.art_map.art_to_comp_map:
|
|
63
|
+
stats['test_traces_matched'] += 1
|
|
64
|
+
|
|
65
|
+
for lt in self.art_container.log_traces:
|
|
66
|
+
if lt.art_id in self.art_map.art_to_comp_map:
|
|
67
|
+
stats['log_traces_matched'] += 1
|
|
68
|
+
|
|
69
|
+
for sa in self.art_container.src_code:
|
|
70
|
+
if sa.art_id in self.art_map.art_to_comp_map:
|
|
71
|
+
stats['src_code_arts_matched'] += 1
|
|
84
72
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
failure = ChangeFailure(
|
|
91
|
-
change=change.__class__.__name__,
|
|
92
|
-
stage="apply_change",
|
|
93
|
-
error_type=type(e).__name__,
|
|
94
|
-
error=str(e),
|
|
95
|
-
)
|
|
96
|
-
# NOTE: no new_state exists; we recommend against old_state
|
|
97
|
-
recs = Recommender().recommend(old_state, old_state, failure=failure)
|
|
98
|
-
|
|
99
|
-
return ProcessChangeError(
|
|
100
|
-
ok=False,
|
|
101
|
-
stage="apply_change",
|
|
102
|
-
error=e,
|
|
103
|
-
change=change,
|
|
104
|
-
old_state=old_state,
|
|
105
|
-
# If your ProcessChangeError supports it, include recs:
|
|
106
|
-
# recommendations=recs,
|
|
107
|
-
)
|
|
73
|
+
for dr in self.art_container.doc_ref:
|
|
74
|
+
if dr.art_id in self.art_map.art_to_comp_map:
|
|
75
|
+
stats['doc_arts_matched'] += 1
|
|
76
|
+
|
|
77
|
+
return stats
|
|
108
78
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
analyze_change(
|
|
112
|
-
spec=new_state.spec,
|
|
113
|
-
art_map=new_state.art_map,
|
|
114
|
-
art_cont=new_state.art_container,
|
|
115
|
-
change=change,
|
|
116
|
-
json_only=json_only,
|
|
117
|
-
)
|
|
118
|
-
except Exception as e:
|
|
119
|
-
failure = ChangeFailure(
|
|
120
|
-
change=change.__class__.__name__,
|
|
121
|
-
stage="analyze_change",
|
|
122
|
-
error_type=type(e).__name__,
|
|
123
|
-
error=str(e),
|
|
124
|
-
)
|
|
125
|
-
recs = Recommender().recommend(old_state, new_state, failure=failure)
|
|
126
|
-
# Optionally store on the candidate for debugging:
|
|
127
|
-
new_state.recommendations = recs
|
|
128
|
-
|
|
129
|
-
return ProcessChangeError(
|
|
130
|
-
ok=False,
|
|
131
|
-
stage="analyze_change",
|
|
132
|
-
error=e,
|
|
133
|
-
change=change,
|
|
134
|
-
old_state=old_state,
|
|
135
|
-
# If supported:
|
|
136
|
-
# new_state=new_state,
|
|
137
|
-
# recommendations=recs,
|
|
138
|
-
)
|
|
79
|
+
def summary(self) -> dict[str,int|str]:
|
|
80
|
+
""" Return a dict with various StateInstance statistics """
|
|
139
81
|
|
|
140
|
-
# 3) Update stats (should not crash the whole pipeline ideally, but keeping your behavior)
|
|
141
|
-
try:
|
|
142
|
-
new_state.art_container.refresh_stats(new_state.art_map)
|
|
143
|
-
refresh_spec_stats(new_state.spec, art_map=new_state.art_map)
|
|
144
|
-
except Exception as e:
|
|
145
|
-
failure = ChangeFailure(
|
|
146
|
-
change=change.__class__.__name__,
|
|
147
|
-
stage="refresh_stats",
|
|
148
|
-
error_type=type(e).__name__,
|
|
149
|
-
error=str(e),
|
|
150
|
-
)
|
|
151
|
-
recs = Recommender().recommend(old_state, new_state, failure=failure)
|
|
152
|
-
new_state.recommendations = recs
|
|
153
|
-
|
|
154
|
-
return ProcessChangeError(
|
|
155
|
-
ok=False,
|
|
156
|
-
stage="refresh_stats",
|
|
157
|
-
error=e,
|
|
158
|
-
change=change,
|
|
159
|
-
old_state=old_state,
|
|
160
|
-
# new_state=new_state,
|
|
161
|
-
)
|
|
162
82
|
|
|
163
|
-
|
|
164
|
-
self.instances.insert(0, new_state)
|
|
83
|
+
matched_stats = self.get_matched_stats()
|
|
165
84
|
|
|
166
|
-
|
|
167
|
-
try:
|
|
168
|
-
if len(self.instances) >= 2:
|
|
169
|
-
# old = self.instances[1], new = self.instances[0]
|
|
170
|
-
self.instances[0].state_diff = calc_diff(self.instances[1], self.instances[0])
|
|
171
|
-
else:
|
|
172
|
-
self.instances[0].state_diff = None
|
|
173
|
-
except Exception as e:
|
|
174
|
-
failure = ChangeFailure(
|
|
175
|
-
change=change.__class__.__name__,
|
|
176
|
-
stage="calc_diff",
|
|
177
|
-
error_type=type(e).__name__,
|
|
178
|
-
error=str(e),
|
|
179
|
-
)
|
|
180
|
-
# We *do* have a committed new state now
|
|
181
|
-
recs = Recommender().recommend(old_state, self.instances[0], failure=failure)
|
|
182
|
-
self.instances[0].recommendations = recs
|
|
183
|
-
|
|
184
|
-
return ProcessChangeError(
|
|
185
|
-
ok=False,
|
|
186
|
-
stage="calc_diff",
|
|
187
|
-
error=e,
|
|
188
|
-
change=change,
|
|
189
|
-
old_state=old_state,
|
|
190
|
-
# new_state=self.instances[0],
|
|
191
|
-
)
|
|
85
|
+
s : dict[str, int|str] = {}
|
|
192
86
|
|
|
193
|
-
|
|
194
|
-
try:
|
|
195
|
-
# old is previous instance if it exists; otherwise old_state (same as new baseline)
|
|
196
|
-
prev = self.instances[1] if len(self.instances) >= 2 else self.instances[0]
|
|
197
|
-
self.instances[0].recommendations = Recommender().recommend(prev, self.instances[0])
|
|
198
|
-
except Exception as e:
|
|
199
|
-
# Recommender should never take down the CLI; but if it does, degrade gracefully.
|
|
200
|
-
self.instances[0].recommendations = [
|
|
201
|
-
Recommendation(
|
|
202
|
-
text=f"Recommender failed: {type(e).__name__}: {e}",
|
|
203
|
-
kind="warning",
|
|
204
|
-
priority=5,
|
|
205
|
-
)
|
|
206
|
-
]
|
|
207
|
-
|
|
208
|
-
# 7) Success
|
|
209
|
-
return ProcessChangeSuccess(
|
|
210
|
-
ok=True,
|
|
211
|
-
stage="ok",
|
|
212
|
-
change=change,
|
|
213
|
-
old_state=(self.instances[1] if len(self.instances) >= 2 else old_state),
|
|
214
|
-
new_state=self.instances[0],
|
|
215
|
-
state_diff=self.instances[0].state_diff,
|
|
216
|
-
new_num_instances=len(self.instances),
|
|
217
|
-
# If your success model includes it:
|
|
218
|
-
# recommendations=self.instances[0].recommendations,
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
def run_change_command (
|
|
222
|
-
self,
|
|
223
|
-
s : StateInstance,
|
|
224
|
-
change : StateChange,
|
|
225
|
-
) -> StateInstance:
|
|
226
|
-
"""
|
|
227
|
-
Process change
|
|
228
|
-
"""
|
|
87
|
+
s['idx'] = str(self.state_idx)
|
|
229
88
|
|
|
230
|
-
|
|
231
|
-
|
|
89
|
+
|
|
90
|
+
s['changes'] = ",\n".join(map (lambda x: x.short_str(), self.changes))
|
|
91
|
+
|
|
92
|
+
model_info = self.model.info()
|
|
232
93
|
|
|
233
|
-
|
|
234
|
-
if s.spec.domain_model.pred_exists(change.pred_name):
|
|
235
|
-
raise ValueError (f"Predicate with name {change.pred_name} already exists")
|
|
94
|
+
s['domain_status'] = f"{model_info.domain_iml_status}"
|
|
236
95
|
|
|
237
|
-
|
|
96
|
+
# Predicates
|
|
97
|
+
s['predicates'] = f"{model_info.preds_total} ({model_info.preds_errored}/45%; {matched_stats['predicates_matched']}/55%)"
|
|
238
98
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
raise ValueError (f"Predicate with name {change.pred_name} doesn't exist")
|
|
99
|
+
# Scenarios
|
|
100
|
+
s['scenarios'] = f"{model_info.scenarios_total} ({model_info.scenarios_errored}/45%; {matched_stats['scenarios_matched']}/55%)"
|
|
242
101
|
|
|
243
|
-
|
|
102
|
+
art_info = self.art_container.info()
|
|
244
103
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
104
|
+
# Test traces
|
|
105
|
+
s['test_data'] = "250 (122/45%; 100/55%)"
|
|
106
|
+
s['log_data'] = "250 (122/45%; 100/55%)"
|
|
107
|
+
s['src_code_arts'] = "1200 (450/500%)"
|
|
108
|
+
s['doc_arts'] = "1200 (230/45%)"
|
|
250
109
|
|
|
251
|
-
|
|
252
|
-
if s.spec.domain_model.trans_exists(change.trans_name):
|
|
253
|
-
raise ValueError (f"Transition with this name {change.trans_name} already exists!")
|
|
110
|
+
return s
|
|
254
111
|
|
|
255
|
-
|
|
112
|
+
def short_summary(self):
|
|
113
|
+
""" """
|
|
114
|
+
return f'=> hello, again {1+1}'
|
|
256
115
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
116
|
+
def report(self) -> StateReport:
|
|
117
|
+
"""
|
|
118
|
+
Generate a full report
|
|
119
|
+
"""
|
|
261
120
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
raise ValueError (f"Transition with this name {change.trans_name} doesn't exist")
|
|
265
|
-
|
|
266
|
-
s.spec.domain_model.rem_transition(change.trans_name)
|
|
121
|
+
model_report = self.model.report()
|
|
122
|
+
artifact_report = ArtifactReport()
|
|
267
123
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
124
|
+
return StateReport (
|
|
125
|
+
model_report=model_report,
|
|
126
|
+
artifact_report=artifact_report
|
|
127
|
+
)
|
|
271
128
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
change.then,
|
|
277
|
-
)
|
|
129
|
+
class InstancesList(BaseModel):
|
|
130
|
+
"""
|
|
131
|
+
"""
|
|
132
|
+
states : list[StateInstance]
|
|
278
133
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
)
|
|
134
|
+
def __rich__(self):
|
|
135
|
+
""" """
|
|
136
|
+
|
|
137
|
+
columns = [
|
|
138
|
+
Column(header='State ID', justify="center")
|
|
139
|
+
, Column(header='Changes', justify="center")
|
|
140
|
+
, Column(header='Domain model', justify="center")
|
|
141
|
+
, Column(header='Preds\n # (Errors, Used)', justify="center")
|
|
142
|
+
, Column(header='Scenarios\n # (Errors, Used)', justify="center")
|
|
143
|
+
, Column(header='Test traces\n # (Formalized, Matched)', justify="center")
|
|
144
|
+
, Column(header='Log traces\n # (Formalized, Matched)', justify="center")
|
|
145
|
+
, Column(header='Src code arts\n # (Matched)', justify="center")
|
|
146
|
+
, Column(header='Doc arts\n # (Matched)', justify="center")
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
t = Table (
|
|
150
|
+
*columns
|
|
151
|
+
, title="SpecLogician State List"
|
|
152
|
+
, expand = True
|
|
153
|
+
, highlight=True
|
|
154
|
+
#, padding=(0, 1) # Optional padding adjustments
|
|
155
|
+
, show_edge=False
|
|
156
|
+
#, show_lines=True
|
|
157
|
+
)
|
|
288
158
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
given=change.given,
|
|
302
|
-
when=change.when,
|
|
303
|
-
then=change.then,
|
|
304
|
-
time=change.time,
|
|
305
|
-
# TestTrace specific
|
|
306
|
-
name=change.name,
|
|
307
|
-
filepath=change.filepath,
|
|
308
|
-
language=change.language,
|
|
309
|
-
contents=change.contents,
|
|
310
|
-
)
|
|
311
|
-
s.art_container.add_trace(tr)
|
|
312
|
-
|
|
313
|
-
elif isinstance(change, EditTestTrace):
|
|
314
|
-
# Use model_fields_set so passing None explicitly can clear fields.
|
|
315
|
-
patch: dict[str, object] = {}
|
|
316
|
-
for f in change.model_fields_set:
|
|
317
|
-
if f == "art_id":
|
|
318
|
-
continue
|
|
319
|
-
patch[f] = getattr(change, f)
|
|
320
|
-
s.art_container.edit_trace(change.art_id, patch)
|
|
321
|
-
|
|
322
|
-
elif isinstance(change, AddLogTrace):
|
|
323
|
-
tr = LogTrace(
|
|
324
|
-
art_id=change.art_id,
|
|
325
|
-
# TraceArtifact core
|
|
326
|
-
given=change.given,
|
|
327
|
-
when=change.when,
|
|
328
|
-
then=change.then,
|
|
329
|
-
time=change.time,
|
|
330
|
-
# LogTrace specific
|
|
331
|
-
filename=change.filename,
|
|
332
|
-
contents=change.contents,
|
|
333
|
-
)
|
|
334
|
-
s.art_container.add_trace(tr)
|
|
335
|
-
|
|
336
|
-
elif isinstance(change, EditLogTrace):
|
|
337
|
-
patch: dict[str, object] = {}
|
|
338
|
-
for f in change.model_fields_set:
|
|
339
|
-
if f == "art_id":
|
|
340
|
-
continue
|
|
341
|
-
patch[f] = getattr(change, f)
|
|
342
|
-
s.art_container.edit_trace(change.art_id, patch)
|
|
343
|
-
|
|
344
|
-
elif isinstance(change, RemoveTraceArtifact):
|
|
345
|
-
s.art_container.rem_trace(change.art_id)
|
|
346
|
-
|
|
347
|
-
# --- Data artifact changes (DocRef / SrcCodeRef) -----------------------------
|
|
348
|
-
|
|
349
|
-
elif isinstance(change, AddDocRef):
|
|
350
|
-
# Build DocRef and add via container API
|
|
351
|
-
s.art_container.add_data_art(
|
|
352
|
-
DocRef(
|
|
353
|
-
art_id=change.art_id,
|
|
354
|
-
text=change.text,
|
|
355
|
-
meta=change.meta,
|
|
356
|
-
)
|
|
159
|
+
for s in self.states[::-1]:
|
|
160
|
+
ss = s.summary()
|
|
161
|
+
t.add_row(
|
|
162
|
+
Text(ss['idx']),
|
|
163
|
+
Text(ss['changes']),
|
|
164
|
+
Text(ss['domain_status']),
|
|
165
|
+
Text(ss['predicates']),
|
|
166
|
+
Text(ss['scenarios']),
|
|
167
|
+
Text(ss['test_data']),
|
|
168
|
+
Text(ss['log_data']),
|
|
169
|
+
Text(ss['src_code_arts']),
|
|
170
|
+
Text(ss['doc_arts'])
|
|
357
171
|
)
|
|
172
|
+
return t
|
|
358
173
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
patch["text"] = change.text
|
|
364
|
-
if change.meta is not None:
|
|
365
|
-
patch["meta"] = change.meta
|
|
366
|
-
|
|
367
|
-
if patch:
|
|
368
|
-
s.art_container.edit_data_art(change.art_id, patch)
|
|
369
|
-
|
|
370
|
-
elif isinstance(change, AddSrcCodeRef):
|
|
371
|
-
# Build SrcCodeRef and add via container API
|
|
372
|
-
s.art_container.add_data_art(
|
|
373
|
-
SrcCodeRef(
|
|
374
|
-
art_id=change.art_id,
|
|
375
|
-
src_code=change.src_code,
|
|
376
|
-
language=change.language,
|
|
377
|
-
file_path=change.file_path,
|
|
378
|
-
iml_code=change.iml_code,
|
|
379
|
-
meta=change.meta,
|
|
380
|
-
)
|
|
381
|
-
)
|
|
174
|
+
class State(BaseModel):
|
|
175
|
+
"""
|
|
176
|
+
TestLogician state (contains the various test formalizations)
|
|
177
|
+
"""
|
|
382
178
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
patch: dict[str, object] = {}
|
|
386
|
-
if change.src_code is not None:
|
|
387
|
-
patch["src_code"] = change.src_code
|
|
388
|
-
if change.language is not None:
|
|
389
|
-
patch["language"] = change.language
|
|
390
|
-
if change.file_path is not None:
|
|
391
|
-
patch["file_path"] = change.file_path
|
|
392
|
-
if change.iml_code is not None:
|
|
393
|
-
patch["iml_code"] = change.iml_code
|
|
394
|
-
if change.meta is not None:
|
|
395
|
-
patch["meta"] = change.meta
|
|
179
|
+
instances : list[StateInstance] = []
|
|
180
|
+
curr_state_idx : int = -1
|
|
396
181
|
|
|
397
|
-
|
|
398
|
-
|
|
182
|
+
def set_curr_state_idx(self, new_idx:int) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Set the state instance cursor to `new_idx`
|
|
185
|
+
"""
|
|
399
186
|
|
|
400
|
-
|
|
401
|
-
s.art_container.rem_data_art(change.art_id)
|
|
187
|
+
self.curr_state_idx = new_idx
|
|
402
188
|
|
|
403
|
-
|
|
404
|
-
|
|
189
|
+
def curr_state(self) -> StateInstance:
|
|
190
|
+
return self.instances[self.curr_state_idx]
|
|
405
191
|
|
|
406
|
-
|
|
407
|
-
|
|
192
|
+
def inst_list(self):
|
|
193
|
+
""" """
|
|
194
|
+
return InstancesList(states=self.instances)
|
|
408
195
|
|
|
409
|
-
|
|
410
|
-
|
|
196
|
+
def process_change(
|
|
197
|
+
self,
|
|
198
|
+
change : ModelChange,
|
|
199
|
+
cut_new_instance : bool = True
|
|
200
|
+
):
|
|
201
|
+
"""
|
|
202
|
+
Process change
|
|
203
|
+
"""
|
|
411
204
|
|
|
412
|
-
|
|
205
|
+
self.curr_state().model.proc_change(change)
|
|
413
206
|
|
|
414
207
|
def to_json(self):
|
|
415
|
-
"""
|
|
208
|
+
"""
|
|
209
|
+
"""
|
|
416
210
|
return self.model_dump_json()
|
|
417
211
|
|
|
418
212
|
@staticmethod
|
|
@@ -422,44 +216,34 @@ class State(BaseModel):
|
|
|
422
216
|
"""
|
|
423
217
|
return State.model_validate_json(j)
|
|
424
218
|
|
|
425
|
-
def save(self, dirpath
|
|
219
|
+
def save(self, dirpath:str):
|
|
426
220
|
"""
|
|
427
|
-
Save State to the specified directory
|
|
428
221
|
"""
|
|
429
|
-
|
|
430
|
-
if dirpath is None:
|
|
431
|
-
if self.file_location is None:
|
|
432
|
-
dirpath = os.getcwd()
|
|
433
|
-
filepath = os.path.join(dirpath, 'sl_state.json')
|
|
434
|
-
console.print(f"Saving the state in CWD: {dirpath}")
|
|
435
|
-
else:
|
|
436
|
-
filepath = self.file_location
|
|
437
|
-
else:
|
|
438
|
-
filepath = os.path.join(dirpath, 'sl_state.json')
|
|
439
|
-
|
|
440
|
-
with open(filepath, 'w') as outfile:
|
|
222
|
+
with open(os.path.join(dirpath, 'sl_state.json'), 'w') as outfile:
|
|
441
223
|
print(self.to_json(), file=outfile)
|
|
442
224
|
|
|
443
|
-
def save_to_path (self, path : Path) -> None:
|
|
444
|
-
""" Save State to a specific filepath """
|
|
445
|
-
try:
|
|
446
|
-
with open(path, 'w') as outfile:
|
|
447
|
-
print(self.to_json(), file=outfile)
|
|
448
|
-
except Exception as e:
|
|
449
|
-
raise ValueError(f"Failed to write state to disk [path = {path}]: {e}")
|
|
450
|
-
|
|
451
225
|
@staticmethod
|
|
452
|
-
def from_dir (dirpath
|
|
453
|
-
"""
|
|
454
|
-
Load State from specified directory
|
|
455
|
-
"""
|
|
226
|
+
def from_dir (dirpath:str):
|
|
456
227
|
try:
|
|
457
228
|
state_path = os.path.join(dirpath, "sl_state.json")
|
|
458
229
|
data = Path(state_path).read_text()
|
|
459
|
-
state = State.from_json(data)
|
|
460
|
-
|
|
461
|
-
except Exception as e:
|
|
462
|
-
console.print(f"Encountered error: {e}")
|
|
230
|
+
state = State.from_json(data)
|
|
231
|
+
except:
|
|
463
232
|
return None
|
|
464
233
|
|
|
465
234
|
return state
|
|
235
|
+
|
|
236
|
+
if __name__ == "__main__":
|
|
237
|
+
|
|
238
|
+
states = [
|
|
239
|
+
StateInstance(state_idx=1),
|
|
240
|
+
StateInstance(state_idx=2),
|
|
241
|
+
StateInstance(state_idx=3),
|
|
242
|
+
StateInstance(state_idx=4),
|
|
243
|
+
StateInstance(state_idx=5),
|
|
244
|
+
StateInstance(state_idx=6)
|
|
245
|
+
]
|
|
246
|
+
|
|
247
|
+
il = InstancesList(states=states)
|
|
248
|
+
|
|
249
|
+
console.print(il.__rich__())
|