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,307 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/presentation/renderers/smart_find.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from rich import box
10
- from rich.console import Group
11
- from rich.panel import Panel
12
- from rich.rule import Rule
13
- from rich.table import Table, Column
14
- from rich.text import Text
15
-
16
- from speclogician.presentation.ctx import RenderCtx
17
- from speclogician.presentation.models.smart_find import SmartFindPM
18
-
19
-
20
- # -----------------------------------------------------------------------------
21
- # helpers
22
- # -----------------------------------------------------------------------------
23
-
24
- def _snip(s: str | None, n: int = 140) -> str:
25
- x = (s or "").strip()
26
- if not x:
27
- return ""
28
- x = x.replace("\n", " ")
29
- return (x[: n - 1] + "…") if len(x) > n else x
30
-
31
-
32
- def _validity_text(iml_valid: str) -> Text:
33
- v = (iml_valid or "").lower()
34
- if v == "valid":
35
- return Text("✓ valid", style="bold green")
36
- if v == "invalid":
37
- return Text("✗ invalid", style="bold red")
38
- return Text("? unknown", style="bold yellow")
39
-
40
-
41
- def _counts_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
42
- b = box.MINIMAL if ctx.compact else box.SIMPLE
43
- t = Table(
44
- show_header=True,
45
- header_style="bold",
46
- box=b,
47
- show_edge=False,
48
- pad_edge=False,
49
- expand=False, # keep count column close
50
- )
51
- t.add_column("Type", style="dim", no_wrap=True)
52
- t.add_column("Count", justify="right", no_wrap=True)
53
-
54
- c = pm.counts
55
- t.add_row("Test traces", str(c.num_test_traces))
56
- t.add_row("Log traces", str(c.num_log_traces))
57
- t.add_row("Doc refs", str(c.num_doc_refs))
58
- t.add_row("Src code refs", str(c.num_src_code_refs))
59
- return t
60
-
61
-
62
- def _want_art_id(ctx: RenderCtx) -> bool:
63
- return bool(getattr(ctx, "show_art_ids", False) or getattr(ctx, "show_ids", False))
64
-
65
-
66
- def _rule(title: str, n: int) -> Rule:
67
- return Rule(f"[bold]{title}[/bold] ({n})", style="dim")
68
-
69
-
70
- # -----------------------------------------------------------------------------
71
- # per-kind tables
72
- # -----------------------------------------------------------------------------
73
-
74
- def _test_traces_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
75
- b = box.MINIMAL if ctx.compact else box.SIMPLE
76
- show_art_id = _want_art_id(ctx)
77
-
78
- cols: list[Column] = []
79
- if show_art_id:
80
- cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
81
- cols += [
82
- Column("name", no_wrap=False, width=34, overflow="ellipsis"),
83
- Column("iml", no_wrap=True, width=9),
84
- Column("time", style="dim", no_wrap=True, width=20),
85
- Column("snippet", no_wrap=False, width=68, overflow="ellipsis"),
86
- ]
87
-
88
- t = Table(
89
- *cols,
90
- show_header=True,
91
- header_style="bold",
92
- box=b,
93
- show_edge=False,
94
- pad_edge=False,
95
- expand=False,
96
- )
97
-
98
- for tt in pm.items.test_traces:
99
- core = tt.core
100
- label = tt.name or tt.filepath or "-"
101
- snippet = (
102
- _snip(tt.contents, 180)
103
- or _snip(core.given, 180)
104
- or _snip(core.when, 180)
105
- or _snip(core.then, 180)
106
- or "—"
107
- )
108
-
109
- row = []
110
- if show_art_id:
111
- row.append(Text(core.art_id or "-", style="dim"))
112
- row += [
113
- Text(label, style="bold"),
114
- _validity_text(core.iml_validity.iml_valid),
115
- Text(core.time or "—", style="dim"),
116
- Text(snippet, style="dim" if snippet == "—" else ""),
117
- ]
118
- t.add_row(*row)
119
-
120
- if not pm.items.test_traces:
121
- if show_art_id:
122
- t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"))
123
- else:
124
- t.add_row(Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"), Text("", style="dim"))
125
-
126
- return t
127
-
128
-
129
- def _log_traces_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
130
- b = box.MINIMAL if ctx.compact else box.SIMPLE
131
- show_art_id = _want_art_id(ctx)
132
-
133
- cols: list[Column] = []
134
- if show_art_id:
135
- cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
136
- cols += [
137
- Column("filename", no_wrap=False, width=34, overflow="ellipsis"),
138
- Column("iml", no_wrap=True, width=9),
139
- Column("time", style="dim", no_wrap=True, width=20),
140
- Column("snippet", no_wrap=False, width=68, overflow="ellipsis"),
141
- ]
142
-
143
- t = Table(
144
- *cols,
145
- show_header=True,
146
- header_style="bold",
147
- box=b,
148
- show_edge=False,
149
- pad_edge=False,
150
- expand=False,
151
- )
152
-
153
- for lt in pm.items.log_traces:
154
- core = lt.core
155
- label = lt.filename or "-"
156
- snippet = (
157
- _snip(lt.contents, 180)
158
- or _snip(core.given, 180)
159
- or _snip(core.when, 180)
160
- or _snip(core.then, 180)
161
- or "—"
162
- )
163
-
164
- row = []
165
- if show_art_id:
166
- row.append(Text(core.art_id or "-", style="dim"))
167
- row += [
168
- Text(label, style="bold"),
169
- _validity_text(core.iml_validity.iml_valid),
170
- Text(core.time or "—", style="dim"),
171
- Text(snippet, style="dim" if snippet == "—" else ""),
172
- ]
173
- t.add_row(*row)
174
-
175
- if not pm.items.log_traces:
176
- if show_art_id:
177
- t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"))
178
- else:
179
- t.add_row(Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("", style="dim"), Text("", style="dim"))
180
-
181
- return t
182
-
183
-
184
- def _doc_refs_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
185
- b = box.MINIMAL if ctx.compact else box.SIMPLE
186
- show_art_id = _want_art_id(ctx)
187
-
188
- cols: list[Column] = []
189
- if show_art_id:
190
- cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
191
- cols += [
192
- Column("meta", no_wrap=False, width=46, overflow="ellipsis"),
193
- Column("snippet", no_wrap=False, width=92, overflow="ellipsis"),
194
- ]
195
-
196
- t = Table(
197
- *cols,
198
- show_header=True,
199
- header_style="bold",
200
- box=b,
201
- show_edge=False,
202
- pad_edge=False,
203
- expand=False,
204
- )
205
-
206
- for dr in pm.items.doc_refs:
207
- label = dr.meta or "DocRef"
208
- snippet = _snip(dr.text, 220) or "—"
209
- row = []
210
- if show_art_id:
211
- row.append(Text(dr.core.art_id or "-", style="dim"))
212
- row += [
213
- Text(label, style="bold"),
214
- Text(snippet, style="dim" if snippet == "—" else ""),
215
- ]
216
- t.add_row(*row)
217
-
218
- if not pm.items.doc_refs:
219
- if show_art_id:
220
- t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"))
221
- else:
222
- t.add_row(Text("(none)", style="dim"), Text("—", style="dim"))
223
-
224
- return t
225
-
226
-
227
- def _src_code_refs_table(pm: SmartFindPM, *, ctx: RenderCtx) -> Table:
228
- b = box.MINIMAL if ctx.compact else box.SIMPLE
229
- show_art_id = _want_art_id(ctx)
230
-
231
- cols: list[Column] = []
232
- if show_art_id:
233
- cols.append(Column("art_id", style="dim", no_wrap=True, width=12))
234
- cols += [
235
- Column("file", no_wrap=False, width=44, overflow="ellipsis"),
236
- Column("lang", no_wrap=True, width=10),
237
- Column("iml", no_wrap=True, width=7),
238
- Column("snippet", no_wrap=False, width=77, overflow="ellipsis"),
239
- ]
240
-
241
- t = Table(
242
- *cols,
243
- show_header=True,
244
- header_style="bold",
245
- box=b,
246
- show_edge=False,
247
- pad_edge=False,
248
- expand=False,
249
- )
250
-
251
- for sr in pm.items.src_code_refs:
252
- file_label = sr.file_path or "SrcCodeRef"
253
- lang = sr.language or "—"
254
- has_iml = "✓" if (sr.iml_code or "").strip() else "—"
255
- snippet = _snip(sr.src_code, 220) or _snip(sr.iml_code, 220) or "—"
256
-
257
- row = []
258
- if show_art_id:
259
- row.append(Text(sr.core.art_id or "-", style="dim"))
260
- row += [
261
- Text(file_label, style="bold"),
262
- Text(lang, style="cyan" if lang != "—" else "dim"),
263
- Text(has_iml, style="green" if has_iml == "✓" else "dim"),
264
- Text(snippet, style="dim" if snippet == "—" else ""),
265
- ]
266
- t.add_row(*row)
267
-
268
- if not pm.items.src_code_refs:
269
- if show_art_id:
270
- t.add_row(Text("-", style="dim"), Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("—", style="dim"))
271
- else:
272
- t.add_row(Text("(none)", style="dim"), Text("—", style="dim"), Text("—", style="dim"), Text("—", style="dim"))
273
-
274
- return t
275
-
276
- def _maybe_section(title: str, n: int, body: object, *, ctx: RenderCtx) -> list[object]:
277
- # Default: do not render empty sections
278
- show_empty = bool(getattr(ctx, "show_empty_sections", False))
279
- if n == 0 and not show_empty:
280
- return []
281
- return [Text(""), _rule(title, n), body]
282
-
283
- # -----------------------------------------------------------------------------
284
- # renderer
285
- # -----------------------------------------------------------------------------
286
-
287
- def render_smart_find(pm: SmartFindPM, *, ctx: RenderCtx):
288
- needle = (pm.counts.needle or "").strip().replace("\n", " ")
289
- header = Text.assemble(
290
- ("Smart find", "bold"),
291
- (" ", ""),
292
- ("needle: ", "dim"),
293
- (repr(needle) if needle else "(empty)", "cyan" if needle else "dim"),
294
- )
295
-
296
- blocks: list[object] = [
297
- header,
298
- Text(""),
299
- _counts_table(pm, ctx=ctx),
300
- ]
301
-
302
- blocks += _maybe_section("Test traces", len(pm.items.test_traces), _test_traces_table(pm, ctx=ctx), ctx=ctx)
303
- blocks += _maybe_section("Log traces", len(pm.items.log_traces), _log_traces_table(pm, ctx=ctx), ctx=ctx)
304
- blocks += _maybe_section("Doc refs", len(pm.items.doc_refs), _doc_refs_table(pm, ctx=ctx), ctx=ctx)
305
- blocks += _maybe_section("Src code refs", len(pm.items.src_code_refs), _src_code_refs_table(pm, ctx=ctx), ctx=ctx)
306
-
307
- return Panel(Group(*blocks), title="[bold]Smart find[/bold]", border_style="magenta")
@@ -1,105 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/presentation/renderers/spec.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from rich.console import Group
10
- from rich.panel import Panel
11
- from rich.rule import Rule
12
- from rich.table import Table
13
- from rich.text import Text
14
- from rich import box
15
-
16
- from speclogician.presentation.ctx import RenderCtx
17
- from speclogician.presentation.models.spec import SpecPM
18
-
19
-
20
- def _scenario_stats_table(pm: SpecPM, *, ctx: RenderCtx) -> Table:
21
- b = box.MINIMAL if ctx.compact else box.SIMPLE
22
- t = Table(show_header=False, box=b, show_edge=False, pad_edge=False)
23
- t.add_column(style="italic dim", no_wrap=True)
24
- t.add_column(justify="right", no_wrap=True)
25
-
26
- c = pm.counts
27
-
28
- t.add_row(Text("Scenarios", style="bold magenta"), Text(""))
29
- t.add_row("Total", Text(str(c.num_sc_total), style="bold"))
30
- t.add_row("Missing components", Text(str(c.num_sc_missing)))
31
- t.add_row("Matched", Text(str(c.num_sc_matched)))
32
- t.add_row("Inconsistent", Text(str(c.num_sc_inconsistent)))
33
- t.add_row("Conflicted", Text(str(c.num_sc_conflicted)))
34
-
35
- return t
36
-
37
-
38
- def _scenario_list_table(pm: SpecPM, *, ctx: RenderCtx) -> Table:
39
- b = box.MINIMAL if ctx.compact else box.SIMPLE
40
- t = Table(title="Scenario list", box=b, show_edge=False, pad_edge=False, expand=False)
41
- t.add_column("Name", no_wrap=True)
42
-
43
- if not pm.scenario_names:
44
- t.add_row(Text("(none)", style="dim"))
45
- return t
46
-
47
- for name in pm.scenario_names:
48
- t.add_row(Text(name))
49
-
50
- return t
51
-
52
-
53
- def _scenario_complement_table(pm: SpecPM, *, ctx: RenderCtx) -> Table:
54
- """
55
- Lightweight complement summary (counts + optional preview list).
56
- Full regions are intentionally not rendered here (they can be huge).
57
- """
58
- b = box.MINIMAL if ctx.compact else box.SIMPLE
59
- t = Table(show_header=False, box=b, show_edge=False, pad_edge=False)
60
- t.add_column(style="italic dim", no_wrap=True)
61
- t.add_column(justify="right", no_wrap=True)
62
-
63
- sc = getattr(pm, "scenario_complement", None)
64
- if sc is None:
65
- t.add_row(Text("Complement", style="bold magenta"), Text(""))
66
- t.add_row(Text("(none)", style="dim"), Text(""))
67
- return t
68
-
69
- t.add_row(Text("Complement", style="bold magenta"), Text(""))
70
- t.add_row("Regions", Text(str(getattr(sc, "num_regions", 0)), style="bold"))
71
-
72
- # Optional “preview” identifiers if you included them in the PM
73
- preview = list(getattr(sc, "region_fps_preview", []) or [])
74
- if preview:
75
- t.add_row(Text("Preview", style="dim"), Text(""))
76
- for fp in preview[:3]:
77
- t.add_row("•", Text(str(fp), style="dim"))
78
-
79
- return t
80
-
81
-
82
- def render_spec(pm: SpecPM, *, ctx: RenderCtx):
83
- # Domain model block (reuse your existing renderer, but DON'T wrap it in extra panels here)
84
- from speclogician.presentation.renderers.domain import render_domain_model
85
-
86
- blocks: list[object] = []
87
-
88
- # ---- Domain ----
89
- blocks.append(Text("Domain", style="bold magenta"))
90
- blocks.append(render_domain_model(pm.domain, ctx=ctx))
91
-
92
- # ---- Scenarios ----
93
- blocks.append(Rule(style="dim"))
94
- blocks.append(Text("Scenarios", style="bold magenta"))
95
- blocks.append(_scenario_stats_table(pm, ctx=ctx))
96
- blocks.append(Text("")) # small spacer
97
- blocks.append(_scenario_list_table(pm, ctx=ctx))
98
-
99
- # ---- Complement ----
100
- sc = getattr(pm, "scenario_complement", None)
101
- if sc is not None or ctx.show_empty_sections:
102
- blocks.append(Rule(style="dim"))
103
- blocks.append(_scenario_complement_table(pm, ctx=ctx))
104
-
105
- return Panel(Group(*blocks), title="[bold]Spec[/bold]", border_style="magenta")
@@ -1,102 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/presentation/renderers/state_diff.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from rich.console import Group, RenderableType
10
- from rich.rule import Rule
11
- from rich.text import Text
12
-
13
- from speclogician.presentation.ctx import RenderCtx
14
- from speclogician.presentation.models.state_diff import StateDiffPM, DiffRowPM
15
- from speclogician.state.diff import ComparisonOutcome
16
-
17
-
18
- def _icon_style_label(oc: ComparisonOutcome) -> tuple[str, str, str]:
19
- """
20
- Returns: (icon, rich_style, human_label)
21
- """
22
- match oc:
23
- case ComparisonOutcome.IMPROVED:
24
- return ("⬆", "green", "improved")
25
- case ComparisonOutcome.DECLINED:
26
- return ("⬇", "red", "declined")
27
- case ComparisonOutcome.NO_CHANGE_GOOD:
28
- return ("🟢", "green", "no change (good)")
29
- case ComparisonOutcome.NO_CHANGE_BAD:
30
- return ("🔴", "red", "no change (bad)")
31
- case ComparisonOutcome.NO_CHANGE:
32
- return ("🟰", "yellow", "no change")
33
- case ComparisonOutcome.UNKNOWN:
34
- return ("🤷", "dim", "unknown")
35
- case _:
36
- # fallback for future enum additions
37
- return ("•", "dim", getattr(oc, "value", str(oc)))
38
-
39
-
40
- def _render_row(r: DiffRowPM, *, label_width: int) -> RenderableType:
41
- icon, style, human = _icon_style_label(r.outcome)
42
-
43
- # Keep alignment stable for monospace-ish terminals
44
- before = str(r.before)
45
- after = str(r.after)
46
-
47
- # Example:
48
- # ⬆ base_status UNKNOWN → VALID (improved)
49
- txt = Text()
50
- txt.append(f"{icon} ", style=style)
51
- txt.append(f"{r.label:<{label_width}}", style="bold")
52
- txt.append(f" {before} → {after}")
53
- txt.append(f" ({human})", style="dim")
54
- return txt
55
-
56
-
57
- def _render_section(title: str, rows: list[DiffRowPM], *, ctx: RenderCtx) -> RenderableType:
58
- if not rows:
59
- return Text("")
60
-
61
- # Compute a reasonable label alignment width for this section (cap to avoid huge padding)
62
- max_len = max((len(r.label) for r in rows), default=len("Metric"))
63
- label_width = min(max(max_len, 18), 34)
64
-
65
- parts: list[RenderableType] = []
66
- # Bracketed header style like your desired output
67
- parts.append(Text(f"[{title}]", style="bold"))
68
-
69
- for r in rows:
70
- parts.append(_render_row(r, label_width=label_width))
71
-
72
- return Group(*parts)
73
-
74
-
75
- def render_state_diff(pm: StateDiffPM, *, ctx: RenderCtx) -> RenderableType:
76
- # Plain-text-ish layout, no panels/tables.
77
- # Desired style:
78
- # State diff
79
- # ──────────
80
- #
81
- # [Section]
82
- # ⬆ metric before → after (improved)
83
-
84
- if not pm.has_changes:
85
- return Group(
86
- Text("State diff", style="bold"),
87
- Rule(style="dim"),
88
- Text("No differences detected", style="italic dim"),
89
- )
90
-
91
- blocks: list[RenderableType] = [Text("State diff", style="bold"), Rule(style="dim")]
92
-
93
- first = True
94
- for sec in pm.sections:
95
- if sec.empty():
96
- continue
97
- if not first and not ctx.compact:
98
- blocks.append(Text("")) # blank line between sections
99
- first = False
100
- blocks.append(_render_section(sec.title, sec.rows, ctx=ctx))
101
-
102
- return Group(*blocks)
@@ -1,82 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/presentation/renderers/state_instance.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from rich.console import Group, RenderableType
10
- from rich.panel import Panel
11
-
12
- from speclogician.presentation.ctx import RenderCtx
13
- from speclogician.presentation.models.state_instance import StateInstancePM
14
-
15
- from speclogician.presentation.renderers.spec import render_spec
16
- from speclogician.presentation.renderers.state_diff import render_state_diff
17
- from speclogician.presentation.renderers.container import render_artifact_container
18
- from speclogician.presentation.renderers.state_instance_summary import (
19
- render_state_instance_summary,
20
- )
21
- from speclogician.presentation.renderers.recommendations import render_recommendations
22
-
23
- def render_state_instance(pm: StateInstancePM, *, ctx: RenderCtx) -> RenderableType:
24
- summary_panel = render_state_instance_summary(pm.summary, ctx=ctx)
25
-
26
- # Always show recommendations first if present (cheap + high-signal).
27
- rec_panel: RenderableType | None = None
28
- if getattr(pm, "recommendations", None) is not None:
29
- rec_panel = render_recommendations(pm.recommendations, ctx=ctx)
30
-
31
- # Stats-only: summary (and recs if available)
32
- if ctx.show_stats_only:
33
- blocks: list[RenderableType] = []
34
- if rec_panel is not None:
35
- blocks.append(rec_panel)
36
- blocks.append(summary_panel)
37
- return Panel(
38
- Group(*blocks),
39
- title="[bold]State instance[/bold]",
40
- border_style="magenta",
41
- expand=True,
42
- )
43
-
44
- blocks: list[RenderableType] = []
45
- if rec_panel is not None:
46
- blocks.append(rec_panel)
47
-
48
- blocks.append(summary_panel)
49
-
50
- if pm.state_diff is not None and getattr(ctx, "show_diff", True):
51
- blocks.append(
52
- Panel(
53
- render_state_diff(pm.state_diff, ctx=ctx),
54
- title="[bold]Diff[/bold]",
55
- border_style="yellow",
56
- )
57
- )
58
-
59
- if pm.spec is not None:
60
- blocks.append(
61
- Panel(
62
- render_spec(pm.spec, ctx=ctx),
63
- title="[bold]Spec[/bold]",
64
- border_style="cyan",
65
- )
66
- )
67
-
68
- if pm.artifacts is not None:
69
- blocks.append(
70
- Panel(
71
- render_artifact_container(pm.artifacts, ctx=ctx),
72
- title="[bold]Artifacts[/bold]",
73
- border_style="green",
74
- )
75
- )
76
-
77
- return Panel(
78
- Group(*blocks),
79
- title="[bold]State instance[/bold]",
80
- border_style="magenta",
81
- expand=True,
82
- )