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/tui/main_screen.py
DELETED
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/tui/main_screen.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from typing import Any
|
|
10
|
-
|
|
11
|
-
from rich.console import Group
|
|
12
|
-
from rich.panel import Panel
|
|
13
|
-
from rich.syntax import Syntax
|
|
14
|
-
from rich.text import Text
|
|
15
|
-
|
|
16
|
-
from textual.app import ComposeResult
|
|
17
|
-
from textual.containers import Container, Horizontal
|
|
18
|
-
from textual.screen import Screen
|
|
19
|
-
from textual.widgets import (
|
|
20
|
-
Footer,
|
|
21
|
-
Header,
|
|
22
|
-
ListItem,
|
|
23
|
-
ListView,
|
|
24
|
-
RichLog,
|
|
25
|
-
Static,
|
|
26
|
-
TabbedContent,
|
|
27
|
-
TabPane,
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
from ..data.refs import DocRef, SrcCodeRef
|
|
31
|
-
from ..data.traces import LogTrace, TestTrace
|
|
32
|
-
from ..modeling.predicates import ActionPredicate, StatePredicate, Transition
|
|
33
|
-
from ..modeling.scenario import Scenario
|
|
34
|
-
from ..state.inst import StateInstance
|
|
35
|
-
from ..presentation.api import (
|
|
36
|
-
present_data_artifact,
|
|
37
|
-
present_predicate,
|
|
38
|
-
present_scenario,
|
|
39
|
-
present_state_instance,
|
|
40
|
-
present_trace,
|
|
41
|
-
present_transition,
|
|
42
|
-
present_scenario_complement, # NEW
|
|
43
|
-
)
|
|
44
|
-
from ..presentation.ctx import RenderCtx
|
|
45
|
-
|
|
46
|
-
ctx = RenderCtx()
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class InstanceItem(ListItem):
|
|
50
|
-
"""Single-line list entry for a StateInstance."""
|
|
51
|
-
|
|
52
|
-
def __init__(self, inst: StateInstance) -> None:
|
|
53
|
-
self.inst = inst
|
|
54
|
-
super().__init__(Static(self._render_line()))
|
|
55
|
-
|
|
56
|
-
def _render_line(self) -> str:
|
|
57
|
-
"""
|
|
58
|
-
Format:
|
|
59
|
-
2025-02-03 14:22:11 | base=OK sc(miss=1 unsat=0) rec(e=1 w=2 n=3)
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
# --- timestamp ---
|
|
63
|
-
ts = self.inst.created_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
64
|
-
|
|
65
|
-
parts: list[str] = []
|
|
66
|
-
|
|
67
|
-
# --- base status ---
|
|
68
|
-
base_status = None
|
|
69
|
-
try:
|
|
70
|
-
base_status = self.inst.spec.domain_model.base_status
|
|
71
|
-
except Exception:
|
|
72
|
-
pass
|
|
73
|
-
|
|
74
|
-
if base_status is not None:
|
|
75
|
-
parts.append(f"base={base_status.name}")
|
|
76
|
-
else:
|
|
77
|
-
parts.append("base=—")
|
|
78
|
-
|
|
79
|
-
# --- scenario health (from state_diff if present) ---
|
|
80
|
-
sd = getattr(self.inst, "state_diff", None)
|
|
81
|
-
if sd is not None:
|
|
82
|
-
miss = getattr(getattr(sd, "num_sc_missing", None), "after", None)
|
|
83
|
-
unsat = getattr(getattr(sd, "num_sc_inconsistent", None), "after", None)
|
|
84
|
-
|
|
85
|
-
if isinstance(miss, int) or isinstance(unsat, int):
|
|
86
|
-
parts.append(f"sc(miss={miss or 0} unsat={unsat or 0})")
|
|
87
|
-
|
|
88
|
-
# --- recommendations summary ---
|
|
89
|
-
recs = getattr(self.inst, "recommendations", None)
|
|
90
|
-
if recs is not None:
|
|
91
|
-
e = getattr(recs, "num_error", 0) or 0
|
|
92
|
-
w = getattr(recs, "num_warning", 0) or 0
|
|
93
|
-
n = getattr(recs, "num_next", 0) or 0
|
|
94
|
-
parts.append(f"rec(e={e} w={w} n={n})")
|
|
95
|
-
|
|
96
|
-
return f"{ts} | " + " ".join(parts)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
class MainScreen(Screen[Any]):
|
|
100
|
-
"""Main TUI screen."""
|
|
101
|
-
|
|
102
|
-
def compose(self) -> ComposeResult:
|
|
103
|
-
yield Header()
|
|
104
|
-
|
|
105
|
-
with Container(id="body"):
|
|
106
|
-
# ----- split: instances list + details -----
|
|
107
|
-
with Horizontal(id="instances_split"):
|
|
108
|
-
|
|
109
|
-
# left: instances list only
|
|
110
|
-
with Container(id="instances_panel"):
|
|
111
|
-
self.instances_list = ListView(id="instances_list")
|
|
112
|
-
yield self.instances_list
|
|
113
|
-
|
|
114
|
-
# ----- right: tabbed details -----
|
|
115
|
-
with TabbedContent(initial="summary"):
|
|
116
|
-
with TabPane("Summary", id="summary"):
|
|
117
|
-
self.instance_summary = RichLog(
|
|
118
|
-
id="instance_summary",
|
|
119
|
-
wrap=False,
|
|
120
|
-
highlight=True,
|
|
121
|
-
auto_scroll=False,
|
|
122
|
-
)
|
|
123
|
-
yield self.instance_summary
|
|
124
|
-
|
|
125
|
-
with TabPane("Full IML Model", id="model"):
|
|
126
|
-
with Container(id="full_iml_panel"):
|
|
127
|
-
with Horizontal(id="full_iml_toolbar"):
|
|
128
|
-
yield Static("Press 'c' to copy", id="copy_hint")
|
|
129
|
-
|
|
130
|
-
self.full_iml_model = RichLog(
|
|
131
|
-
id="full_iml_model",
|
|
132
|
-
wrap=False,
|
|
133
|
-
highlight=True,
|
|
134
|
-
auto_scroll=False,
|
|
135
|
-
)
|
|
136
|
-
yield self.full_iml_model
|
|
137
|
-
|
|
138
|
-
with TabPane("Predicates", id="predicates"):
|
|
139
|
-
self.predicates = RichLog(
|
|
140
|
-
id="predicates_view",
|
|
141
|
-
wrap=True,
|
|
142
|
-
highlight=True,
|
|
143
|
-
auto_scroll=False,
|
|
144
|
-
)
|
|
145
|
-
yield self.predicates
|
|
146
|
-
|
|
147
|
-
with TabPane("Transitions", id="transitions"):
|
|
148
|
-
self.transitions = RichLog(
|
|
149
|
-
id="transitions_view",
|
|
150
|
-
wrap=True,
|
|
151
|
-
highlight=True,
|
|
152
|
-
auto_scroll=False,
|
|
153
|
-
)
|
|
154
|
-
yield self.transitions
|
|
155
|
-
|
|
156
|
-
with TabPane("Scenarios", id="scenarios"):
|
|
157
|
-
self.scenarios = RichLog(
|
|
158
|
-
id="scenarios_view",
|
|
159
|
-
wrap=True,
|
|
160
|
-
highlight=True,
|
|
161
|
-
auto_scroll=False,
|
|
162
|
-
)
|
|
163
|
-
yield self.scenarios
|
|
164
|
-
|
|
165
|
-
# NEW: Scenario complement tab
|
|
166
|
-
with TabPane("Complement", id="complement"):
|
|
167
|
-
self.complement = RichLog(
|
|
168
|
-
id="complement_view",
|
|
169
|
-
wrap=True,
|
|
170
|
-
highlight=True,
|
|
171
|
-
auto_scroll=False,
|
|
172
|
-
)
|
|
173
|
-
yield self.complement
|
|
174
|
-
|
|
175
|
-
with TabPane("Test Traces", id="test_traces"):
|
|
176
|
-
self.test_traces = RichLog(
|
|
177
|
-
id="test_traces_view",
|
|
178
|
-
wrap=True,
|
|
179
|
-
highlight=True,
|
|
180
|
-
auto_scroll=False,
|
|
181
|
-
)
|
|
182
|
-
yield self.test_traces
|
|
183
|
-
|
|
184
|
-
with TabPane("Log Traces", id="log_traces"):
|
|
185
|
-
self.log_traces = RichLog(
|
|
186
|
-
id="log_traces_view",
|
|
187
|
-
wrap=True,
|
|
188
|
-
highlight=True,
|
|
189
|
-
auto_scroll=False,
|
|
190
|
-
)
|
|
191
|
-
yield self.log_traces
|
|
192
|
-
|
|
193
|
-
with TabPane("Src Artifacts", id="src_artifacts"):
|
|
194
|
-
self.src_refs = RichLog(
|
|
195
|
-
id="src_artifacts_view",
|
|
196
|
-
wrap=True,
|
|
197
|
-
highlight=True,
|
|
198
|
-
auto_scroll=False,
|
|
199
|
-
)
|
|
200
|
-
yield self.src_refs
|
|
201
|
-
|
|
202
|
-
with TabPane("Doc Artifacts", id="doc_artifacts"):
|
|
203
|
-
self.doc_refs = RichLog(
|
|
204
|
-
id="doc_artifacts_view",
|
|
205
|
-
wrap=True,
|
|
206
|
-
highlight=True,
|
|
207
|
-
auto_scroll=False,
|
|
208
|
-
)
|
|
209
|
-
yield self.doc_refs
|
|
210
|
-
|
|
211
|
-
# --- status line above Footer ---
|
|
212
|
-
self.status_line = Static("", id="status_line")
|
|
213
|
-
yield self.status_line
|
|
214
|
-
|
|
215
|
-
yield Footer()
|
|
216
|
-
|
|
217
|
-
# -------------------------------------------------------------------------
|
|
218
|
-
# UI plumbing
|
|
219
|
-
# -------------------------------------------------------------------------
|
|
220
|
-
|
|
221
|
-
def set_status(self, msg: str = "") -> None:
|
|
222
|
-
"""Set footer status line text."""
|
|
223
|
-
self.status_line.update(msg)
|
|
224
|
-
|
|
225
|
-
def clear_status(self) -> None:
|
|
226
|
-
self.set_status("")
|
|
227
|
-
|
|
228
|
-
def refresh_from_state(self) -> None:
|
|
229
|
-
"""
|
|
230
|
-
Rebuild instances list from self.app.state and reselect an instance.
|
|
231
|
-
"""
|
|
232
|
-
prev_created_at: str | None = None
|
|
233
|
-
try:
|
|
234
|
-
if self.instances_list.index is not None and self.instances_list.index >= 0:
|
|
235
|
-
item = self.instances_list.children[self.instances_list.index]
|
|
236
|
-
if isinstance(item, InstanceItem):
|
|
237
|
-
prev_created_at = item.inst.created_at.isoformat()
|
|
238
|
-
except Exception:
|
|
239
|
-
prev_created_at = None
|
|
240
|
-
|
|
241
|
-
self.instances_list.clear()
|
|
242
|
-
for inst in self.app.state.instances:
|
|
243
|
-
self.instances_list.append(InstanceItem(inst))
|
|
244
|
-
|
|
245
|
-
if not self.app.state.instances:
|
|
246
|
-
self.clear_status()
|
|
247
|
-
return
|
|
248
|
-
|
|
249
|
-
idx = 0
|
|
250
|
-
if prev_created_at is not None:
|
|
251
|
-
for i, inst in enumerate(self.app.state.instances):
|
|
252
|
-
if inst.created_at.isoformat() == prev_created_at:
|
|
253
|
-
idx = i
|
|
254
|
-
break
|
|
255
|
-
|
|
256
|
-
self.instances_list.index = idx
|
|
257
|
-
self._select_instance(self.app.state.instances[idx])
|
|
258
|
-
|
|
259
|
-
# reflect current update-available status
|
|
260
|
-
if getattr(self.app, "_update_available", False):
|
|
261
|
-
sig = getattr(self.app, "_pending_state_sig", None)
|
|
262
|
-
if sig is not None:
|
|
263
|
-
mtime_ns, _ = sig
|
|
264
|
-
self.set_status(
|
|
265
|
-
f"[yellow]Update available[/yellow] — press [bold]u[/bold] to reload ({self.app._fmt_mtime(mtime_ns)})"
|
|
266
|
-
)
|
|
267
|
-
else:
|
|
268
|
-
self.clear_status()
|
|
269
|
-
|
|
270
|
-
# -------------------------------------------------------------------------
|
|
271
|
-
# rendering helpers
|
|
272
|
-
# -------------------------------------------------------------------------
|
|
273
|
-
|
|
274
|
-
def _show_instance_summary(self, inst: StateInstance) -> None:
|
|
275
|
-
self.instance_summary.clear()
|
|
276
|
-
self.instance_summary.write(present_state_instance(inst, ctx=ctx, json_only=False))
|
|
277
|
-
self.instance_summary.scroll_to(y=0, animate=False, immediate=True, force=True)
|
|
278
|
-
|
|
279
|
-
def _show_full_iml_model(self, iml_code: str) -> None:
|
|
280
|
-
self._full_iml_text = iml_code or ""
|
|
281
|
-
self.full_iml_model.clear()
|
|
282
|
-
self.full_iml_model.write(Syntax(self._full_iml_text, "ocaml", line_numbers=True, word_wrap=False))
|
|
283
|
-
|
|
284
|
-
def _show_predicates(
|
|
285
|
-
self,
|
|
286
|
-
state_preds: list[StatePredicate],
|
|
287
|
-
action_preds: list[ActionPredicate],
|
|
288
|
-
) -> None:
|
|
289
|
-
self.predicates.clear()
|
|
290
|
-
|
|
291
|
-
state_r = [present_predicate(p, ctx=ctx, json_only=False) for p in state_preds]
|
|
292
|
-
action_r = [present_predicate(p, ctx=ctx, json_only=False) for p in action_preds]
|
|
293
|
-
|
|
294
|
-
self.predicates.write(
|
|
295
|
-
Panel(
|
|
296
|
-
Group(*state_r) if state_r else Text("— none —"),
|
|
297
|
-
title=f"State predicates ({len(state_r)})",
|
|
298
|
-
)
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
self.predicates.write(
|
|
302
|
-
Panel(
|
|
303
|
-
Group(*action_r) if action_r else Text("— none —"),
|
|
304
|
-
title=f"Action predicates ({len(action_r)})",
|
|
305
|
-
)
|
|
306
|
-
)
|
|
307
|
-
self.predicates.scroll_home(animate=False)
|
|
308
|
-
|
|
309
|
-
def _show_transitions(self, transitions: list[Transition]) -> None:
|
|
310
|
-
self.transitions.clear()
|
|
311
|
-
trs = [present_transition(t, ctx=ctx, json_only=False) for t in transitions]
|
|
312
|
-
self.transitions.write(
|
|
313
|
-
Panel(
|
|
314
|
-
Group(*trs) if trs else Text("— none —"),
|
|
315
|
-
title=f"Transitions ({len(trs)})",
|
|
316
|
-
)
|
|
317
|
-
)
|
|
318
|
-
self.transitions.scroll_home(animate=False)
|
|
319
|
-
|
|
320
|
-
def _show_scenarios(self, scenarios: list[Scenario]) -> None:
|
|
321
|
-
self.scenarios.clear()
|
|
322
|
-
scs = [present_scenario(s, ctx=ctx, json_only=False) for s in scenarios]
|
|
323
|
-
self.scenarios.write(
|
|
324
|
-
Panel(
|
|
325
|
-
Group(*scs) if scs else Text("— none —"),
|
|
326
|
-
title=f"Scenarios ({len(scs)})",
|
|
327
|
-
)
|
|
328
|
-
)
|
|
329
|
-
self.scenarios.scroll_home(animate=False)
|
|
330
|
-
|
|
331
|
-
def _show_scenario_complement(self, inst: StateInstance) -> None:
|
|
332
|
-
"""
|
|
333
|
-
Render spec.scenario_comp via the new presentation API.
|
|
334
|
-
"""
|
|
335
|
-
self.complement.clear()
|
|
336
|
-
|
|
337
|
-
comp = getattr(getattr(inst, "spec", None), "scenario_comp", None)
|
|
338
|
-
if comp is None:
|
|
339
|
-
self.complement.write(Panel(Text("— no scenario complement —", style="dim"), title="Complement"))
|
|
340
|
-
self.complement.scroll_home(animate=False)
|
|
341
|
-
return
|
|
342
|
-
|
|
343
|
-
try:
|
|
344
|
-
rend = present_scenario_complement(comp, ctx=ctx, json_only=False)
|
|
345
|
-
except Exception as e:
|
|
346
|
-
self.complement.write(
|
|
347
|
-
Panel(
|
|
348
|
-
Text(f"Failed to render complement: {type(e).__name__}: {e}", style="red"),
|
|
349
|
-
title="Complement",
|
|
350
|
-
)
|
|
351
|
-
)
|
|
352
|
-
self.complement.scroll_home(animate=False)
|
|
353
|
-
return
|
|
354
|
-
|
|
355
|
-
# `rend` is a rich renderable
|
|
356
|
-
self.complement.write(rend)
|
|
357
|
-
self.complement.scroll_home(animate=False)
|
|
358
|
-
|
|
359
|
-
def _show_test_traces(self, test_traces: list[TestTrace]) -> None:
|
|
360
|
-
self.test_traces.clear()
|
|
361
|
-
tts = [present_trace(tt, ctx=ctx, json_only=False) for tt in test_traces]
|
|
362
|
-
self.test_traces.write(
|
|
363
|
-
Panel(
|
|
364
|
-
Group(*tts) if tts else Text("— none —"),
|
|
365
|
-
title=f"Test Traces ({len(tts)})",
|
|
366
|
-
)
|
|
367
|
-
)
|
|
368
|
-
self.test_traces.scroll_home(animate=False)
|
|
369
|
-
|
|
370
|
-
def _show_log_traces(self, log_traces: list[LogTrace]) -> None:
|
|
371
|
-
self.log_traces.clear()
|
|
372
|
-
lts = [present_trace(lt, ctx=ctx, json_only=False) for lt in log_traces]
|
|
373
|
-
self.log_traces.write(
|
|
374
|
-
Panel(
|
|
375
|
-
Group(*lts) if lts else Text("— none —"),
|
|
376
|
-
title=f"Log Traces ({len(lts)})",
|
|
377
|
-
)
|
|
378
|
-
)
|
|
379
|
-
self.log_traces.scroll_home(animate=False, immediate=True)
|
|
380
|
-
|
|
381
|
-
def _show_src_artifacts(self, src_refs: list[SrcCodeRef]) -> None:
|
|
382
|
-
self.src_refs.clear()
|
|
383
|
-
srefs = [present_data_artifact(sr, ctx=ctx, json_only=False) for sr in src_refs]
|
|
384
|
-
self.src_refs.write(
|
|
385
|
-
Panel(
|
|
386
|
-
Group(*srefs) if srefs else Text("— none —"),
|
|
387
|
-
title=f"Source Code References ({len(srefs)})",
|
|
388
|
-
)
|
|
389
|
-
)
|
|
390
|
-
self.src_refs.scroll_home(animate=False, immediate=True)
|
|
391
|
-
|
|
392
|
-
def _show_doc_artifacts(self, doc_refs: list[DocRef]) -> None:
|
|
393
|
-
self.doc_refs.clear()
|
|
394
|
-
drefs = [present_data_artifact(dr, ctx=ctx, json_only=False) for dr in doc_refs]
|
|
395
|
-
self.doc_refs.write(
|
|
396
|
-
Panel(
|
|
397
|
-
Group(*drefs) if drefs else Text("— none —"),
|
|
398
|
-
title=f"Documentation References ({len(drefs)})",
|
|
399
|
-
)
|
|
400
|
-
)
|
|
401
|
-
self.doc_refs.scroll_home(animate=False, immediate=True)
|
|
402
|
-
|
|
403
|
-
def _select_instance(self, inst: StateInstance) -> None:
|
|
404
|
-
self._show_instance_summary(inst)
|
|
405
|
-
self._show_full_iml_model(inst.spec.full_model())
|
|
406
|
-
self._show_predicates(inst.spec.domain_model.state_preds, inst.spec.domain_model.action_preds)
|
|
407
|
-
self._show_transitions(inst.spec.domain_model.transitions)
|
|
408
|
-
self._show_scenarios(inst.spec.scenarios)
|
|
409
|
-
self._show_scenario_complement(inst) # NEW
|
|
410
|
-
self._show_test_traces(inst.art_container.test_traces)
|
|
411
|
-
self._show_log_traces(inst.art_container.log_traces)
|
|
412
|
-
self._show_src_artifacts(inst.art_container.src_code)
|
|
413
|
-
self._show_doc_artifacts(inst.art_container.doc_ref)
|
|
414
|
-
|
|
415
|
-
# -------------------------------------------------------------------------
|
|
416
|
-
# events
|
|
417
|
-
# -------------------------------------------------------------------------
|
|
418
|
-
|
|
419
|
-
def on_mount(self) -> None:
|
|
420
|
-
for inst in self.app.state.instances:
|
|
421
|
-
self.instances_list.append(InstanceItem(inst))
|
|
422
|
-
|
|
423
|
-
if self.app.state and self.app.state.instances:
|
|
424
|
-
self.instances_list.index = 0
|
|
425
|
-
self._select_instance(self.app.state.instances[0])
|
|
426
|
-
|
|
427
|
-
self.instances_list.border_title = "State Instances"
|
|
428
|
-
|
|
429
|
-
# reflect update status on first mount
|
|
430
|
-
if getattr(self.app, "_update_available", False):
|
|
431
|
-
sig = getattr(self.app, "_pending_state_sig", None)
|
|
432
|
-
if sig is not None:
|
|
433
|
-
mtime_ns, _ = sig
|
|
434
|
-
self.set_status(
|
|
435
|
-
f"[yellow]Update available[/yellow] — press [bold]u[/bold] to reload ({self.app._fmt_mtime(mtime_ns)})"
|
|
436
|
-
)
|
|
437
|
-
else:
|
|
438
|
-
self.clear_status()
|
|
439
|
-
|
|
440
|
-
def action_show_tab(self, tab: str) -> None:
|
|
441
|
-
tc = self.query_one(TabbedContent)
|
|
442
|
-
tc.active = tab
|
|
443
|
-
|
|
444
|
-
def on_list_view_highlighted(self, event: ListView.Highlighted) -> None:
|
|
445
|
-
if event.list_view is not self.instances_list:
|
|
446
|
-
return
|
|
447
|
-
if isinstance(event.item, InstanceItem):
|
|
448
|
-
self._select_instance(event.item.inst)
|
|
449
|
-
|
|
450
|
-
def on_list_view_selected(self, event: ListView.Selected) -> None:
|
|
451
|
-
if event.list_view is not self.instances_list:
|
|
452
|
-
return
|
|
453
|
-
if isinstance(event.item, InstanceItem):
|
|
454
|
-
self._select_instance(event.item.inst)
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/tui/splash.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
from typing import Any
|
|
8
|
-
from pathlib import Path
|
|
9
|
-
from textual.screen import Screen
|
|
10
|
-
from textual.containers import Container
|
|
11
|
-
from textual.app import ComposeResult
|
|
12
|
-
from textual.widgets import Static
|
|
13
|
-
from textual_image.widget import Image
|
|
14
|
-
from rich.align import Align
|
|
15
|
-
from rich.text import Text
|
|
16
|
-
|
|
17
|
-
class SplashScreen(Screen[Any]):
|
|
18
|
-
CSS = """
|
|
19
|
-
SplashScreen {
|
|
20
|
-
align: center middle;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
#logo {
|
|
24
|
-
margin-bottom: 1;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
#status {
|
|
28
|
-
width: auto;
|
|
29
|
-
content-align: center middle;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
"""
|
|
33
|
-
LOGO_PATH = Path(__file__).parent / "images" / "speclogician-minimal.png"
|
|
34
|
-
def compose(self) -> ComposeResult:
|
|
35
|
-
with Container(id="logo") as c:
|
|
36
|
-
c.border_title = "width: auto; height: auto;"
|
|
37
|
-
yield Image(self.LOGO_PATH, classes="width-auto height-auto")
|
|
38
|
-
|
|
39
|
-
yield Static(
|
|
40
|
-
Align.center(Text("Loading…", style="dim")),
|
|
41
|
-
id="status",
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
def on_mount(self) -> None:
|
|
45
|
-
def go_next() -> None:
|
|
46
|
-
if getattr(self.app, "demo_md", None):
|
|
47
|
-
self.app.action_demo()
|
|
48
|
-
else:
|
|
49
|
-
self.app.switch_screen("main")
|
|
50
|
-
|
|
51
|
-
self.set_timer(2.0, go_next)
|
speclogician/tui/stats_screen.py
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/tui/stats.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
from typing import Any
|
|
8
|
-
from textual.screen import Screen
|
|
9
|
-
from textual.app import ComposeResult
|
|
10
|
-
from textual.widgets import Header, Footer
|
|
11
|
-
from textual.containers import Container
|
|
12
|
-
from textual_plotext import PlotextPlot
|
|
13
|
-
|
|
14
|
-
from ..state.state_stats import StatsCalculator
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class StatsScreen(Screen[Any]):
|
|
18
|
-
CSS = """
|
|
19
|
-
#body { height: 1fr; }
|
|
20
|
-
|
|
21
|
-
/* 2x2 grid */
|
|
22
|
-
#grid {
|
|
23
|
-
height: 1fr;
|
|
24
|
-
layout: grid;
|
|
25
|
-
grid-size: 2 2;
|
|
26
|
-
grid-gutter: 1 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
PlotextPlot {
|
|
30
|
-
height: 1fr;
|
|
31
|
-
width: 1fr;
|
|
32
|
-
}
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
def compose(self) -> ComposeResult:
|
|
36
|
-
yield Header()
|
|
37
|
-
with Container(id="body"):
|
|
38
|
-
with Container(id="grid"):
|
|
39
|
-
self.plot_model = PlotextPlot()
|
|
40
|
-
self.plot_scenarios = PlotextPlot()
|
|
41
|
-
self.plot_artifacts = PlotextPlot()
|
|
42
|
-
self.plot_ratios = PlotextPlot()
|
|
43
|
-
|
|
44
|
-
yield self.plot_model
|
|
45
|
-
yield self.plot_scenarios
|
|
46
|
-
yield self.plot_artifacts
|
|
47
|
-
yield self.plot_ratios
|
|
48
|
-
yield Footer()
|
|
49
|
-
|
|
50
|
-
def on_mount(self) -> None:
|
|
51
|
-
# If you want live updating while running:
|
|
52
|
-
# self.set_interval(0.5, self.refresh_plot)
|
|
53
|
-
self.refresh_plot()
|
|
54
|
-
|
|
55
|
-
@staticmethod
|
|
56
|
-
def _safe_ratio_series(num: list[int], den: list[int]) -> list[float]:
|
|
57
|
-
out: list[float] = []
|
|
58
|
-
for n, d in zip(num, den):
|
|
59
|
-
out.append(n / (d if d else 1))
|
|
60
|
-
return out
|
|
61
|
-
|
|
62
|
-
def refresh_plot(self) -> None:
|
|
63
|
-
|
|
64
|
-
v = StatsCalculator.stats(self.app.state.instances)
|
|
65
|
-
|
|
66
|
-
# oldest -> newest already
|
|
67
|
-
xs = list(range(len(v["created_at"])))
|
|
68
|
-
|
|
69
|
-
# -------------------------
|
|
70
|
-
# 1) Model growth & usage
|
|
71
|
-
# -------------------------
|
|
72
|
-
plt = self.plot_model.plt
|
|
73
|
-
plt.clear_data()
|
|
74
|
-
plt.title("Model growth & usage")
|
|
75
|
-
plt.xlabel("Instance (oldest → newest)")
|
|
76
|
-
plt.ylabel("count")
|
|
77
|
-
plt.plot(xs, v["num_preds_total"])
|
|
78
|
-
plt.plot(xs, v["num_preds_matched"])
|
|
79
|
-
plt.plot(xs, v["num_trans_total"])
|
|
80
|
-
plt.scatter(xs, v["num_preds_matched"])
|
|
81
|
-
self.plot_model.refresh()
|
|
82
|
-
|
|
83
|
-
# -------------------------
|
|
84
|
-
# 2) Scenario health
|
|
85
|
-
# -------------------------
|
|
86
|
-
plt = self.plot_scenarios.plt
|
|
87
|
-
plt.clear_data()
|
|
88
|
-
plt.title("Scenario health")
|
|
89
|
-
plt.xlabel("Instance (oldest → newest)")
|
|
90
|
-
plt.ylabel("count")
|
|
91
|
-
plt.plot(xs, v["num_sc_total"])
|
|
92
|
-
plt.plot(xs, v["num_sc_matched"])
|
|
93
|
-
plt.plot(xs, v["num_sc_missing"])
|
|
94
|
-
plt.plot(xs, v["num_sc_inconsistent"])
|
|
95
|
-
plt.plot(xs, v["num_sc_conflicted"])
|
|
96
|
-
self.plot_scenarios.refresh()
|
|
97
|
-
|
|
98
|
-
# -------------------------
|
|
99
|
-
# 3) Artifact pipeline quality
|
|
100
|
-
# -------------------------
|
|
101
|
-
plt = self.plot_artifacts.plt
|
|
102
|
-
plt.clear_data()
|
|
103
|
-
plt.title("Artifacts: total → logic-good → matched")
|
|
104
|
-
plt.xlabel("Instance (oldest → newest)")
|
|
105
|
-
plt.ylabel("count")
|
|
106
|
-
plt.plot(xs, v["num_traces_total"])
|
|
107
|
-
plt.plot(xs, v["num_traces_logic_good"])
|
|
108
|
-
plt.plot(xs, v["num_traces_matched"])
|
|
109
|
-
plt.scatter(xs, v["num_traces_matched"])
|
|
110
|
-
self.plot_artifacts.refresh()
|
|
111
|
-
|
|
112
|
-
# -------------------------
|
|
113
|
-
# 4) Coverage ratios
|
|
114
|
-
# -------------------------
|
|
115
|
-
plt = self.plot_ratios.plt
|
|
116
|
-
plt.clear_data()
|
|
117
|
-
plt.title("Coverage ratios")
|
|
118
|
-
plt.xlabel("Instance (oldest → newest)")
|
|
119
|
-
plt.ylabel("ratio (0..1)")
|
|
120
|
-
preds_ratio = self._safe_ratio_series(v["num_preds_matched"], v["num_preds_total"])
|
|
121
|
-
traces_ratio = self._safe_ratio_series(v["num_traces_matched"], v["num_traces_total"])
|
|
122
|
-
plt.plot(xs, preds_ratio)
|
|
123
|
-
plt.plot(xs, traces_ratio)
|
|
124
|
-
plt.scatter(xs, preds_ratio)
|
|
125
|
-
self.plot_ratios.refresh()
|