speclogician 0.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. speclogician/__init__.py +0 -0
  2. speclogician/commands/__init__.py +15 -0
  3. speclogician/commands/cmd_ch.py +616 -0
  4. speclogician/commands/cmd_find.py +256 -0
  5. speclogician/commands/cmd_view.py +202 -0
  6. speclogician/commands/runner.py +149 -0
  7. speclogician/commands/utils.py +101 -0
  8. speclogician/data/__init__.py +0 -0
  9. speclogician/data/artifact.py +63 -0
  10. speclogician/data/container.py +402 -0
  11. speclogician/data/mapping.py +88 -0
  12. speclogician/data/refs.py +24 -0
  13. speclogician/data/traces.py +26 -0
  14. speclogician/demos/.DS_Store +0 -0
  15. speclogician/demos/cmd_demo.py +278 -0
  16. speclogician/demos/loader.py +135 -0
  17. speclogician/demos/model.py +27 -0
  18. speclogician/demos/runner.py +51 -0
  19. speclogician/logic/__init__.py +11 -0
  20. speclogician/logic/api/__init__.py +29 -0
  21. speclogician/logic/api/client.py +606 -0
  22. speclogician/logic/api/decomp.py +67 -0
  23. speclogician/logic/api/scenario.py +102 -0
  24. speclogician/logic/api/traces.py +59 -0
  25. speclogician/logic/lib/__init__.py +19 -0
  26. speclogician/logic/lib/complement.py +107 -0
  27. speclogician/logic/lib/domain_model.py +59 -0
  28. speclogician/logic/lib/predicates.py +151 -0
  29. speclogician/logic/lib/scenarios.py +369 -0
  30. speclogician/logic/lib/traces.py +114 -0
  31. speclogician/logic/lib/transitions.py +104 -0
  32. speclogician/logic/main.py +246 -0
  33. speclogician/logic/strings.py +194 -0
  34. speclogician/logic/utils.py +135 -0
  35. speclogician/main.py +139 -0
  36. speclogician/modeling/__init__.py +31 -0
  37. speclogician/modeling/complement.py +104 -0
  38. speclogician/modeling/component.py +71 -0
  39. speclogician/modeling/conflict.py +26 -0
  40. speclogician/modeling/domain.py +349 -0
  41. speclogician/modeling/predicates.py +59 -0
  42. speclogician/modeling/scenario.py +162 -0
  43. speclogician/modeling/spec.py +306 -0
  44. speclogician/modeling/spec_stats.py +39 -0
  45. speclogician/presentation/__init__.py +0 -0
  46. speclogician/presentation/api.py +244 -0
  47. speclogician/presentation/builders/__init__.py +0 -0
  48. speclogician/presentation/builders/_links.py +44 -0
  49. speclogician/presentation/builders/container.py +53 -0
  50. speclogician/presentation/builders/data_artifact.py +42 -0
  51. speclogician/presentation/builders/domain.py +54 -0
  52. speclogician/presentation/builders/instances_list.py +38 -0
  53. speclogician/presentation/builders/predicate.py +51 -0
  54. speclogician/presentation/builders/recommendations.py +41 -0
  55. speclogician/presentation/builders/scenario.py +41 -0
  56. speclogician/presentation/builders/scenario_complement.py +82 -0
  57. speclogician/presentation/builders/smart_find.py +39 -0
  58. speclogician/presentation/builders/spec.py +39 -0
  59. speclogician/presentation/builders/state_diff.py +150 -0
  60. speclogician/presentation/builders/state_instance.py +42 -0
  61. speclogician/presentation/builders/state_instance_summary.py +84 -0
  62. speclogician/presentation/builders/trace.py +58 -0
  63. speclogician/presentation/ctx.py +38 -0
  64. speclogician/presentation/models/__init__.py +0 -0
  65. speclogician/presentation/models/container.py +44 -0
  66. speclogician/presentation/models/data_artifact.py +33 -0
  67. speclogician/presentation/models/domain.py +50 -0
  68. speclogician/presentation/models/instances_list.py +23 -0
  69. speclogician/presentation/models/predicate.py +60 -0
  70. speclogician/presentation/models/recommendations.py +34 -0
  71. speclogician/presentation/models/scenario.py +31 -0
  72. speclogician/presentation/models/scenario_complement.py +40 -0
  73. speclogician/presentation/models/smart_find.py +34 -0
  74. speclogician/presentation/models/spec.py +32 -0
  75. speclogician/presentation/models/state_diff.py +34 -0
  76. speclogician/presentation/models/state_instance.py +31 -0
  77. speclogician/presentation/models/state_instance_summary.py +102 -0
  78. speclogician/presentation/models/trace.py +42 -0
  79. speclogician/presentation/preview/__init__.py +13 -0
  80. speclogician/presentation/preview/cli.py +50 -0
  81. speclogician/presentation/preview/fixtures/__init__.py +205 -0
  82. speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
  83. speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
  84. speclogician/presentation/preview/fixtures/domain_model.py +162 -0
  85. speclogician/presentation/preview/fixtures/instances_list.py +162 -0
  86. speclogician/presentation/preview/fixtures/predicate.py +184 -0
  87. speclogician/presentation/preview/fixtures/scenario.py +84 -0
  88. speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
  89. speclogician/presentation/preview/fixtures/smart_find.py +140 -0
  90. speclogician/presentation/preview/fixtures/spec.py +95 -0
  91. speclogician/presentation/preview/fixtures/state_diff.py +158 -0
  92. speclogician/presentation/preview/fixtures/state_instance.py +128 -0
  93. speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
  94. speclogician/presentation/preview/fixtures/trace.py +206 -0
  95. speclogician/presentation/preview/registry.py +42 -0
  96. speclogician/presentation/renderers/__init__.py +24 -0
  97. speclogician/presentation/renderers/container.py +136 -0
  98. speclogician/presentation/renderers/data_artifact.py +144 -0
  99. speclogician/presentation/renderers/domain.py +123 -0
  100. speclogician/presentation/renderers/instances_list.py +120 -0
  101. speclogician/presentation/renderers/predicate.py +180 -0
  102. speclogician/presentation/renderers/recommendations.py +90 -0
  103. speclogician/presentation/renderers/scenario.py +94 -0
  104. speclogician/presentation/renderers/scenario_complement.py +59 -0
  105. speclogician/presentation/renderers/smart_find.py +307 -0
  106. speclogician/presentation/renderers/spec.py +105 -0
  107. speclogician/presentation/renderers/state_diff.py +102 -0
  108. speclogician/presentation/renderers/state_instance.py +82 -0
  109. speclogician/presentation/renderers/state_instance_summary.py +143 -0
  110. speclogician/presentation/renderers/trace.py +122 -0
  111. speclogician/py.typed +0 -0
  112. speclogician/shell/app.py +170 -0
  113. speclogician/shell/shell_ch.py +263 -0
  114. speclogician/shell/shell_view.py +153 -0
  115. speclogician/state/__init__.py +0 -0
  116. speclogician/state/change.py +428 -0
  117. speclogician/state/change_result.py +32 -0
  118. speclogician/state/diff.py +191 -0
  119. speclogician/state/inst.py +574 -0
  120. speclogician/state/recommendation.py +13 -0
  121. speclogician/state/recommender.py +577 -0
  122. speclogician/state/state.py +465 -0
  123. speclogician/state/state_stats.py +133 -0
  124. speclogician/tui/__init__.py +0 -0
  125. speclogician/tui/app.py +257 -0
  126. speclogician/tui/app.tcss +160 -0
  127. speclogician/tui/demo.py +45 -0
  128. speclogician/tui/images/speclogician-full.png +0 -0
  129. speclogician/tui/images/speclogician-minimal.png +0 -0
  130. speclogician/tui/main_screen.py +454 -0
  131. speclogician/tui/splash_screen.py +51 -0
  132. speclogician/tui/stats_screen.py +125 -0
  133. speclogician/utils/__init__.py +78 -0
  134. speclogician/utils/load.py +166 -0
  135. speclogician/utils/prompt.md +325 -0
  136. speclogician/utils/testing.py +151 -0
  137. speclogician-0.0.0b1.dist-info/METADATA +116 -0
  138. speclogician-0.0.0b1.dist-info/RECORD +139 -0
  139. speclogician-0.0.0b1.dist-info/WHEEL +4 -0
