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,418 +1,212 @@
1
1
  #
2
2
  # Imandra Inc.
3
3
  #
4
- # speclogician/state/state.py
4
+ # state.py
5
5
  #
6
6
 
7
7
  import os
8
8
  from pathlib import Path
9
- from pydantic import BaseModel, Field
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
- class State(BaseModel):
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
- SpecLogician state instance container
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
- def curr_state(self) -> StateInstance:
59
- """ """
60
- return self.instances[0]
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
- def inst_list(self):
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
- - json_only=False: returns object payload for rich printing / internal callers.
75
- - json_only=True : returns JSON-safe payload (summaries + json dumps).
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
- old_state: StateInstance = self.curr_state()
79
-
80
- # Create a new candidate instance (deterministic snapshot + change list)
81
- state_copy = old_state.model_copy(deep=True)
82
- state_copy.update_created_at()
83
- state_copy.changes = [change]
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
- # 1) Apply change to state
86
- try:
87
- new_state: StateInstance = self.run_change_command(state_copy, change)
88
- except Exception as e:
89
- # recommendations still useful on failure (triage)
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
- # 2) Perform logical analysis
110
- try:
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
- # 4) Commit (newest at index 0)
164
- self.instances.insert(0, new_state)
83
+ matched_stats = self.get_matched_stats()
165
84
 
166
- # 5) Diff (old -> new), only if we actually have a previous instance
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
- # 6) Compute recommendations (success path)
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
- if isinstance (change, DomainModelBaseEdit):
231
- s.spec.domain_model.set_base(change.new_base_src)
89
+
90
+ s['changes'] = ",\n".join(map (lambda x: x.short_str(), self.changes))
91
+
92
+ model_info = self.model.info()
232
93
 
233
- elif isinstance (change, PredicateAdd):
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
- s.spec.domain_model.add_predicate(change.pred_name, change.pred_type, change.pred_src)
96
+ # Predicates
97
+ s['predicates'] = f"{model_info.preds_total} ({model_info.preds_errored}/45%; {matched_stats['predicates_matched']}/55%)"
238
98
 
239
- elif isinstance (change, PredicateEdit):
240
- if not s.spec.domain_model.pred_exists(change.pred_name):
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
- s.spec.domain_model.edit_predicate(change.pred_name, change.pred_src)
102
+ art_info = self.art_container.info()
244
103
 
245
- elif isinstance (change, PredicateRemove):
246
- if not s.spec.domain_model.pred_exists(change.pred_name):
247
- raise ValueError (f"Predicate with name {change.pred_name} doesn't exist")
248
-
249
- s.spec.domain_model.rem_predicate(change.pred_name)
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
- elif isinstance (change, TransitionAdd):
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
- s.spec.domain_model.add_transition(name=change.trans_name, src_code=change.trans_src)
112
+ def short_summary(self):
113
+ """ """
114
+ return f'=> hello, again {1+1}'
256
115
 
257
- elif isinstance (change, TransitionEdit):
258
- if not s.spec.domain_model.trans_exists(change.trans_name):
259
- raise ValueError (f"Transition with this name {change.trans_name} doesn't exist")
260
- s.spec.domain_model.edit_transition(name=change.trans_name, src_code=change.trans_src)
116
+ def report(self) -> StateReport:
117
+ """
118
+ Generate a full report
119
+ """
261
120
 
262
- elif isinstance (change, TransitionRemove):
263
- if not s.spec.domain_model.trans_exists(change.trans_name):
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
- elif isinstance(change, ScenarioAdd):
269
- if s.spec.scenario_exists(change.scenario_name):
270
- raise ValueError(f"Scenario with this name {change.scenario_name} already exists")
124
+ return StateReport (
125
+ model_report=model_report,
126
+ artifact_report=artifact_report
127
+ )
271
128
 
272
- s.spec.add_scenario(
273
- change.scenario_name,
274
- change.given,
275
- change.when,
276
- change.then,
277
- )
129
+ class InstancesList(BaseModel):
130
+ """
131
+ """
132
+ states : list[StateInstance]
278
133
 
279
- elif isinstance (change, ScenarioEdit):
280
- if not s.spec.scenario_exists(change.scenario_name):
281
- raise ValueError (f"Scenario with the name '{change.scenario_name}' does not exist!")
282
-
283
- s.spec.edit_scenario(
284
- change.scenario_name,
285
- change.given,
286
- change.when, change.then
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
- elif isinstance (change, ScenarioRemove):
290
- if not s.spec.scenario_exists(change.scenario_name):
291
- raise ValueError (f"Scenario with the name '{change.scenario_name}' does not exist!")
292
-
293
- s.spec.rem_scenario(change.scenario_name)
294
-
295
- # Test Traces
296
-
297
- elif isinstance(change, AddTestTrace):
298
- tr = TestTrace(
299
- art_id=change.art_id,
300
- # TraceArtifact core
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
- elif isinstance(change, EditDocRef):
360
- # Patch-edit via container API (only update provided fields)
361
- patch: dict[str, object] = {}
362
- if change.text is not None:
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
- elif isinstance(change, EditSrcCodeRef):
384
- # Patch-edit via container API (only update provided fields)
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
- if patch:
398
- s.art_container.edit_data_art(change.art_id, patch)
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
- elif isinstance(change, RemoveDataArtifact):
401
- s.art_container.rem_data_art(change.art_id)
187
+ self.curr_state_idx = new_idx
402
188
 
403
- elif isinstance (change, LinkArtifactsComponents):
404
- s.art_map.add_connection(art_id=change.art_id, comp_name=change.comp_name)
189
+ def curr_state(self) -> StateInstance:
190
+ return self.instances[self.curr_state_idx]
405
191
 
406
- elif isinstance (change, RemoveArtComponentLink):
407
- s.art_map.rem_connection(artifact_id=change.art_id, comp_name=change.comp_name)
192
+ def inst_list(self):
193
+ """ """
194
+ return InstancesList(states=self.instances)
408
195
 
409
- else:
410
- raise Exception(f"Unrecognized state change: {type(change).__name__}")
196
+ def process_change(
197
+ self,
198
+ change : ModelChange,
199
+ cut_new_instance : bool = True
200
+ ):
201
+ """
202
+ Process change
203
+ """
411
204
 
412
- return s
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 : str | None | Path = None):
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 : str | Path):
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
- state.file_location = state_path
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__())
@@ -0,0 +1,10 @@
1
+ Screen {
2
+ layout: grid;
3
+ grid-size: 2;
4
+ grid-columns: 1fr 2fr;
5
+ }
6
+
7
+ .box {
8
+ height: 100%;
9
+ border: solid green;
10
+ }