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.
- speclogician/__init__.py +0 -0
- speclogician/commands/__init__.py +15 -0
- speclogician/commands/cmd_ch.py +616 -0
- speclogician/commands/cmd_find.py +256 -0
- speclogician/commands/cmd_view.py +202 -0
- speclogician/commands/runner.py +149 -0
- speclogician/commands/utils.py +101 -0
- speclogician/data/__init__.py +0 -0
- speclogician/data/artifact.py +63 -0
- speclogician/data/container.py +402 -0
- speclogician/data/mapping.py +88 -0
- speclogician/data/refs.py +24 -0
- speclogician/data/traces.py +26 -0
- speclogician/demos/.DS_Store +0 -0
- speclogician/demos/cmd_demo.py +278 -0
- speclogician/demos/loader.py +135 -0
- speclogician/demos/model.py +27 -0
- speclogician/demos/runner.py +51 -0
- speclogician/logic/__init__.py +11 -0
- speclogician/logic/api/__init__.py +29 -0
- speclogician/logic/api/client.py +606 -0
- speclogician/logic/api/decomp.py +67 -0
- speclogician/logic/api/scenario.py +102 -0
- speclogician/logic/api/traces.py +59 -0
- speclogician/logic/lib/__init__.py +19 -0
- speclogician/logic/lib/complement.py +107 -0
- speclogician/logic/lib/domain_model.py +59 -0
- speclogician/logic/lib/predicates.py +151 -0
- speclogician/logic/lib/scenarios.py +369 -0
- speclogician/logic/lib/traces.py +114 -0
- speclogician/logic/lib/transitions.py +104 -0
- speclogician/logic/main.py +246 -0
- speclogician/logic/strings.py +194 -0
- speclogician/logic/utils.py +135 -0
- speclogician/main.py +139 -0
- speclogician/modeling/__init__.py +31 -0
- speclogician/modeling/complement.py +104 -0
- speclogician/modeling/component.py +71 -0
- speclogician/modeling/conflict.py +26 -0
- speclogician/modeling/domain.py +349 -0
- speclogician/modeling/predicates.py +59 -0
- speclogician/modeling/scenario.py +162 -0
- speclogician/modeling/spec.py +306 -0
- speclogician/modeling/spec_stats.py +39 -0
- speclogician/presentation/__init__.py +0 -0
- speclogician/presentation/api.py +244 -0
- speclogician/presentation/builders/__init__.py +0 -0
- speclogician/presentation/builders/_links.py +44 -0
- speclogician/presentation/builders/container.py +53 -0
- speclogician/presentation/builders/data_artifact.py +42 -0
- speclogician/presentation/builders/domain.py +54 -0
- speclogician/presentation/builders/instances_list.py +38 -0
- speclogician/presentation/builders/predicate.py +51 -0
- speclogician/presentation/builders/recommendations.py +41 -0
- speclogician/presentation/builders/scenario.py +41 -0
- speclogician/presentation/builders/scenario_complement.py +82 -0
- speclogician/presentation/builders/smart_find.py +39 -0
- speclogician/presentation/builders/spec.py +39 -0
- speclogician/presentation/builders/state_diff.py +150 -0
- speclogician/presentation/builders/state_instance.py +42 -0
- speclogician/presentation/builders/state_instance_summary.py +84 -0
- speclogician/presentation/builders/trace.py +58 -0
- speclogician/presentation/ctx.py +38 -0
- speclogician/presentation/models/__init__.py +0 -0
- speclogician/presentation/models/container.py +44 -0
- speclogician/presentation/models/data_artifact.py +33 -0
- speclogician/presentation/models/domain.py +50 -0
- speclogician/presentation/models/instances_list.py +23 -0
- speclogician/presentation/models/predicate.py +60 -0
- speclogician/presentation/models/recommendations.py +34 -0
- speclogician/presentation/models/scenario.py +31 -0
- speclogician/presentation/models/scenario_complement.py +40 -0
- speclogician/presentation/models/smart_find.py +34 -0
- speclogician/presentation/models/spec.py +32 -0
- speclogician/presentation/models/state_diff.py +34 -0
- speclogician/presentation/models/state_instance.py +31 -0
- speclogician/presentation/models/state_instance_summary.py +102 -0
- speclogician/presentation/models/trace.py +42 -0
- speclogician/presentation/preview/__init__.py +13 -0
- speclogician/presentation/preview/cli.py +50 -0
- speclogician/presentation/preview/fixtures/__init__.py +205 -0
- speclogician/presentation/preview/fixtures/artifact_container.py +150 -0
- speclogician/presentation/preview/fixtures/data_artifact.py +144 -0
- speclogician/presentation/preview/fixtures/domain_model.py +162 -0
- speclogician/presentation/preview/fixtures/instances_list.py +162 -0
- speclogician/presentation/preview/fixtures/predicate.py +184 -0
- speclogician/presentation/preview/fixtures/scenario.py +84 -0
- speclogician/presentation/preview/fixtures/scenario_complement.py +81 -0
- speclogician/presentation/preview/fixtures/smart_find.py +140 -0
- speclogician/presentation/preview/fixtures/spec.py +95 -0
- speclogician/presentation/preview/fixtures/state_diff.py +158 -0
- speclogician/presentation/preview/fixtures/state_instance.py +128 -0
- speclogician/presentation/preview/fixtures/state_instance_summary.py +80 -0
- speclogician/presentation/preview/fixtures/trace.py +206 -0
- speclogician/presentation/preview/registry.py +42 -0
- speclogician/presentation/renderers/__init__.py +24 -0
- speclogician/presentation/renderers/container.py +136 -0
- speclogician/presentation/renderers/data_artifact.py +144 -0
- speclogician/presentation/renderers/domain.py +123 -0
- speclogician/presentation/renderers/instances_list.py +120 -0
- speclogician/presentation/renderers/predicate.py +180 -0
- speclogician/presentation/renderers/recommendations.py +90 -0
- speclogician/presentation/renderers/scenario.py +94 -0
- speclogician/presentation/renderers/scenario_complement.py +59 -0
- speclogician/presentation/renderers/smart_find.py +307 -0
- speclogician/presentation/renderers/spec.py +105 -0
- speclogician/presentation/renderers/state_diff.py +102 -0
- speclogician/presentation/renderers/state_instance.py +82 -0
- speclogician/presentation/renderers/state_instance_summary.py +143 -0
- speclogician/presentation/renderers/trace.py +122 -0
- speclogician/py.typed +0 -0
- speclogician/shell/app.py +170 -0
- speclogician/shell/shell_ch.py +263 -0
- speclogician/shell/shell_view.py +153 -0
- speclogician/state/__init__.py +0 -0
- speclogician/state/change.py +428 -0
- speclogician/state/change_result.py +32 -0
- speclogician/state/diff.py +191 -0
- speclogician/state/inst.py +574 -0
- speclogician/state/recommendation.py +13 -0
- speclogician/state/recommender.py +577 -0
- speclogician/state/state.py +465 -0
- speclogician/state/state_stats.py +133 -0
- speclogician/tui/__init__.py +0 -0
- speclogician/tui/app.py +257 -0
- speclogician/tui/app.tcss +160 -0
- speclogician/tui/demo.py +45 -0
- speclogician/tui/images/speclogician-full.png +0 -0
- speclogician/tui/images/speclogician-minimal.png +0 -0
- speclogician/tui/main_screen.py +454 -0
- speclogician/tui/splash_screen.py +51 -0
- speclogician/tui/stats_screen.py +125 -0
- speclogician/utils/__init__.py +78 -0
- speclogician/utils/load.py +166 -0
- speclogician/utils/prompt.md +325 -0
- speclogician/utils/testing.py +151 -0
- speclogician-0.0.0b1.dist-info/METADATA +116 -0
- speclogician-0.0.0b1.dist-info/RECORD +139 -0
- speclogician-0.0.0b1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/preview/fixtures/trace.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
9
|
+
from speclogician.presentation.models.trace import (
|
|
10
|
+
TraceIMLValidityPM,
|
|
11
|
+
TraceCorePM,
|
|
12
|
+
TestTracePM,
|
|
13
|
+
LogTracePM,
|
|
14
|
+
)
|
|
15
|
+
from speclogician.presentation.renderers.trace import (
|
|
16
|
+
render_test_trace,
|
|
17
|
+
render_log_trace,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# -----------------------------------------------------------------------------
|
|
22
|
+
# Shared helpers
|
|
23
|
+
# -----------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
def minimal_iml_validity() -> TraceIMLValidityPM:
|
|
26
|
+
return TraceIMLValidityPM(iml_valid="unknown", err=None)
|
|
27
|
+
|
|
28
|
+
def typical_iml_validity_valid() -> TraceIMLValidityPM:
|
|
29
|
+
return TraceIMLValidityPM(iml_valid="valid", err=None)
|
|
30
|
+
|
|
31
|
+
def typical_iml_validity_invalid() -> TraceIMLValidityPM:
|
|
32
|
+
return TraceIMLValidityPM(
|
|
33
|
+
iml_valid="invalid",
|
|
34
|
+
err="Parse error: expected ')' at line 1, col 12",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def edge_iml_validity_invalid_long() -> TraceIMLValidityPM:
|
|
38
|
+
return TraceIMLValidityPM(
|
|
39
|
+
iml_valid="invalid",
|
|
40
|
+
err=(
|
|
41
|
+
"Type error: cannot unify int and bool in expression "
|
|
42
|
+
"`if s.x > 0 then 1 else false` (line 42, col 3). "
|
|
43
|
+
"Hint: check predicate body and variable bindings."
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def minimal_trace_core() -> TraceCorePM:
|
|
48
|
+
return TraceCorePM(
|
|
49
|
+
art_id="00000000-0000-0000-0000-000000000000",
|
|
50
|
+
given="",
|
|
51
|
+
when=None,
|
|
52
|
+
then=None,
|
|
53
|
+
time="",
|
|
54
|
+
iml_validity=minimal_iml_validity(),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def typical_trace_core_ok(*, art_id: str = "11111111-1111-1111-1111-111111111111") -> TraceCorePM:
|
|
58
|
+
return TraceCorePM(
|
|
59
|
+
art_id=art_id,
|
|
60
|
+
given="{ x = 10 }",
|
|
61
|
+
when="{ a = 2 }",
|
|
62
|
+
then="{ x = 12 }",
|
|
63
|
+
time="2026-01-09T10:12:00Z",
|
|
64
|
+
iml_validity=typical_iml_validity_valid(),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def typical_trace_core_invalid(*, art_id: str = "22222222-2222-2222-2222-222222222222") -> TraceCorePM:
|
|
68
|
+
return TraceCorePM(
|
|
69
|
+
art_id=art_id,
|
|
70
|
+
given="{ x = 10 }",
|
|
71
|
+
when="{ a = 2 }",
|
|
72
|
+
then="{ x = 12 }",
|
|
73
|
+
time="2026-01-09T10:13:00Z",
|
|
74
|
+
iml_validity=typical_iml_validity_invalid(),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def edge_trace_core_long(*, art_id: str = "99999999-9999-9999-9999-999999999999") -> TraceCorePM:
|
|
78
|
+
# Deliberately long / multi-line / weird to stress renderers
|
|
79
|
+
return TraceCorePM(
|
|
80
|
+
art_id=art_id,
|
|
81
|
+
given=(
|
|
82
|
+
"{ x = 10; notes = \"a very long string value that will wrap in tables\"; "
|
|
83
|
+
"nested = { y = 1234567890; z = -42 } }"
|
|
84
|
+
),
|
|
85
|
+
when="{ a = 2; meta = \"multiline\\nwith newlines\\nand spaces\\n\" }",
|
|
86
|
+
then="{ x = 12; result = Some \"OK\"; debug = \"a\" ^ \"b\" ^ \"c\" }",
|
|
87
|
+
time="2026-01-09T10:12:00.123456Z",
|
|
88
|
+
iml_validity=edge_iml_validity_invalid_long(),
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# -----------------------------------------------------------------------------
|
|
92
|
+
# TestTracePM fixtures
|
|
93
|
+
# -----------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
def minimal_test_trace_pm() -> TestTracePM:
|
|
96
|
+
return TestTracePM(
|
|
97
|
+
core=minimal_trace_core(),
|
|
98
|
+
name="",
|
|
99
|
+
filepath="",
|
|
100
|
+
language=None,
|
|
101
|
+
contents=None,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def typical_test_trace_pm(*, with_contents: bool = False, invalid: bool = False) -> TestTracePM:
|
|
105
|
+
core = typical_trace_core_invalid() if invalid else typical_trace_core_ok()
|
|
106
|
+
return TestTracePM(
|
|
107
|
+
core=core,
|
|
108
|
+
name="Two parent orders, one rejected",
|
|
109
|
+
filepath="tests/risk/OrderValidationSpec.groovy",
|
|
110
|
+
language="groovy",
|
|
111
|
+
contents=(
|
|
112
|
+
"given: '...'\nwhen: '...'\nthen: '...'\n"
|
|
113
|
+
if with_contents
|
|
114
|
+
else None
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def edge_test_trace_pm() -> TestTracePM:
|
|
119
|
+
return TestTracePM(
|
|
120
|
+
core=edge_trace_core_long(),
|
|
121
|
+
name="test_" + ("really_long_name_" * 6) + "end",
|
|
122
|
+
filepath=("very/long/path/" * 8) + "OrderValidationSpec.groovy",
|
|
123
|
+
language="groovy",
|
|
124
|
+
contents=(
|
|
125
|
+
"given: \"A very long test body that includes code\"\n"
|
|
126
|
+
+ ("// line\n" * 60)
|
|
127
|
+
+ "then: \"assert something\"\n"
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# -----------------------------------------------------------------------------
|
|
132
|
+
# LogTracePM fixtures
|
|
133
|
+
# -----------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
def minimal_log_trace_pm() -> LogTracePM:
|
|
136
|
+
return LogTracePM(
|
|
137
|
+
core=minimal_trace_core(),
|
|
138
|
+
filename="",
|
|
139
|
+
contents=None,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
def typical_log_trace_pm(*, with_contents: bool = False, invalid: bool = False) -> LogTracePM:
|
|
143
|
+
core = typical_trace_core_invalid() if invalid else typical_trace_core_ok(
|
|
144
|
+
art_id="33333333-3333-3333-3333-333333333333"
|
|
145
|
+
)
|
|
146
|
+
return LogTracePM(
|
|
147
|
+
core=core,
|
|
148
|
+
filename="prod_2026-01-09_FIX.log",
|
|
149
|
+
contents=("8=FIX.4.4|9=...|35=D|..." if with_contents else None),
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
def edge_log_trace_pm() -> LogTracePM:
|
|
153
|
+
return LogTracePM(
|
|
154
|
+
core=edge_trace_core_long(art_id="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"),
|
|
155
|
+
filename="prod_" + ("2026-01-09_" * 5) + "huge_FIX.log",
|
|
156
|
+
contents=("8=FIX.4.4|9=...|35=D|...\n" * 200),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# -----------------------------------------------------------------------------
|
|
160
|
+
# Fixture entrypoints for PreviewSpec(make=...)
|
|
161
|
+
# -----------------------------------------------------------------------------
|
|
162
|
+
|
|
163
|
+
def make_test_trace_pm(ctx: RenderCtx, variant: str) -> TestTracePM:
|
|
164
|
+
"""
|
|
165
|
+
Entry point used by PreviewSpec(make=...).
|
|
166
|
+
|
|
167
|
+
Variants:
|
|
168
|
+
- typical: valid + includes original contents
|
|
169
|
+
- minimal: smallest possible (no code blocks / metadata)
|
|
170
|
+
- edge: long values + invalid + lots of contents
|
|
171
|
+
"""
|
|
172
|
+
v = (variant or "").strip().lower()
|
|
173
|
+
if v == "typical":
|
|
174
|
+
# typical should exercise code rendering; include original contents
|
|
175
|
+
return typical_test_trace_pm(with_contents=True, invalid=False)
|
|
176
|
+
if v == "minimal":
|
|
177
|
+
return minimal_test_trace_pm()
|
|
178
|
+
if v == "edge":
|
|
179
|
+
return edge_test_trace_pm()
|
|
180
|
+
raise ValueError(f"Unknown variant: {variant!r}")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def make_log_trace_pm(ctx: RenderCtx, variant: str) -> LogTracePM:
|
|
184
|
+
"""
|
|
185
|
+
Entry point used by PreviewSpec(make=...).
|
|
186
|
+
|
|
187
|
+
Variants:
|
|
188
|
+
- typical: valid + includes log contents
|
|
189
|
+
- minimal: smallest possible
|
|
190
|
+
- edge: long values + invalid + lots of contents
|
|
191
|
+
"""
|
|
192
|
+
v = (variant or "").strip().lower()
|
|
193
|
+
if v == "typical":
|
|
194
|
+
return typical_log_trace_pm(with_contents=True, invalid=False)
|
|
195
|
+
if v == "minimal":
|
|
196
|
+
return minimal_log_trace_pm()
|
|
197
|
+
if v == "edge":
|
|
198
|
+
return edge_log_trace_pm()
|
|
199
|
+
raise ValueError(f"Unknown variant: {variant!r}")
|
|
200
|
+
|
|
201
|
+
def render_test(pm: TestTracePM, ctx: RenderCtx):
|
|
202
|
+
return render_test_trace(pm, ctx=ctx)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def render_log(pm: LogTracePM, ctx: RenderCtx):
|
|
206
|
+
return render_log_trace(pm, ctx=ctx)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/preview/registry.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Callable, Any
|
|
11
|
+
|
|
12
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class PreviewSpec:
|
|
17
|
+
"""
|
|
18
|
+
A preview entry in the registry.
|
|
19
|
+
- make(ctx, variant) returns a *PM object* (pydantic model)
|
|
20
|
+
- render(pm, ctx) returns a Rich renderable
|
|
21
|
+
"""
|
|
22
|
+
name: str
|
|
23
|
+
variants: tuple[str, ...]
|
|
24
|
+
make: Callable[[RenderCtx, str], Any]
|
|
25
|
+
render: Callable[[Any, RenderCtx], Any]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_REGISTRY: dict[str, PreviewSpec] = {}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def register(spec: PreviewSpec) -> None:
|
|
32
|
+
_REGISTRY[spec.name] = spec
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get(name: str) -> PreviewSpec:
|
|
36
|
+
if name not in _REGISTRY:
|
|
37
|
+
raise KeyError(f"Unknown preview: {name}. Available: {', '.join(sorted(_REGISTRY))}")
|
|
38
|
+
return _REGISTRY[name]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def list_previews() -> list[PreviewSpec]:
|
|
42
|
+
return [*_REGISTRY.values()]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/renderers/__init__.py (or similar)
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
def iml_validity_icon_style_label(iml_valid: str | None) -> tuple[str, str, str]:
|
|
10
|
+
"""
|
|
11
|
+
Returns: (icon, rich_style, human_label)
|
|
12
|
+
"""
|
|
13
|
+
v = (iml_valid or "").lower()
|
|
14
|
+
|
|
15
|
+
match v:
|
|
16
|
+
case "valid":
|
|
17
|
+
return ("✓", "green", "valid")
|
|
18
|
+
case "invalid":
|
|
19
|
+
return ("✗", "red", "invalid")
|
|
20
|
+
case "unknown" | "":
|
|
21
|
+
return ("…", "yellow", "unknown")
|
|
22
|
+
case _:
|
|
23
|
+
# forward-compatible / defensive
|
|
24
|
+
return ("?", "dim", v)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/renderers/container.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.table import Table
|
|
13
|
+
from rich.text import Text
|
|
14
|
+
from rich.rule import Rule
|
|
15
|
+
|
|
16
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
17
|
+
from speclogician.presentation.models.container import ArtifactContainerPM
|
|
18
|
+
from speclogician.presentation.renderers.trace import render_test_trace, render_log_trace
|
|
19
|
+
from speclogician.presentation.renderers.data_artifact import render_doc_ref, render_src_code_ref
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _group_with_separators(renderables: list[object], *, style: str = "dim") -> Group:
|
|
23
|
+
"""
|
|
24
|
+
Render list items with a horizontal separator between them.
|
|
25
|
+
"""
|
|
26
|
+
parts: list[object] = []
|
|
27
|
+
for i, r in enumerate(renderables):
|
|
28
|
+
parts.append(r)
|
|
29
|
+
if i < len(renderables) - 1:
|
|
30
|
+
parts.append(Rule(style=style))
|
|
31
|
+
return Group(*parts)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def render_artifact_container(pm: ArtifactContainerPM, *, ctx: RenderCtx):
|
|
35
|
+
b = box.MINIMAL if ctx.compact else box.SIMPLE
|
|
36
|
+
|
|
37
|
+
# --- Counts table ---
|
|
38
|
+
t = Table(
|
|
39
|
+
show_header=False,
|
|
40
|
+
box=b,
|
|
41
|
+
show_edge=False,
|
|
42
|
+
pad_edge=False,
|
|
43
|
+
expand=False,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
t.add_column(no_wrap=True)
|
|
47
|
+
t.add_column(
|
|
48
|
+
justify="right",
|
|
49
|
+
width=6,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def section(title: str) -> None:
|
|
53
|
+
t.add_row(
|
|
54
|
+
Text(title, style="bold magenta"),
|
|
55
|
+
Text(""),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def metric(label: str, value: int, *, icon: str = "•") -> None:
|
|
59
|
+
t.add_row(
|
|
60
|
+
Text(f" {icon} {label}", style="dim"),
|
|
61
|
+
Text(str(value)),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
c = pm.counts
|
|
65
|
+
|
|
66
|
+
section("Test traces")
|
|
67
|
+
metric("Total", c.num_test_traces_total)
|
|
68
|
+
metric("Matched", c.num_test_traces_matched, icon="✓")
|
|
69
|
+
metric("Valid IML", c.num_test_traces_logic_good, icon="✓")
|
|
70
|
+
|
|
71
|
+
section("Log traces")
|
|
72
|
+
metric("Total", c.num_log_traces_total)
|
|
73
|
+
metric("Matched", c.num_log_traces_matched, icon="✓")
|
|
74
|
+
metric("Valid IML", c.num_log_traces_logic_good, icon="✓")
|
|
75
|
+
|
|
76
|
+
section("Source code refs")
|
|
77
|
+
metric("Total", c.num_src_code_arts_total)
|
|
78
|
+
metric("Matched", c.num_src_code_arts_matched, icon="✓")
|
|
79
|
+
|
|
80
|
+
section("Doc refs")
|
|
81
|
+
metric("Total", c.num_doc_arts_total)
|
|
82
|
+
metric("Matched", c.num_doc_arts_matched, icon="✓")
|
|
83
|
+
|
|
84
|
+
if ctx.show_stats_only:
|
|
85
|
+
return Panel(
|
|
86
|
+
t,
|
|
87
|
+
title="[bold]Artifacts[/bold]",
|
|
88
|
+
border_style="magenta",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
blocks: list[object] = [Panel(t, title="[bold]Counts[/bold]", border_style="magenta")]
|
|
92
|
+
|
|
93
|
+
# --- Split by type ---
|
|
94
|
+
items = pm.items
|
|
95
|
+
|
|
96
|
+
if items.test_traces:
|
|
97
|
+
rendered = [render_test_trace(x, ctx=ctx) for x in items.test_traces]
|
|
98
|
+
blocks.append(
|
|
99
|
+
Panel(
|
|
100
|
+
_group_with_separators(rendered),
|
|
101
|
+
title=f"[bold]Test traces[/bold] ({len(items.test_traces)})",
|
|
102
|
+
border_style="cyan",
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
if items.log_traces:
|
|
107
|
+
rendered = [render_log_trace(x, ctx=ctx) for x in items.log_traces]
|
|
108
|
+
blocks.append(
|
|
109
|
+
Panel(
|
|
110
|
+
_group_with_separators(rendered),
|
|
111
|
+
title=f"[bold]Log traces[/bold] ({len(items.log_traces)})",
|
|
112
|
+
border_style="cyan",
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if items.doc_refs:
|
|
117
|
+
rendered = [render_doc_ref(x, ctx=ctx) for x in items.doc_refs]
|
|
118
|
+
blocks.append(
|
|
119
|
+
Panel(
|
|
120
|
+
_group_with_separators(rendered),
|
|
121
|
+
title=f"[bold]Doc refs[/bold] ({len(items.doc_refs)})",
|
|
122
|
+
border_style="green",
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if items.src_code_refs:
|
|
127
|
+
rendered = [render_src_code_ref(x, ctx=ctx) for x in items.src_code_refs]
|
|
128
|
+
blocks.append(
|
|
129
|
+
Panel(
|
|
130
|
+
_group_with_separators(rendered),
|
|
131
|
+
title=f"[bold]Src code refs[/bold] ({len(items.src_code_refs)})",
|
|
132
|
+
border_style="green",
|
|
133
|
+
)
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return Panel(Group(*blocks), title="[bold]Artifact container[/bold]", border_style="magenta")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/renderers/data_artifact.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
from rich.console import Group
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
from rich.syntax import Syntax
|
|
12
|
+
from rich.rule import Rule
|
|
13
|
+
|
|
14
|
+
from speclogician.presentation.ctx import RenderCtx
|
|
15
|
+
from speclogician.presentation.models.data_artifact import DocRefPM, SrcCodeRefPM
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _kv_table(ctx: RenderCtx) -> Table:
|
|
19
|
+
# tighter + predictable widths
|
|
20
|
+
t = Table(
|
|
21
|
+
show_header=False,
|
|
22
|
+
box=None, # no box for header table
|
|
23
|
+
show_edge=False,
|
|
24
|
+
pad_edge=False,
|
|
25
|
+
expand=True,
|
|
26
|
+
)
|
|
27
|
+
t.add_column(style="bold", no_wrap=True, width=10) # fixed key col
|
|
28
|
+
t.add_column(ratio=1) # value col fills remaining
|
|
29
|
+
return t
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _add_kv(t: Table, k: str, v: str | None, *, style: str | None = None) -> None:
|
|
33
|
+
if v is None:
|
|
34
|
+
return
|
|
35
|
+
v = v.strip()
|
|
36
|
+
if not v:
|
|
37
|
+
return
|
|
38
|
+
t.add_row(Text(k, style="bold"), Text(v, style=style or ""))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def render_doc_ref(pm: DocRefPM, *, ctx: RenderCtx):
|
|
42
|
+
blocks = []
|
|
43
|
+
|
|
44
|
+
# Header
|
|
45
|
+
t = _kv_table(ctx)
|
|
46
|
+
_add_kv(t, "kind", "doc_ref", style="magenta")
|
|
47
|
+
if getattr(ctx, "show_ids", False):
|
|
48
|
+
_add_kv(t, "art_id", pm.core.art_id, style="dim")
|
|
49
|
+
_add_kv(t, "meta", pm.meta)
|
|
50
|
+
|
|
51
|
+
if t.row_count > 0:
|
|
52
|
+
blocks.append(t)
|
|
53
|
+
|
|
54
|
+
# Text section
|
|
55
|
+
text = (pm.text or "").strip()
|
|
56
|
+
|
|
57
|
+
blocks.append(Rule("text"))
|
|
58
|
+
|
|
59
|
+
if getattr(ctx, "show_code", True):
|
|
60
|
+
if text:
|
|
61
|
+
blocks.append(Syntax(text, "markdown", word_wrap=True))
|
|
62
|
+
else:
|
|
63
|
+
blocks.append(Text("(empty)", style="dim"))
|
|
64
|
+
else:
|
|
65
|
+
if not text:
|
|
66
|
+
blocks.append(Text("(empty)", style="dim"))
|
|
67
|
+
else:
|
|
68
|
+
snippet = text if len(text) <= 200 else text[:200] + "…"
|
|
69
|
+
blocks.append(Text(snippet))
|
|
70
|
+
|
|
71
|
+
return Panel(Group(*blocks), title="[bold]Doc reference[/bold]", border_style="magenta")
|
|
72
|
+
|
|
73
|
+
# -----------------------------------------------------------------------------
|
|
74
|
+
# SrcCodeRef
|
|
75
|
+
# -----------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
def _maybe_code_block(
|
|
78
|
+
*,
|
|
79
|
+
title: str,
|
|
80
|
+
content: str | None,
|
|
81
|
+
lang: str,
|
|
82
|
+
show_code: bool,
|
|
83
|
+
) -> list[object]:
|
|
84
|
+
"""
|
|
85
|
+
Returns renderables for a section that always shows something:
|
|
86
|
+
- if show_code: Syntax when present, else '(empty)'
|
|
87
|
+
- if not show_code: snippet when present, else '(empty)'
|
|
88
|
+
"""
|
|
89
|
+
blocks: list[object] = [Rule(title)]
|
|
90
|
+
text = (content or "").strip()
|
|
91
|
+
|
|
92
|
+
if not text:
|
|
93
|
+
blocks.append(Text("(empty)", style="dim"))
|
|
94
|
+
return blocks
|
|
95
|
+
|
|
96
|
+
if show_code:
|
|
97
|
+
blocks.append(Syntax(text, lang, word_wrap=True))
|
|
98
|
+
else:
|
|
99
|
+
snippet = text if len(text) <= 200 else text[:200] + "…"
|
|
100
|
+
blocks.append(Text(snippet))
|
|
101
|
+
return blocks
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def render_src_code_ref(pm: SrcCodeRefPM, *, ctx: RenderCtx):
|
|
105
|
+
blocks: list[object] = []
|
|
106
|
+
|
|
107
|
+
# --- header ---
|
|
108
|
+
t = _kv_table(ctx)
|
|
109
|
+
_add_kv(t, "kind", "src_code_ref", style="magenta")
|
|
110
|
+
|
|
111
|
+
if getattr(ctx, "show_ids", False):
|
|
112
|
+
_add_kv(t, "art_id", pm.core.art_id, style="dim")
|
|
113
|
+
|
|
114
|
+
_add_kv(t, "language", pm.language)
|
|
115
|
+
_add_kv(t, "file_path", pm.file_path)
|
|
116
|
+
_add_kv(t, "meta", pm.meta)
|
|
117
|
+
|
|
118
|
+
if t.row_count > 0:
|
|
119
|
+
blocks.append(t)
|
|
120
|
+
|
|
121
|
+
show_code = getattr(ctx, "show_code", True)
|
|
122
|
+
|
|
123
|
+
# --- src_code section (always visible; shows (empty) if missing) ---
|
|
124
|
+
lang = (pm.language or "text").lower()
|
|
125
|
+
blocks.extend(
|
|
126
|
+
_maybe_code_block(
|
|
127
|
+
title="src_code",
|
|
128
|
+
content=pm.src_code,
|
|
129
|
+
lang=lang,
|
|
130
|
+
show_code=show_code,
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# --- iml_code section (always visible; shows (empty) if missing) ---
|
|
135
|
+
blocks.extend(
|
|
136
|
+
_maybe_code_block(
|
|
137
|
+
title="iml_code",
|
|
138
|
+
content=pm.iml_code,
|
|
139
|
+
lang="ocaml",
|
|
140
|
+
show_code=show_code,
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return Panel(Group(*blocks), title="[bold]Source code reference[/bold]", border_style="magenta")
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# speclogician/presentation/renderers/domain.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.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.domain import DomainModelPM
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def render_domain_model(pm: DomainModelPM, *, ctx: RenderCtx):
|
|
21
|
+
b = box.MINIMAL if ctx.compact else box.SIMPLE
|
|
22
|
+
|
|
23
|
+
c = pm.counts
|
|
24
|
+
code = pm.code
|
|
25
|
+
|
|
26
|
+
stats = Table(
|
|
27
|
+
show_header=False,
|
|
28
|
+
box=b,
|
|
29
|
+
show_edge=False,
|
|
30
|
+
pad_edge=False,
|
|
31
|
+
expand=False, # ← critical change
|
|
32
|
+
)
|
|
33
|
+
stats.add_column(style="dim", no_wrap=True)
|
|
34
|
+
stats.add_column(justify="right")
|
|
35
|
+
|
|
36
|
+
def hdr(label: str) -> None:
|
|
37
|
+
stats.add_row(Text(label, style="bold magenta"), Text(""))
|
|
38
|
+
|
|
39
|
+
def row(label: str, value: object, *, bold: bool = False) -> None:
|
|
40
|
+
stats.add_row(
|
|
41
|
+
Text(" " + label),
|
|
42
|
+
Text(str(value), style="bold" if bold else "")
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# ----------------------------
|
|
46
|
+
# Base
|
|
47
|
+
# ----------------------------
|
|
48
|
+
hdr("Base")
|
|
49
|
+
row("Base status", c.base_status, bold=True)
|
|
50
|
+
row("Has state", c.base_has_state)
|
|
51
|
+
row("Has action", c.base_has_action)
|
|
52
|
+
|
|
53
|
+
# ----------------------------
|
|
54
|
+
# Predicates
|
|
55
|
+
# ----------------------------
|
|
56
|
+
hdr("Predicates")
|
|
57
|
+
row("State total", c.num_state_preds_total, bold=True)
|
|
58
|
+
row("State valid", c.num_state_preds_valid_logic)
|
|
59
|
+
row("State matched", c.num_state_preds_matched)
|
|
60
|
+
|
|
61
|
+
row("Action total", c.num_action_preds_total, bold=True)
|
|
62
|
+
row("Action valid", c.num_action_preds_valid_logic)
|
|
63
|
+
row("Action matched", c.num_action_preds_matched)
|
|
64
|
+
|
|
65
|
+
row("All total", c.num_preds_total, bold=True)
|
|
66
|
+
row("All valid", c.num_preds_valid_logic)
|
|
67
|
+
row("All matched", c.num_preds_matched)
|
|
68
|
+
|
|
69
|
+
# ----------------------------
|
|
70
|
+
# Transitions
|
|
71
|
+
# ----------------------------
|
|
72
|
+
hdr("Transitions")
|
|
73
|
+
row("Total", c.num_trans_total, bold=True)
|
|
74
|
+
row("Valid", c.num_trans_valid_logic)
|
|
75
|
+
row("Matched", c.num_trans_matched)
|
|
76
|
+
|
|
77
|
+
if ctx.show_stats_only or not ctx.show_code:
|
|
78
|
+
return Panel(stats, title="[bold]Domain model[/bold]", border_style="magenta")
|
|
79
|
+
|
|
80
|
+
blocks: list[object] = [
|
|
81
|
+
Panel(stats, title="[bold]Stats[/bold]", border_style="magenta"),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Base code
|
|
85
|
+
if code.base.strip():
|
|
86
|
+
blocks.append(
|
|
87
|
+
Panel(
|
|
88
|
+
Syntax(code.base, "ocaml", word_wrap=True, line_numbers=not ctx.compact),
|
|
89
|
+
title="Base",
|
|
90
|
+
border_style="cyan",
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Predicates code
|
|
95
|
+
if code.state_preds_iml.strip():
|
|
96
|
+
blocks.append(
|
|
97
|
+
Panel(
|
|
98
|
+
Syntax(code.state_preds_iml, "ocaml", word_wrap=True, line_numbers=not ctx.compact),
|
|
99
|
+
title="State predicates",
|
|
100
|
+
border_style="green",
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if code.action_preds_iml.strip():
|
|
105
|
+
blocks.append(
|
|
106
|
+
Panel(
|
|
107
|
+
Syntax(code.action_preds_iml, "ocaml", word_wrap=True, line_numbers=not ctx.compact),
|
|
108
|
+
title="Action predicates",
|
|
109
|
+
border_style="green",
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Transitions code
|
|
114
|
+
if code.transitions_iml.strip():
|
|
115
|
+
blocks.append(
|
|
116
|
+
Panel(
|
|
117
|
+
Syntax(code.transitions_iml, "ocaml", word_wrap=True, line_numbers=not ctx.compact),
|
|
118
|
+
title="Transitions",
|
|
119
|
+
border_style="blue",
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return Panel(Group(*blocks), title="[bold]Domain model[/bold]", border_style="magenta")
|