@@ -0,0 +1,120 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/presentation/renderers/instances_list.py
5
+ #
6
+
7
+ from __future__ import annotations
8
+
9
+ from rich import box
10
+ from rich.panel import Panel
11
+ from rich.table import Table, Column
12
+ from rich.text import Text
13
+
14
+ from speclogician.presentation.ctx import RenderCtx
15
+ from speclogician.presentation.models.instances_list import InstancesListPM
16
+
17
+
18
+ def _bullet_list(changes: str) -> Text:
19
+ """
20
+ Render a comma-separated change list as a left-aligned bullet list.
21
+ """
22
+ if not changes:
23
+ return Text("–", style="dim")
24
+
25
+ items = [c.strip() for c in changes.split(",") if c.strip()]
26
+ if not items:
27
+ return Text("–", style="dim")
28
+
29
+ txt = Text()
30
+ for i, item in enumerate(items):
31
+ if i > 0:
32
+ txt.append("\n")
33
+ txt.append("• ", style="dim")
34
+ txt.append(item)
35
+ return txt
36
+
37
+
38
+ def render_instances_list(pm: InstancesListPM, *, ctx: RenderCtx):
39
+ b = box.MINIMAL if ctx.compact else box.SIMPLE
40
+
41
+ columns = [
42
+ Column(header="State ID", justify="center"),
43
+ Column(header="Changes", justify="left"),
44
+ Column(header="Domain model", justify="center"),
45
+ Column(header="Preds\n# (Errors, Used)", justify="center"),
46
+ Column(header="Scenarios\n# (Errors, Used)", justify="center"),
47
+ Column(header="Test traces\n# (Formalized, Matched)", justify="center"),
48
+ Column(header="Log traces\n# (Formalized, Matched)", justify="center"),
49
+ Column(header="Src code arts\n# (Matched)", justify="center"),
50
+ Column(header="Doc arts\n# (Matched)", justify="center"),
51
+ ]
52
+
53
+ t = Table(
54
+ *columns,
55
+ title="SpecLogician State List",
56
+ expand=True,
57
+ highlight=True,
58
+ show_edge=False,
59
+ box=b,
60
+ )
61
+
62
+ for idx, row in enumerate(pm.rows):
63
+ if idx > 0:
64
+ t.add_section() # thin separator between instances
65
+
66
+ s = row.summary
67
+
68
+ # ---- Domain model ----
69
+ domain_str = str(getattr(s, "base_status", "unknown"))
70
+
71
+ # ---- Predicates (totals) ----
72
+ preds_total = int(getattr(s, "num_preds_total", 0) or 0)
73
+ preds_err = int(getattr(s, "num_preds_errored", 0) or 0)
74
+ preds_used = int(getattr(s, "num_preds_matched", 0) or 0)
75
+ preds_str = f"{preds_total} ({preds_err}, {preds_used})"
76
+
77
+ # These are also available:
78
+ # num_state_preds_total / matched / errored
79
+ # num_action_preds_total / matched / errored
80
+
81
+ # ---- Scenarios ----
82
+ sc_total = int(getattr(s, "num_sc_total", 0) or 0)
83
+ sc_missing = int(getattr(s, "num_sc_missing", 0) or 0)
84
+ sc_inconsistent = int(getattr(s, "num_sc_inconsistent", 0) or 0)
85
+ sc_conflicted = int(getattr(s, "num_sc_conflicted", 0) or 0)
86
+
87
+ # "Errors" bucket includes: missing + inconsistent + conflicted
88
+ sc_err = sc_missing + sc_inconsistent + sc_conflicted
89
+ sc_used = int(getattr(s, "num_sc_matched", 0) or 0)
90
+ sc_str = f"{sc_total} ({sc_err}, {sc_used})"
91
+
92
+ # ---- Test traces ----
93
+ tt_total = int(getattr(s, "num_test_traces_total", 0) or 0)
94
+ tt_formal = int(getattr(s, "num_test_traces_logic_good", 0) or 0)
95
+ tt_matched = int(getattr(s, "num_test_traces_matched", 0) or 0)
96
+ test_str = f"{tt_total} ({tt_formal}, {tt_matched})"
97
+
98
+ # ---- Log traces ----
99
+ lt_total = int(getattr(s, "num_log_traces_total", 0) or 0)
100
+ lt_formal = int(getattr(s, "num_log_traces_logic_good", 0) or 0)
101
+ lt_matched = int(getattr(s, "num_log_traces_matched", 0) or 0)
102
+ log_str = f"{lt_total} ({lt_formal}, {lt_matched})"
103
+
104
+ # ---- Data artifacts ----
105
+ src_matched = int(getattr(s, "num_src_code_arts_matched", 0) or 0)
106
+ doc_matched = int(getattr(s, "num_doc_arts_matched", 0) or 0)
107
+
108
+ t.add_row(
109
+ Text(row.state_label, style="bold green" if idx == 0 else ""),
110
+ _bullet_list(row.changes),
111
+ Text(domain_str),
112
+ Text(preds_str),
113
+ Text(sc_str),
114
+ Text(test_str),
115
+ Text(log_str),
116
+ Text(str(src_matched)),
117
+ Text(str(doc_matched)),
118
+ )
119
+
120
+ return Panel(t, title="[bold]Instances[/bold]", border_style="magenta")
@@ -0,0 +1,180 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/presentation/renderers/predicate.py
5
+ #
6
+
7
+ from __future__ import annotations
8
+
9
+ from rich import box
10
+ from rich.console import Group, RenderableType
11
+ from rich.panel import Panel
12
+ from rich.syntax import Syntax
13
+ from rich.table import Table
14
+ from rich.text import Text
15
+
16
+ from speclogician.presentation.ctx import RenderCtx
17
+ from speclogician.presentation.models.predicate import PredicatePM, TransitionPM
18
+ from speclogician.utils import IMLValidity
19
+
20
+
21
+ # -----------------------------------------------------------------------------
22
+ # small helpers
23
+ # -----------------------------------------------------------------------------
24
+
25
+ def _meta_table(*, ctx: RenderCtx) -> Table:
26
+ b = box.MINIMAL if getattr(ctx, "compact", False) else box.SIMPLE
27
+ t = Table(
28
+ show_header=False,
29
+ box=b,
30
+ show_edge=False,
31
+ pad_edge=False,
32
+ expand=False,
33
+ )
34
+ # Make keys a bit more prominent than before (consistent with your newer style)
35
+ t.add_column(style="bold", no_wrap=True)
36
+ t.add_column(justify="left")
37
+ return t
38
+
39
+
40
+ def _badge(v: IMLValidity) -> Text:
41
+ # Normalize: v may be enum; keep it robust
42
+ s = (getattr(v, "value", str(v)) or "").lower()
43
+
44
+ if s == "valid":
45
+ return Text("✓ valid", style="bold green")
46
+ if s == "invalid":
47
+ return Text("✗ invalid", style="bold red")
48
+ if s == "unknown":
49
+ return Text("? unknown", style="bold yellow")
50
+ return Text(str(getattr(v, "value", v)), style="bold yellow")
51
+
52
+
53
+ def _empty(text: str = "∅ empty") -> Text:
54
+ return Text(text, style="dim")
55
+
56
+
57
+ def _code_block(*, title: str, code: str, lang: str) -> RenderableType:
58
+ # word_wrap=True tends to look better in narrow terminals
59
+ return Panel(
60
+ Syntax(code, lang, line_numbers=False, word_wrap=True),
61
+ title=title,
62
+ border_style="cyan",
63
+ )
64
+
65
+
66
+ def _snippet(s: str, *, limit: int = 200) -> str:
67
+ s2 = (s or "").strip()
68
+ if not s2:
69
+ return ""
70
+ if len(s2) <= limit:
71
+ return s2
72
+ return s2[:limit] + "…"
73
+
74
+
75
+ def section(t: Table, label: str) -> None:
76
+ t.add_row(Text(label, style="italic dim"), Text(""))
77
+
78
+ def field(t: Table, name: str, value: Text | str) -> None:
79
+ t.add_row(
80
+ Text(f" {name}", style="bold"), # ⬅️ indent
81
+ value if isinstance(value, Text) else Text(str(value)),
82
+ )
83
+
84
+
85
+ # -----------------------------------------------------------------------------
86
+ # main renderers
87
+ # -----------------------------------------------------------------------------
88
+
89
+ def render_predicate(pm: PredicatePM, *, ctx: RenderCtx) -> RenderableType:
90
+ t = _meta_table(ctx=ctx)
91
+
92
+ # ── Identity ─────────────────────────────
93
+ section(t, "Identity")
94
+ field(t, "name", Text(pm.name or "-", style="bold"))
95
+ field(t, "signature", Text(pm.signature or "", style="dim"))
96
+ field(t, "type", Text(str(pm.pred_type), style="cyan"))
97
+ if ctx.show_ids:
98
+ field(t, "comp_id", Text(pm.comp_id or "", style="dim"))
99
+ field(t, "updated", Text(pm.last_updated.isoformat(), style="dim"))
100
+
101
+ # ── Logic ────────────────────────────────
102
+ section(t, "Logic")
103
+ field(t, "iml_valid", _badge(pm.is_iml_valid))
104
+ if pm.iml_error:
105
+ field(t, "iml_error", Text(pm.iml_error, style="red"))
106
+
107
+ # ── Connections ──────────────────────────
108
+ section(t, "Connections")
109
+ field(t, "linked artifacts", pm.links.total)
110
+ field(t, "test / log traces", f"{pm.links.test_traces}/{pm.links.log_traces}")
111
+ field(t, "doc / src refs", f"{pm.links.doc_refs}/{pm.links.src_code_refs}")
112
+ if pm.links.unresolved_ids:
113
+ field(t, "unresolved", Text(str(pm.links.unresolved_ids), style="yellow"))
114
+
115
+ blocks: list[RenderableType] = [t]
116
+
117
+ if ctx.show_code:
118
+ blocks.append(Text(""))
119
+ blocks.append(
120
+ Panel(
121
+ Syntax(pm.iml or "(* empty *)", "ocaml", word_wrap=True),
122
+ title="iml",
123
+ border_style="cyan",
124
+ )
125
+ )
126
+
127
+ border = "blue" if pm.kind == "state_predicate" else "magenta"
128
+ return Panel(
129
+ Group(*blocks),
130
+ title=f"[bold]Predicate[/bold]: {pm.name or '-'}",
131
+ border_style=border,
132
+ )
133
+
134
+
135
+ def render_transition(pm: TransitionPM, *, ctx: RenderCtx) -> RenderableType:
136
+ t = _meta_table(ctx=ctx)
137
+
138
+ # Section: identity
139
+ t.add_row(Text("Identity", style="italic dim"), Text(""))
140
+ t.add_row(Text("name"), Text(pm.name or "-", style="bold"))
141
+ t.add_row(Text("signature"), Text(pm.signature or "", style="dim"))
142
+ if ctx.show_ids:
143
+ t.add_row(Text("comp_id"), Text(pm.comp_id or "", style="dim"))
144
+ t.add_row(Text("updated"), Text(pm.last_updated.isoformat(), style="dim"))
145
+
146
+ # Section: logic
147
+ t.add_row(Text("Logic", style="italic dim"), Text(""))
148
+ t.add_row(Text("iml_valid"), _badge(pm.is_iml_valid))
149
+ if pm.iml_error:
150
+ t.add_row(Text("iml_error"), Text(pm.iml_error, style="red"))
151
+
152
+ # Section: connections
153
+ t.add_row(Text("Connections", style="italic dim"), Text(""))
154
+ t.add_row(Text("linked artifacts"), Text(str(pm.links.total)))
155
+ t.add_row(Text("test/log traces"), Text(f"{pm.links.test_traces}/{pm.links.log_traces}"))
156
+ t.add_row(Text("doc/src refs"), Text(f"{pm.links.doc_refs}/{pm.links.src_code_refs}"))
157
+ if pm.links.unresolved_ids:
158
+ t.add_row(Text("unresolved"), Text(str(pm.links.unresolved_ids), style="yellow"))
159
+
160
+ blocks: list[RenderableType] = [t]
161
+
162
+ if ctx.show_code:
163
+ blocks.append(Text(""))
164
+ blocks.append(
165
+ _code_block(
166
+ title="iml",
167
+ code=(pm.iml or "(* empty *)"),
168
+ lang="ocaml",
169
+ )
170
+ )
171
+ else:
172
+ snip = _snippet(pm.iml or pm.src_code, limit=120)
173
+ blocks.append(Text(""))
174
+ blocks.append(Text(f"iml: {snip}" if snip else "iml: ∅", style="dim"))
175
+
176
+ return Panel(
177
+ Group(*blocks),
178
+ title=f"[bold]Transition[/bold]: {pm.name or '-'}",
179
+ border_style="green",
180
+ )
@@ -0,0 +1,90 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/presentation/renderers/recommendations.py
5
+ #
6
+
7
+
8
+ from __future__ import annotations
9
+
10
+ from rich import box
11
+ from rich.console import Group
12
+ from rich.panel import Panel
13
+ from rich.table import Table
14
+ from rich.text import Text
15
+
16
+ from speclogician.presentation.ctx import RenderCtx
17
+ from speclogician.presentation.models.recommendations import RecommendationsPM, RecommendationPM
18
+
19
+
20
+ def _kind_style(kind: str) -> str:
21
+ k = (kind or "").lower()
22
+ if k == "error":
23
+ return "red"
24
+ if k == "warning":
25
+ return "yellow"
26
+ if k == "info":
27
+ return "cyan"
28
+ return "green" # next
29
+
30
+
31
+ def _header(pm: RecommendationsPM) -> Text:
32
+ parts = []
33
+ if pm.num_error:
34
+ parts.append(Text(f"{pm.num_error} error", style="red"))
35
+ if pm.num_warning:
36
+ parts.append(Text(f"{pm.num_warning} warning", style="yellow"))
37
+ if pm.num_next:
38
+ parts.append(Text(f"{pm.num_next} next", style="green"))
39
+ if pm.num_info:
40
+ parts.append(Text(f"{pm.num_info} info", style="cyan"))
41
+
42
+ if not parts:
43
+ return Text("No recommendations", style="dim")
44
+
45
+ out = Text(" • ").join(parts)
46
+ return out
47
+
48
+
49
+ def _render_item(r: RecommendationPM, *, show_priority: bool) -> list[Text]:
50
+ style = _kind_style(r.kind)
51
+ left = Text(r.kind.upper(), style=f"bold {style}")
52
+ if show_priority:
53
+ left.append(f" p{r.priority}", style="dim")
54
+ body = Text(r.text)
55
+ return [left, body]
56
+
57
+
58
+ def render_recommendations(pm: RecommendationsPM, *, ctx: RenderCtx) -> Panel:
59
+ b = box.MINIMAL if ctx.compact else box.SIMPLE
60
+
61
+ if pm.count == 0:
62
+ body = Text("— none —", style="dim")
63
+ return Panel(body, title="Recommendations", border_style="dim", box=b)
64
+
65
+ show_priority = ctx.show_ids # cheap reuse: if ids are shown, show pri too
66
+
67
+ t = Table(
68
+ box=b,
69
+ pad_edge=False,
70
+ show_edge=False,
71
+ expand=True,
72
+ show_header=False,
73
+ )
74
+ t.add_column(style="dim", no_wrap=True)
75
+ t.add_column(no_wrap=False)
76
+
77
+ # In compact mode, show fewer items (but keep deterministic)
78
+ items = pm.items
79
+ if ctx.compact and len(items) > 8:
80
+ items = items[:8]
81
+
82
+ for r in items:
83
+ left, body = _render_item(r, show_priority=show_priority)
84
+ t.add_row(left, body)
85
+
86
+ blocks = [t]
87
+ # show a small header line if useful
88
+ blocks.insert(0, _header(pm))
89
+
90
+ return Panel(Group(*blocks), title="Recommendations", border_style="cyan", box=b)
@@ -0,0 +1,94 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/presentation/renderers/scenario.py
5
+ #
6
+
7
+ from __future__ import annotations
8
+
9
+ from rich import box
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+
14
+ from speclogician.presentation.ctx import RenderCtx
15
+ from speclogician.presentation.models.scenario import ScenarioPM
16
+
17
+
18
+ def _status_style(kind: str) -> str:
19
+ k = (kind or "").lower()
20
+ if k == "valid":
21
+ return "green"
22
+ if k == "missing":
23
+ return "yellow"
24
+ if k == "inconsistent":
25
+ return "red"
26
+ return "dim"
27
+
28
+
29
+ def _section(label: str) -> Text:
30
+ return Text(label, style="italic magenta")
31
+
32
+
33
+ def _bool_badge(v: bool) -> Text:
34
+ return Text("✓" if v else "✗", style=("green" if v else "red"))
35
+
36
+
37
+ def _names_block(items: list[str], *, empty: str = "—") -> Text:
38
+ """
39
+ Render a list of names as plain styled text (no Syntax, no black background).
40
+ """
41
+ if not items:
42
+ return Text(empty, style="dim")
43
+
44
+ out = Text()
45
+ for i, name in enumerate(items):
46
+ if i:
47
+ out.append("\n")
48
+ out.append("• ", style="dim")
49
+ out.append(name, style="cyan")
50
+ return out
51
+
52
+
53
+ def render_scenario(pm: ScenarioPM, *, ctx: RenderCtx) -> Panel:
54
+ status_style = _status_style(pm.status.kind)
55
+
56
+ title = Text.assemble(
57
+ ("Scenario: ", "bold"),
58
+ (pm.name or "(unnamed)", "cyan"),
59
+ )
60
+
61
+ b = box.MINIMAL if ctx.compact else box.SIMPLE
62
+ t = Table(
63
+ show_header=False,
64
+ box=b,
65
+ pad_edge=False,
66
+ show_edge=False,
67
+ expand=False,
68
+ )
69
+ t.add_column(style="italic dim", no_wrap=True)
70
+ t.add_column(no_wrap=False)
71
+
72
+ # optional id
73
+ if ctx.show_ids:
74
+ t.add_row("comp_id", Text(pm.comp_id, style="dim"))
75
+
76
+ # Predicates
77
+ t.add_row(_section("Predicates"), Text(""))
78
+ t.add_row(" given", _names_block(pm.given))
79
+ t.add_row(" when", _names_block(pm.when))
80
+ t.add_row(" then", _names_block(pm.then))
81
+
82
+ # Checks
83
+ t.add_row(_section("Checks"), Text(""))
84
+ t.add_row(" given consistent", _bool_badge(pm.given_preds_consistent))
85
+ t.add_row(" when consistent", _bool_badge(pm.when_preds_consistent))
86
+ t.add_row(" all consistent", _bool_badge(pm.all_preds_consistent))
87
+
88
+ # Missing components
89
+ if getattr(ctx, "show_missing_components", False) and pm.status.kind == "missing":
90
+ t.add_row(_section("Missing"), Text(""))
91
+ t.add_row(" predicates", _names_block(pm.status.missing_preds))
92
+ t.add_row(" transitions", _names_block(pm.status.missing_trans))
93
+
94
+ return Panel(t, title=title, border_style=status_style)
@@ -0,0 +1,59 @@
1
+ #
2
+ # Imandra Inc.
3
+ #
4
+ # speclogician/presentation/renderers/scenario_complement.py
5
+ #
6
+
7
+ from __future__ import annotations
8
+
9
+ from rich.console import Group
10
+ from rich.panel import Panel
11
+ from rich.text import Text
12
+
13
+ from speclogician.presentation.ctx import RenderCtx
14
+ from speclogician.presentation.models.scenario_complement import ScenarioComplementPM
15
+
16
+
17
+ def render_scenario_complement(pm: ScenarioComplementPM, ctx: RenderCtx):
18
+ # Header summary
19
+ hdr = Text()
20
+ hdr.append("Regions: ")
21
+ hdr.append(str(pm.count_regions), style="bold")
22
+
23
+ if not pm.regions_preview:
24
+ return Panel(
25
+ Group(hdr, Text("— no regions (or not computed yet) —")),
26
+ title="Scenario Complement",
27
+ )
28
+
29
+ lines = []
30
+ for r in pm.regions_preview:
31
+ # One-liner summary
32
+ line = Text()
33
+ line.append(r.fp, style="bold")
34
+ line.append(" ")
35
+ line.append(f"constraints={r.num_constraints}")
36
+
37
+ # If we have extra details, add small indented lines
38
+ blocks = [line]
39
+ if r.invariant:
40
+ blocks.append(Text(f" inv: {r.invariant}"))
41
+ if r.constraints_preview:
42
+ blocks.append(Text(f" c0: {r.constraints_preview[0]}"))
43
+ for c in r.constraints_preview[1:]:
44
+ blocks.append(Text(f" {c}"))
45
+ if r.model_eval:
46
+ blocks.append(Text(f" eval: {r.model_eval}"))
47
+ if r.model:
48
+ blocks.append(Text(f" model: {r.model}"))
49
+
50
+ lines.append(Group(*blocks))
51
+
52
+ tail = Text()
53
+ if pm.count_regions > len(pm.regions_preview):
54
+ tail.append(f"(showing {len(pm.regions_preview)} of {pm.count_regions})", style="dim")
55
+
56
+ return Panel(
57
+ Group(hdr, *lines, tail),
58
+ title="Scenario Complement",
59
+ )