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
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# presentation/renderers/state_instance_summary.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.text import Text
|
|
13
|
-
|
|
14
|
-
from speclogician.presentation.ctx import RenderCtx
|
|
15
|
-
from speclogician.presentation.models.state_instance_summary import StateInstanceSummaryPM
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def render_state_instance_summary(pm: StateInstanceSummaryPM, *, ctx: RenderCtx):
|
|
19
|
-
def kv(k: str, v: object, *, k_w: int = 22) -> Text:
|
|
20
|
-
# left-align key, keep values readable; no fancy tables
|
|
21
|
-
return Text(f"{k:<{k_w}} {v}")
|
|
22
|
-
|
|
23
|
-
blocks: list[Text] = []
|
|
24
|
-
|
|
25
|
-
# Header
|
|
26
|
-
blocks.append(Text("State instance", style="bold"))
|
|
27
|
-
blocks.append(Rule(style="dim"))
|
|
28
|
-
|
|
29
|
-
# Top-level
|
|
30
|
-
blocks.append(kv("created_at:", pm.created_at))
|
|
31
|
-
blocks.append(kv("changes:", pm.num_changes))
|
|
32
|
-
blocks.append(Text(""))
|
|
33
|
-
|
|
34
|
-
# Domain model
|
|
35
|
-
blocks.append(Text("Domain model", style="bold"))
|
|
36
|
-
blocks.append(kv("base_status:", pm.base_status))
|
|
37
|
-
blocks.append(kv("has_state/action:", f"{pm.base_has_state} / {pm.base_has_action}"))
|
|
38
|
-
|
|
39
|
-
blocks.append(
|
|
40
|
-
kv(
|
|
41
|
-
"state preds:",
|
|
42
|
-
f"{pm.num_state_preds_total} total, "
|
|
43
|
-
f"{pm.num_state_preds_matched} matched, "
|
|
44
|
-
f"{pm.num_state_preds_valid_logic} logic-valid, "
|
|
45
|
-
f"{pm.num_state_preds_errored} errored",
|
|
46
|
-
)
|
|
47
|
-
)
|
|
48
|
-
blocks.append(
|
|
49
|
-
kv(
|
|
50
|
-
"action preds:",
|
|
51
|
-
f"{pm.num_action_preds_total} total, "
|
|
52
|
-
f"{pm.num_action_preds_matched} matched, "
|
|
53
|
-
f"{pm.num_action_preds_valid_logic} logic-valid, "
|
|
54
|
-
f"{pm.num_action_preds_errored} errored",
|
|
55
|
-
)
|
|
56
|
-
)
|
|
57
|
-
blocks.append(
|
|
58
|
-
kv(
|
|
59
|
-
"predicates:",
|
|
60
|
-
f"{pm.num_preds_total} total, "
|
|
61
|
-
f"{pm.num_preds_matched} matched, "
|
|
62
|
-
f"{pm.num_preds_valid_logic} logic-valid, "
|
|
63
|
-
f"{pm.num_preds_errored} errored",
|
|
64
|
-
)
|
|
65
|
-
)
|
|
66
|
-
blocks.append(
|
|
67
|
-
kv(
|
|
68
|
-
"transitions:",
|
|
69
|
-
f"{pm.num_trans_total} total, "
|
|
70
|
-
f"{pm.num_trans_matched} matched, "
|
|
71
|
-
f"{pm.num_trans_valid_logic} logic-valid, "
|
|
72
|
-
f"{pm.num_trans_errored} errored",
|
|
73
|
-
)
|
|
74
|
-
)
|
|
75
|
-
blocks.append(Text(""))
|
|
76
|
-
|
|
77
|
-
# Spec / scenarios
|
|
78
|
-
blocks.append(Text("Spec / scenarios", style="bold"))
|
|
79
|
-
blocks.append(
|
|
80
|
-
kv(
|
|
81
|
-
"scenarios:",
|
|
82
|
-
f"{pm.num_sc_total} total, "
|
|
83
|
-
f"{pm.num_sc_missing} missing, "
|
|
84
|
-
f"{pm.num_sc_matched} matched, "
|
|
85
|
-
f"{pm.num_sc_inconsistent} inconsistent",
|
|
86
|
-
)
|
|
87
|
-
)
|
|
88
|
-
blocks.append(
|
|
89
|
-
kv(
|
|
90
|
-
"conflicts:",
|
|
91
|
-
f"{pm.num_sc_conflicted} total, "
|
|
92
|
-
f"{pm.num_sc_overlap} overlap, "
|
|
93
|
-
f"{pm.num_sc_consumed} consumed",
|
|
94
|
-
)
|
|
95
|
-
)
|
|
96
|
-
blocks.append(Text(""))
|
|
97
|
-
|
|
98
|
-
# Artifacts
|
|
99
|
-
blocks.append(Text("Artifacts", style="bold"))
|
|
100
|
-
blocks.append(
|
|
101
|
-
kv(
|
|
102
|
-
"test traces:",
|
|
103
|
-
f"{pm.num_test_traces_total} total, "
|
|
104
|
-
f"{pm.num_test_traces_logic_good} logic-good, "
|
|
105
|
-
f"{pm.num_test_traces_matched} matched",
|
|
106
|
-
)
|
|
107
|
-
)
|
|
108
|
-
blocks.append(
|
|
109
|
-
kv(
|
|
110
|
-
"log traces:",
|
|
111
|
-
f"{pm.num_log_traces_total} total, "
|
|
112
|
-
f"{pm.num_log_traces_logic_good} logic-good, "
|
|
113
|
-
f"{pm.num_log_traces_matched} matched",
|
|
114
|
-
)
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
# NEW: src + docs (optional)
|
|
118
|
-
# Only show if ctx allows and the fields exist on the PM (they should, after your model update).
|
|
119
|
-
if getattr(ctx, "show_artifacts", True):
|
|
120
|
-
if (pm.num_src_code_arts_total is not None) or (pm.num_src_code_arts_matched is not None):
|
|
121
|
-
blocks.append(
|
|
122
|
-
kv(
|
|
123
|
-
"src code:",
|
|
124
|
-
f"{pm.num_src_code_arts_total} total, "
|
|
125
|
-
f"{pm.num_src_code_arts_matched} matched",
|
|
126
|
-
)
|
|
127
|
-
)
|
|
128
|
-
if (pm.num_doc_arts_total is not None) or (pm.num_doc_arts_matched is not None):
|
|
129
|
-
blocks.append(
|
|
130
|
-
kv(
|
|
131
|
-
"docs:",
|
|
132
|
-
f"{pm.num_doc_arts_total} total, "
|
|
133
|
-
f"{pm.num_doc_arts_matched} matched",
|
|
134
|
-
)
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
body = Group(*blocks)
|
|
138
|
-
|
|
139
|
-
return Panel(
|
|
140
|
-
body,
|
|
141
|
-
title="[bold]Summary[/bold]",
|
|
142
|
-
border_style="magenta",
|
|
143
|
-
)
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/presentation/renderers/trace.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
from rich import box
|
|
10
|
-
from rich.panel import Panel
|
|
11
|
-
from rich.console import Group
|
|
12
|
-
from rich.rule import Rule
|
|
13
|
-
from rich.table import Table
|
|
14
|
-
from rich.text import Text
|
|
15
|
-
from rich.syntax import Syntax
|
|
16
|
-
|
|
17
|
-
from speclogician.presentation.ctx import RenderCtx
|
|
18
|
-
from speclogician.presentation.models.trace import TestTracePM, LogTracePM
|
|
19
|
-
from speclogician.presentation.renderers import iml_validity_icon_style_label
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def _validity_text(iml_valid: str | None) -> Text:
|
|
23
|
-
icon, style, label = iml_validity_icon_style_label(iml_valid)
|
|
24
|
-
return Text(f"{icon} {label}", style=f"bold {style}")
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def _kv_table(ctx: RenderCtx) -> Table:
|
|
28
|
-
b = box.MINIMAL if getattr(ctx, "compact", False) else box.SIMPLE
|
|
29
|
-
t = Table(show_header=False, box=b, show_edge=False, pad_edge=False, expand=True)
|
|
30
|
-
t.add_column(style="italic dim", no_wrap=True)
|
|
31
|
-
t.add_column()
|
|
32
|
-
return t
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _code_block(title: str, code: str, *, lang: str) -> Group:
|
|
36
|
-
# No panel: just a rule + syntax
|
|
37
|
-
return Group(
|
|
38
|
-
Rule(f"[bold]{title}[/bold]"),
|
|
39
|
-
Syntax(code or "", lang, word_wrap=True),
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def render_test_trace(pm: TestTracePM, *, ctx: RenderCtx):
|
|
44
|
-
core = pm.core
|
|
45
|
-
|
|
46
|
-
t = _kv_table(ctx)
|
|
47
|
-
|
|
48
|
-
if getattr(ctx, "show_ids", False):
|
|
49
|
-
t.add_row("art_id", Text(core.art_id, style="dim"))
|
|
50
|
-
|
|
51
|
-
t.add_row("name", Text(pm.name or "-", style="bold"))
|
|
52
|
-
if pm.filepath:
|
|
53
|
-
t.add_row("filepath", Text(pm.filepath))
|
|
54
|
-
if pm.language:
|
|
55
|
-
t.add_row("language", Text(pm.language))
|
|
56
|
-
|
|
57
|
-
t.add_row("iml_valid", _validity_text(core.iml_validity.iml_valid))
|
|
58
|
-
if core.iml_validity.err:
|
|
59
|
-
t.add_row("iml_error", Text(core.iml_validity.err, style="red"))
|
|
60
|
-
|
|
61
|
-
if core.time:
|
|
62
|
-
t.add_row("time", Text(core.time, style="dim"))
|
|
63
|
-
|
|
64
|
-
blocks: list[object] = [t]
|
|
65
|
-
|
|
66
|
-
if getattr(ctx, "show_code", True):
|
|
67
|
-
blocks.append(_code_block("given : state", core.given, lang="ocaml"))
|
|
68
|
-
if core.when:
|
|
69
|
-
blocks.append(_code_block("when : action", core.when, lang="ocaml"))
|
|
70
|
-
if core.then:
|
|
71
|
-
blocks.append(_code_block("then : state", core.then, lang="ocaml"))
|
|
72
|
-
|
|
73
|
-
if pm.contents:
|
|
74
|
-
blocks.append(
|
|
75
|
-
_code_block(
|
|
76
|
-
"original test contents",
|
|
77
|
-
pm.contents,
|
|
78
|
-
lang=(pm.language or "text"),
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
return Panel(
|
|
83
|
-
Group(*blocks),
|
|
84
|
-
title="[bold]Test trace[/bold]",
|
|
85
|
-
border_style="cyan",
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
def render_log_trace(pm: LogTracePM, *, ctx: RenderCtx):
|
|
90
|
-
core = pm.core
|
|
91
|
-
|
|
92
|
-
t = _kv_table(ctx)
|
|
93
|
-
|
|
94
|
-
if getattr(ctx, "show_ids", False):
|
|
95
|
-
t.add_row("art_id", Text(core.art_id, style="dim"))
|
|
96
|
-
|
|
97
|
-
t.add_row("filename", Text(pm.filename or "-", style="bold"))
|
|
98
|
-
|
|
99
|
-
t.add_row("iml_valid", _validity_text(core.iml_validity.iml_valid))
|
|
100
|
-
if core.iml_validity.err:
|
|
101
|
-
t.add_row("iml_error", Text(core.iml_validity.err, style="red"))
|
|
102
|
-
|
|
103
|
-
if core.time:
|
|
104
|
-
t.add_row("time", Text(core.time, style="dim"))
|
|
105
|
-
|
|
106
|
-
blocks: list[object] = [t]
|
|
107
|
-
|
|
108
|
-
if getattr(ctx, "show_code", True):
|
|
109
|
-
blocks.append(_code_block("given : state", core.given, lang="ocaml"))
|
|
110
|
-
if core.when:
|
|
111
|
-
blocks.append(_code_block("when : action", core.when, lang="ocaml"))
|
|
112
|
-
if core.then:
|
|
113
|
-
blocks.append(_code_block("then : state", core.then, lang="ocaml"))
|
|
114
|
-
|
|
115
|
-
if pm.contents:
|
|
116
|
-
blocks.append(_code_block("log contents", pm.contents, lang="text"))
|
|
117
|
-
|
|
118
|
-
return Panel(
|
|
119
|
-
Group(*blocks),
|
|
120
|
-
title="[bold]Log trace[/bold]",
|
|
121
|
-
border_style="cyan",
|
|
122
|
-
)
|
speclogician/shell/app.py
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/shell/app.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import json
|
|
10
|
-
import argparse
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from cmd2 import Cmd, with_argparser
|
|
13
|
-
from rich.text import Text
|
|
14
|
-
from rich.align import Align
|
|
15
|
-
from rich.console import RenderableType
|
|
16
|
-
from rich.rule import Rule
|
|
17
|
-
|
|
18
|
-
from speclogician.utils import console
|
|
19
|
-
from speclogician.state.state import State
|
|
20
|
-
from speclogician.shell.shell_ch import ChangeShell
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
SPEC_LOGICIAN_BANNER = r"""
|
|
24
|
-
.___ .___ _________ .____ .__ .__
|
|
25
|
-
| | _____ _____ ____ __| _/___________ / _____/_____ ____ ____ | | ____ ____ |__| ____ |__|____ ____
|
|
26
|
-
| |/ \\__ \ / \ / __ |\_ __ \__ \ \_____ \\____ \_/ __ \_/ ___\| | / _ \ / ___\| |/ ___\| \__ \ / \
|
|
27
|
-
| | Y Y \/ __ \| | \/ /_/ | | | \// __ \_ / \ |_> > ___/\ \___| |__( <_> ) /_/ > \ \___| |/ __ \| | \
|
|
28
|
-
|___|__|_| (____ /___| /\____ | |__| (____ / /_______ / __/ \___ >\___ >_______ \____/\___ /|__|\___ >__(____ /___| /
|
|
29
|
-
\/ \/ \/ \/ \/ \/|__| \/ \/ \/ /_____/ \/ \/ \/
|
|
30
|
-
"""
|
|
31
|
-
|
|
32
|
-
def _print_result(res: object) -> None:
|
|
33
|
-
"""Best-effort printing for ProcessChange* results."""
|
|
34
|
-
# If your ProcessChange JSON-safe models have .model_dump(), use that.
|
|
35
|
-
if hasattr(res, "model_dump"):
|
|
36
|
-
console.print_json(json.dumps(res.model_dump(), indent=2))
|
|
37
|
-
return
|
|
38
|
-
|
|
39
|
-
# Otherwise fall back to repr / dict
|
|
40
|
-
if isinstance(res, dict):
|
|
41
|
-
console.print_json(json.dumps(res, indent=2, default=str))
|
|
42
|
-
return
|
|
43
|
-
|
|
44
|
-
console.print(res)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class SpecLogicianShell(Cmd):
|
|
48
|
-
intro = "SpecLogician shell. Type 'help' or '?' to list commands.\n"
|
|
49
|
-
prompt = "sl> "
|
|
50
|
-
allow_cli_args = False
|
|
51
|
-
|
|
52
|
-
def __init__(self, *args, **kwargs):
|
|
53
|
-
# Important: prevent cmd2 from parsing outer CLI args
|
|
54
|
-
self.allow_cli_args = False
|
|
55
|
-
|
|
56
|
-
super().__init__(*args, **kwargs)
|
|
57
|
-
|
|
58
|
-
# Rich console that writes to the SAME stream cmd2 uses
|
|
59
|
-
# (cmd2 writes to self.stdout)
|
|
60
|
-
self.rich = console
|
|
61
|
-
|
|
62
|
-
self.state = None
|
|
63
|
-
self.state_path = None
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def preloop(self) -> None:
|
|
67
|
-
console.print(
|
|
68
|
-
Align.left(
|
|
69
|
-
Text(SPEC_LOGICIAN_BANNER, style="bold magenta")
|
|
70
|
-
)
|
|
71
|
-
)
|
|
72
|
-
console.print(
|
|
73
|
-
Align.left(
|
|
74
|
-
Text("SpecLogician Interactive Shell - www.speclogician.dev", style="italic dim")
|
|
75
|
-
)
|
|
76
|
-
)
|
|
77
|
-
console.print()
|
|
78
|
-
|
|
79
|
-
# ---- helpers ----
|
|
80
|
-
def rprint(self, *renderables: RenderableType) -> None:
|
|
81
|
-
"""Print rich renderables to cmd2 stdout."""
|
|
82
|
-
for r in renderables:
|
|
83
|
-
self.rich.print(r)
|
|
84
|
-
|
|
85
|
-
def info(self, msg: str) -> None:
|
|
86
|
-
self.rich.print(f"[info]{msg}[/info]")
|
|
87
|
-
|
|
88
|
-
def ok(self, msg: str) -> None:
|
|
89
|
-
self.rich.print(f"[ok]✓ {msg}[/ok]")
|
|
90
|
-
|
|
91
|
-
def warn(self, msg: str) -> None:
|
|
92
|
-
self.rich.print(f"[warn]⚠ {msg}[/warn]")
|
|
93
|
-
|
|
94
|
-
def err(self, msg: str) -> None:
|
|
95
|
-
self.rich.print(f"[err]✗ {msg}[/err]")
|
|
96
|
-
|
|
97
|
-
# -------------------------
|
|
98
|
-
# Session management
|
|
99
|
-
# -------------------------
|
|
100
|
-
|
|
101
|
-
# ✅ programmatic API: pass a real State object
|
|
102
|
-
def open_state(self, st: State, *, path: str | Path | None = None) -> None:
|
|
103
|
-
self.state = st
|
|
104
|
-
self.state_path = Path(path) if path is not None else None
|
|
105
|
-
if self.state_path:
|
|
106
|
-
self.poutput(f"Loaded state object (path={self.state_path})")
|
|
107
|
-
else:
|
|
108
|
-
self.poutput("Loaded state object (in-memory)")
|
|
109
|
-
|
|
110
|
-
open_parser = argparse.ArgumentParser()
|
|
111
|
-
open_parser.add_argument("path", help="Path to state.json")
|
|
112
|
-
@with_argparser(open_parser)
|
|
113
|
-
def do_open(self, args) -> None:
|
|
114
|
-
p = Path(args.path)
|
|
115
|
-
st = State.from_json(p) # your actual loader
|
|
116
|
-
self.open_state(st, path=p)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def do_status(self, _arg) -> None:
|
|
120
|
-
"""Show current state/spec summary."""
|
|
121
|
-
if self.state is None:
|
|
122
|
-
self.warn("No state loaded.")
|
|
123
|
-
return
|
|
124
|
-
|
|
125
|
-
cur = self.state.curr_state()
|
|
126
|
-
|
|
127
|
-
# Header that won't render as a black rectangle
|
|
128
|
-
self.rprint(Rule("[bold]Status[/bold]"))
|
|
129
|
-
|
|
130
|
-
# Your DomainModel.__rich__ already returns a Table
|
|
131
|
-
self.rprint(cur)
|
|
132
|
-
|
|
133
|
-
def do_save(self, _args) -> None:
|
|
134
|
-
if not self.state or not self.state_path:
|
|
135
|
-
self.perror("No state loaded.")
|
|
136
|
-
return
|
|
137
|
-
self.state.save_to_path(self.state_path) # adapt to your actual saver
|
|
138
|
-
self.poutput(f"Saved {self.state_path}")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
# -------------------------
|
|
142
|
-
# Output mode
|
|
143
|
-
# -------------------------
|
|
144
|
-
|
|
145
|
-
def do_json(self, arg) -> None:
|
|
146
|
-
"""
|
|
147
|
-
Toggle JSON output mode (affects process_change json flag).
|
|
148
|
-
Usage:
|
|
149
|
-
json on
|
|
150
|
-
json off
|
|
151
|
-
"""
|
|
152
|
-
a = (arg or "").strip().lower()
|
|
153
|
-
if a in ("on", "true", "1"):
|
|
154
|
-
self.json_default = True
|
|
155
|
-
elif a in ("off", "false", "0"):
|
|
156
|
-
self.json_default = False
|
|
157
|
-
else:
|
|
158
|
-
self.poutput(f"json_default={self.json_default}")
|
|
159
|
-
return
|
|
160
|
-
self.poutput(f"json_default={self.json_default}")
|
|
161
|
-
|
|
162
|
-
def do_ch(self, _arg) -> None:
|
|
163
|
-
"""Enter the change sub-shell (pred/trans/scenario/base/...)."""
|
|
164
|
-
ChangeShell(self).cmdloop()
|
|
165
|
-
|
|
166
|
-
def main():
|
|
167
|
-
SpecLogicianShell().cmdloop()
|
|
168
|
-
|
|
169
|
-
if __name__ == "__main__":
|
|
170
|
-
main()
|
speclogician/shell/shell_ch.py
DELETED
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/shell/ch_shell.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import argparse
|
|
10
|
-
import json
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Optional
|
|
13
|
-
|
|
14
|
-
from cmd2 import Cmd, with_argparser
|
|
15
|
-
|
|
16
|
-
from speclogician.modeling.predicates import PredicateType
|
|
17
|
-
from speclogician.modeling.scenario import ScenarioDelta
|
|
18
|
-
from speclogician.state.change import (
|
|
19
|
-
DomainModelBaseEdit,
|
|
20
|
-
PredicateAdd,
|
|
21
|
-
PredicateEdit,
|
|
22
|
-
PredicateRemove,
|
|
23
|
-
TransitionAdd,
|
|
24
|
-
TransitionEdit,
|
|
25
|
-
TransitionRemove,
|
|
26
|
-
ScenarioAdd,
|
|
27
|
-
ScenarioEdit,
|
|
28
|
-
ScenarioRemove,
|
|
29
|
-
# later: AddTestTrace, EditTestTrace, AddLogTrace, ...
|
|
30
|
-
)
|
|
31
|
-
from speclogician.utils.load import load_state
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def _print_result(cmd: Cmd, res: object) -> None:
|
|
35
|
-
"""
|
|
36
|
-
Best-effort printer for ProcessChangeResult / ProcessChangeObjectResult.
|
|
37
|
-
- dict: dump as JSON
|
|
38
|
-
- pydantic-ish: model_dump
|
|
39
|
-
- fallback: str()
|
|
40
|
-
"""
|
|
41
|
-
if isinstance(res, dict):
|
|
42
|
-
cmd.poutput(json.dumps(res, indent=2, default=str))
|
|
43
|
-
return
|
|
44
|
-
if hasattr(res, "model_dump"):
|
|
45
|
-
cmd.poutput(json.dumps(res.model_dump(mode="json"), indent=2, default=str)) # type: ignore[attr-defined]
|
|
46
|
-
return
|
|
47
|
-
cmd.poutput(str(res))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _split_csv(s: Optional[str]) -> list[str]:
|
|
51
|
-
if not s:
|
|
52
|
-
return []
|
|
53
|
-
return [x.strip() for x in s.split(",") if x.strip()]
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _merge_dedup_preserve_order(*lists: list[str]) -> list[str]:
|
|
57
|
-
out: list[str] = []
|
|
58
|
-
seen: set[str] = set()
|
|
59
|
-
for xs in lists:
|
|
60
|
-
for x in xs:
|
|
61
|
-
if x not in seen:
|
|
62
|
-
out.append(x)
|
|
63
|
-
seen.add(x)
|
|
64
|
-
return out
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def _delta_from_add(xs: list[str]) -> ScenarioDelta | None:
|
|
68
|
-
d = ScenarioDelta(add=xs)
|
|
69
|
-
return None if d.is_empty() else d
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
class ChangeShell(Cmd):
|
|
73
|
-
"""
|
|
74
|
-
Nested cmd2 shell that exposes the same 'ch' operations as the CLI.
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
prompt = "sl ch> "
|
|
78
|
-
intro = "Change commands (type 'help' to list, 'exit' to return)\n"
|
|
79
|
-
allow_cli_args = False
|
|
80
|
-
|
|
81
|
-
def __init__(self, parent: Cmd):
|
|
82
|
-
super().__init__(allow_cli_args=False)
|
|
83
|
-
self.parent = parent
|
|
84
|
-
|
|
85
|
-
# -------------------------
|
|
86
|
-
# shared accessors
|
|
87
|
-
# -------------------------
|
|
88
|
-
|
|
89
|
-
def _get_state(self):
|
|
90
|
-
"""
|
|
91
|
-
Use parent.state if present, otherwise load state from default location.
|
|
92
|
-
"""
|
|
93
|
-
st = getattr(self.parent, "state", None)
|
|
94
|
-
if st is None:
|
|
95
|
-
st = load_state()
|
|
96
|
-
setattr(self.parent, "state", st)
|
|
97
|
-
return st
|
|
98
|
-
|
|
99
|
-
def _json(self) -> bool:
|
|
100
|
-
return bool(getattr(self.parent, "json_default", False))
|
|
101
|
-
|
|
102
|
-
# -------------------------
|
|
103
|
-
# base-edit (matches Typer: ch base-edit --src/--file)
|
|
104
|
-
# -------------------------
|
|
105
|
-
|
|
106
|
-
base_edit_parser = argparse.ArgumentParser()
|
|
107
|
-
g = base_edit_parser.add_mutually_exclusive_group(required=True)
|
|
108
|
-
g.add_argument("--src", help="IML source code for the domain base component")
|
|
109
|
-
g.add_argument("--file", type=Path, help="Path to a file containing IML base source code")
|
|
110
|
-
@with_argparser(base_edit_parser)
|
|
111
|
-
def do_base_edit(self, args) -> None:
|
|
112
|
-
st = self._get_state()
|
|
113
|
-
|
|
114
|
-
src: str
|
|
115
|
-
if args.src is not None:
|
|
116
|
-
src = args.src
|
|
117
|
-
else:
|
|
118
|
-
p: Path = args.file
|
|
119
|
-
src = p.read_text()
|
|
120
|
-
|
|
121
|
-
change = DomainModelBaseEdit(new_base_src=src)
|
|
122
|
-
res = st.process_change(change, json_only=self._json())
|
|
123
|
-
_print_result(self, res)
|
|
124
|
-
|
|
125
|
-
# -------------------------
|
|
126
|
-
# predicates
|
|
127
|
-
# -------------------------
|
|
128
|
-
|
|
129
|
-
pred_add_parser = argparse.ArgumentParser()
|
|
130
|
-
pred_add_parser.add_argument("--pred-type", choices=["state", "action"], required=True)
|
|
131
|
-
pred_add_parser.add_argument("--pred-name", required=True)
|
|
132
|
-
pred_add_parser.add_argument("--pred-src", required=True)
|
|
133
|
-
@with_argparser(pred_add_parser)
|
|
134
|
-
def do_pred_add(self, args) -> None:
|
|
135
|
-
st = self._get_state()
|
|
136
|
-
ptype = PredicateType.STATE if args.pred_type == "state" else PredicateType.ACTION
|
|
137
|
-
change = PredicateAdd(pred_type=ptype, pred_name=args.pred_name, pred_src=args.pred_src)
|
|
138
|
-
res = st.process_change(change, json_only=self._json())
|
|
139
|
-
_print_result(self, res)
|
|
140
|
-
|
|
141
|
-
pred_edit_parser = argparse.ArgumentParser()
|
|
142
|
-
pred_edit_parser.add_argument("--pred-name", required=True)
|
|
143
|
-
pred_edit_parser.add_argument("--pred-src", required=True)
|
|
144
|
-
@with_argparser(pred_edit_parser)
|
|
145
|
-
def do_pred_edit(self, args) -> None:
|
|
146
|
-
st = self._get_state()
|
|
147
|
-
change = PredicateEdit(pred_name=args.pred_name, pred_src=args.pred_src)
|
|
148
|
-
res = st.process_change(change, json_only=self._json())
|
|
149
|
-
_print_result(self, res)
|
|
150
|
-
|
|
151
|
-
pred_remove_parser = argparse.ArgumentParser()
|
|
152
|
-
pred_remove_parser.add_argument("--pred-name", required=True)
|
|
153
|
-
@with_argparser(pred_remove_parser)
|
|
154
|
-
def do_pred_remove(self, args) -> None:
|
|
155
|
-
st = self._get_state()
|
|
156
|
-
change = PredicateRemove(pred_name=args.pred_name)
|
|
157
|
-
res = st.process_change(change, json_only=self._json())
|
|
158
|
-
_print_result(self, res)
|
|
159
|
-
|
|
160
|
-
# -------------------------
|
|
161
|
-
# transitions
|
|
162
|
-
# -------------------------
|
|
163
|
-
|
|
164
|
-
trans_add_parser = argparse.ArgumentParser()
|
|
165
|
-
trans_add_parser.add_argument("--trans-name", required=True)
|
|
166
|
-
trans_add_parser.add_argument("--trans-src", required=True)
|
|
167
|
-
@with_argparser(trans_add_parser)
|
|
168
|
-
def do_trans_add(self, args) -> None:
|
|
169
|
-
st = self._get_state()
|
|
170
|
-
change = TransitionAdd(trans_name=args.trans_name, trans_src=args.trans_src)
|
|
171
|
-
res = st.process_change(change, json_only=self._json())
|
|
172
|
-
_print_result(self, res)
|
|
173
|
-
|
|
174
|
-
trans_edit_parser = argparse.ArgumentParser()
|
|
175
|
-
trans_edit_parser.add_argument("--trans-name", required=True)
|
|
176
|
-
trans_edit_parser.add_argument("--trans-src", required=True)
|
|
177
|
-
@with_argparser(trans_edit_parser)
|
|
178
|
-
def do_trans_edit(self, args) -> None:
|
|
179
|
-
st = self._get_state()
|
|
180
|
-
change = TransitionEdit(trans_name=args.trans_name, trans_src=args.trans_src)
|
|
181
|
-
res = st.process_change(change, json_only=self._json())
|
|
182
|
-
_print_result(self, res)
|
|
183
|
-
|
|
184
|
-
trans_remove_parser = argparse.ArgumentParser()
|
|
185
|
-
trans_remove_parser.add_argument("--trans-name", required=True)
|
|
186
|
-
@with_argparser(trans_remove_parser)
|
|
187
|
-
def do_trans_remove(self, args) -> None:
|
|
188
|
-
st = self._get_state()
|
|
189
|
-
change = TransitionRemove(trans_name=args.trans_name)
|
|
190
|
-
res = st.process_change(change, json_only=self._json())
|
|
191
|
-
_print_result(self, res)
|
|
192
|
-
|
|
193
|
-
# -------------------------
|
|
194
|
-
# scenarios
|
|
195
|
-
# -------------------------
|
|
196
|
-
|
|
197
|
-
scenario_add_parser = argparse.ArgumentParser()
|
|
198
|
-
scenario_add_parser.add_argument("scenario_name")
|
|
199
|
-
scenario_add_parser.add_argument("--given", action="append", default=[], help="Repeatable")
|
|
200
|
-
scenario_add_parser.add_argument("--when", action="append", default=[], help="Repeatable")
|
|
201
|
-
scenario_add_parser.add_argument("--then", action="append", default=[], help="Repeatable")
|
|
202
|
-
scenario_add_parser.add_argument("--given-list", default=None)
|
|
203
|
-
scenario_add_parser.add_argument("--when-list", default=None)
|
|
204
|
-
scenario_add_parser.add_argument("--then-list", default=None)
|
|
205
|
-
@with_argparser(scenario_add_parser)
|
|
206
|
-
def do_scenario_add(self, args) -> None:
|
|
207
|
-
st = self._get_state()
|
|
208
|
-
|
|
209
|
-
given_all = _merge_dedup_preserve_order(args.given, _split_csv(args.given_list))
|
|
210
|
-
when_all = _merge_dedup_preserve_order(args.when, _split_csv(args.when_list))
|
|
211
|
-
then_all = _merge_dedup_preserve_order(args.then, _split_csv(args.then_list))
|
|
212
|
-
|
|
213
|
-
change = ScenarioAdd(
|
|
214
|
-
scenario_name=args.scenario_name,
|
|
215
|
-
given=_delta_from_add(given_all),
|
|
216
|
-
when=_delta_from_add(when_all),
|
|
217
|
-
then=_delta_from_add(then_all),
|
|
218
|
-
)
|
|
219
|
-
res = st.process_change(change, json_only=self._json())
|
|
220
|
-
_print_result(self, res)
|
|
221
|
-
|
|
222
|
-
scenario_edit_parser = argparse.ArgumentParser()
|
|
223
|
-
scenario_edit_parser.add_argument("scenario_name")
|
|
224
|
-
scenario_edit_parser.add_argument("--given", default=None)
|
|
225
|
-
scenario_edit_parser.add_argument("--when", default=None)
|
|
226
|
-
scenario_edit_parser.add_argument("--then", default=None)
|
|
227
|
-
@with_argparser(scenario_edit_parser)
|
|
228
|
-
def do_scenario_edit(self, args) -> None:
|
|
229
|
-
st = self._get_state()
|
|
230
|
-
|
|
231
|
-
# NOTE: to match CLI behavior, reuse the SAME parser you use in Typer.
|
|
232
|
-
# If you already have parse_scenario_delta(), import it and use it here.
|
|
233
|
-
from speclogician.commands.utils import parse_scenario_delta # adjust import to your actual module
|
|
234
|
-
|
|
235
|
-
change = ScenarioEdit(
|
|
236
|
-
scenario_name=args.scenario_name,
|
|
237
|
-
given=parse_scenario_delta(args.given),
|
|
238
|
-
when=parse_scenario_delta(args.when),
|
|
239
|
-
then=parse_scenario_delta(args.then),
|
|
240
|
-
)
|
|
241
|
-
res = st.process_change(change, json_only=self._json())
|
|
242
|
-
_print_result(self, res)
|
|
243
|
-
|
|
244
|
-
scenario_remove_parser = argparse.ArgumentParser()
|
|
245
|
-
scenario_remove_parser.add_argument("scenario_name")
|
|
246
|
-
@with_argparser(scenario_remove_parser)
|
|
247
|
-
def do_scenario_remove(self, args) -> None:
|
|
248
|
-
st = self._get_state()
|
|
249
|
-
change = ScenarioRemove(scenario_name=args.scenario_name)
|
|
250
|
-
res = st.process_change(change, json_only=self._json())
|
|
251
|
-
_print_result(self, res)
|
|
252
|
-
|
|
253
|
-
# -------------------------
|
|
254
|
-
# navigation
|
|
255
|
-
# -------------------------
|
|
256
|
-
|
|
257
|
-
def do_exit(self, _arg) -> bool:
|
|
258
|
-
"""Return to the main shell."""
|
|
259
|
-
return True
|
|
260
|
-
|
|
261
|
-
def do_EOF(self, _arg) -> bool:
|
|
262
|
-
"""Ctrl-D exits this sub-shell."""
|
|
263
|
-
return True
|