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
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Imandra Inc.
|
|
3
|
+
#
|
|
4
|
+
# overlay.py
|
|
5
|
+
#
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import yaml, os
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
from rich import print
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from typing import List, Optional
|
|
13
|
+
from rich.panel import Panel
|
|
14
|
+
from rich.table import Table
|
|
15
|
+
from rich.syntax import Syntax
|
|
16
|
+
from rich.markdown import Markdown
|
|
17
|
+
from rich.console import RenderableType
|
|
18
|
+
|
|
19
|
+
# ----------------------------
|
|
20
|
+
# Example field
|
|
21
|
+
# ----------------------------
|
|
22
|
+
|
|
23
|
+
class Example(BaseModel):
|
|
24
|
+
source: str # original snippet in the source DSL/language
|
|
25
|
+
iml: str # expected IML output
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ----------------------------
|
|
29
|
+
# Mapping section
|
|
30
|
+
# ----------------------------
|
|
31
|
+
|
|
32
|
+
class TitleMapping(BaseModel):
|
|
33
|
+
type: str
|
|
34
|
+
pattern: str
|
|
35
|
+
extract: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SectionMapping(BaseModel):
|
|
39
|
+
keywords: Optional[List[str]] = None
|
|
40
|
+
chained_keywords: Optional[List[str]] = None
|
|
41
|
+
section: Optional[str] = None
|
|
42
|
+
transform: Optional[str] = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Mapping(BaseModel):
|
|
46
|
+
title: TitleMapping
|
|
47
|
+
given: SectionMapping
|
|
48
|
+
when: SectionMapping
|
|
49
|
+
then: SectionMapping
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# ----------------------------
|
|
53
|
+
# Background
|
|
54
|
+
# ----------------------------
|
|
55
|
+
|
|
56
|
+
class Background(BaseModel):
|
|
57
|
+
enabled: bool
|
|
58
|
+
keywords: List[str]
|
|
59
|
+
given_keywords: List[str]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ----------------------------
|
|
63
|
+
# State configuration
|
|
64
|
+
# ----------------------------
|
|
65
|
+
|
|
66
|
+
class StateConfig(BaseModel):
|
|
67
|
+
initial_name: str
|
|
68
|
+
transition_prefix: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ----------------------------
|
|
72
|
+
# Rules and constraints
|
|
73
|
+
# ----------------------------
|
|
74
|
+
|
|
75
|
+
class Rules(BaseModel):
|
|
76
|
+
state_transitions: List[str]
|
|
77
|
+
predicates: List[str]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class YamlConstraints(BaseModel):
|
|
81
|
+
double_quote_strings: bool
|
|
82
|
+
escape_quotes: bool
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ----------------------------
|
|
86
|
+
# MAIN OVERLAY MODEL
|
|
87
|
+
# ----------------------------
|
|
88
|
+
|
|
89
|
+
class Overlay(BaseModel):
|
|
90
|
+
language: str
|
|
91
|
+
name: str
|
|
92
|
+
description: str
|
|
93
|
+
ext : str
|
|
94
|
+
|
|
95
|
+
mapping: Mapping
|
|
96
|
+
background: Background
|
|
97
|
+
state: StateConfig
|
|
98
|
+
rules: Rules
|
|
99
|
+
yaml_constraints: YamlConstraints
|
|
100
|
+
|
|
101
|
+
notes: List[str]
|
|
102
|
+
|
|
103
|
+
# NEW: structured example consisting of source + IML
|
|
104
|
+
example: Optional[Example] = None
|
|
105
|
+
|
|
106
|
+
def to_yaml (self):
|
|
107
|
+
data = self.model_dump() # Pydantic → dict
|
|
108
|
+
return yaml.safe_dump(data, sort_keys=False)
|
|
109
|
+
|
|
110
|
+
# --------------------------------------------------
|
|
111
|
+
# Rich pretty-printer
|
|
112
|
+
# --------------------------------------------------
|
|
113
|
+
def __rich__(self) -> RenderableType:
|
|
114
|
+
root = Table.grid(padding=1)
|
|
115
|
+
root.title = f"[bold cyan]Overlay ({self.language})[/bold cyan]"
|
|
116
|
+
|
|
117
|
+
# Metadata
|
|
118
|
+
meta = Table(show_header=False, box=None)
|
|
119
|
+
meta.add_row("[bold]Name[/bold]", self.name)
|
|
120
|
+
meta.add_row("[bold]Language[/bold]", self.language)
|
|
121
|
+
meta.add_row("[bold]Description[/bold]", self.description)
|
|
122
|
+
root.add_row(Panel(meta, title="Metadata", border_style="cyan"))
|
|
123
|
+
|
|
124
|
+
# Mapping
|
|
125
|
+
map_table = Table(show_header=True, header_style="bold magenta")
|
|
126
|
+
map_table.add_column("Section")
|
|
127
|
+
map_table.add_column("Keywords")
|
|
128
|
+
map_table.add_column("Chained")
|
|
129
|
+
map_table.add_column("Transform")
|
|
130
|
+
|
|
131
|
+
def fmt(xs):
|
|
132
|
+
return ", ".join(xs) if xs else ""
|
|
133
|
+
|
|
134
|
+
map_table.add_row(
|
|
135
|
+
"Title",
|
|
136
|
+
f"{self.mapping.title.type} / {self.mapping.title.pattern}",
|
|
137
|
+
"",
|
|
138
|
+
f"extract={self.mapping.title.extract}",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
for name, sec in [
|
|
142
|
+
("Given", self.mapping.given),
|
|
143
|
+
("When", self.mapping.when),
|
|
144
|
+
("Then", self.mapping.then),
|
|
145
|
+
]:
|
|
146
|
+
map_table.add_row(
|
|
147
|
+
name,
|
|
148
|
+
fmt(sec.keywords),
|
|
149
|
+
fmt(sec.chained_keywords),
|
|
150
|
+
sec.transform or "",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
root.add_row(Panel(map_table, title="Mapping", border_style="magenta"))
|
|
154
|
+
|
|
155
|
+
# Background
|
|
156
|
+
bg = Table(show_header=False, box=None)
|
|
157
|
+
bg.add_row("[bold]Enabled[/bold]", str(self.background.enabled))
|
|
158
|
+
bg.add_row("[bold]Keywords[/bold]", fmt(self.background.keywords))
|
|
159
|
+
bg.add_row("[bold]Given Keywords[/bold]", fmt(self.background.given_keywords))
|
|
160
|
+
root.add_row(Panel(bg, title="Background", border_style="green"))
|
|
161
|
+
|
|
162
|
+
# State Config
|
|
163
|
+
st = Table(show_header=False, box=None)
|
|
164
|
+
st.add_row("[bold]Initial[/bold]", self.state.initial_name)
|
|
165
|
+
st.add_row("[bold]Transition Prefix[/bold]", self.state.transition_prefix)
|
|
166
|
+
root.add_row(Panel(st, title="State Config", border_style="yellow"))
|
|
167
|
+
|
|
168
|
+
# Rules
|
|
169
|
+
rules_t = Table(show_header=True, header_style="bold blue")
|
|
170
|
+
rules_t.add_column("State Transitions")
|
|
171
|
+
rules_t.add_column("Predicates")
|
|
172
|
+
|
|
173
|
+
max_len = max(len(self.rules.state_transitions), len(self.rules.predicates))
|
|
174
|
+
for i in range(max_len):
|
|
175
|
+
left = self.rules.state_transitions[i] if i < len(self.rules.state_transitions) else ""
|
|
176
|
+
right = self.rules.predicates[i] if i < len(self.rules.predicates) else ""
|
|
177
|
+
rules_t.add_row(left, right)
|
|
178
|
+
|
|
179
|
+
root.add_row(Panel(rules_t, title="Rules", border_style="blue"))
|
|
180
|
+
|
|
181
|
+
# YAML Constraints
|
|
182
|
+
yc = Table(show_header=False, box=None)
|
|
183
|
+
yc.add_row("[bold]Double Quote Strings[/bold]", str(self.yaml_constraints.double_quote_strings))
|
|
184
|
+
yc.add_row("[bold]Escape Quotes[/bold]", str(self.yaml_constraints.escape_quotes))
|
|
185
|
+
root.add_row(Panel(yc, title="YAML Constraints", border_style="red"))
|
|
186
|
+
|
|
187
|
+
# Notes
|
|
188
|
+
notes_md = Markdown("\n".join(f"- {n}" for n in self.notes))
|
|
189
|
+
root.add_row(Panel(notes_md, title="Notes", border_style="white"))
|
|
190
|
+
|
|
191
|
+
# Structured Example
|
|
192
|
+
if self.example:
|
|
193
|
+
# Original source language
|
|
194
|
+
source_block = Syntax(
|
|
195
|
+
self.example.source,
|
|
196
|
+
self.language, # use overlay.language for syntax highlighting
|
|
197
|
+
theme="monokai",
|
|
198
|
+
line_numbers=False,
|
|
199
|
+
)
|
|
200
|
+
root.add_row(Panel(source_block, title="Example (Source)", border_style="bright_magenta"))
|
|
201
|
+
|
|
202
|
+
# IML output
|
|
203
|
+
iml_block = Syntax(
|
|
204
|
+
self.example.iml,
|
|
205
|
+
"ocaml", # Imandra IML is OCaml-like
|
|
206
|
+
theme="monokai",
|
|
207
|
+
line_numbers=False,
|
|
208
|
+
)
|
|
209
|
+
root.add_row(Panel(iml_block, title="Example (IML)", border_style="bright_green"))
|
|
210
|
+
|
|
211
|
+
return Panel(root, border_style="bright_white")
|
|
212
|
+
|
|
213
|
+
@staticmethod
|
|
214
|
+
def from_file(path:str) -> 'Overlay':
|
|
215
|
+
"""Load an overlay YAML file and parse it into the Overlay model."""
|
|
216
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
217
|
+
data = yaml.safe_load(f)
|
|
218
|
+
|
|
219
|
+
overlay : Overlay = Overlay.model_validate(data)
|
|
220
|
+
return overlay
|
|
221
|
+
|
|
222
|
+
class Overlays(BaseModel):
|
|
223
|
+
"""
|
|
224
|
+
"""
|
|
225
|
+
overlays : list[Overlay]
|
|
226
|
+
|
|
227
|
+
def list_overlays(self):
|
|
228
|
+
for o in self.overlays:
|
|
229
|
+
print ('---' * 10)
|
|
230
|
+
print (f"Name: {o.name}")
|
|
231
|
+
print (f"Language: {o.language}")
|
|
232
|
+
print (f"Description: {o.description}")
|
|
233
|
+
|
|
234
|
+
def names(self) -> list[str]:
|
|
235
|
+
return list(map(lambda x: x.name, self.overlays))
|
|
236
|
+
|
|
237
|
+
def get(self, name:str) -> Overlay|None:
|
|
238
|
+
if name in self.names():
|
|
239
|
+
return next(filter(lambda x: x.name == name, self.overlays))
|
|
240
|
+
else:
|
|
241
|
+
return None
|
|
242
|
+
|
|
243
|
+
@staticmethod
|
|
244
|
+
def from_dir(dir_path:str) -> 'Overlays':
|
|
245
|
+
overlays : list[Overlay] = []
|
|
246
|
+
for file in os.listdir(dir_path):
|
|
247
|
+
if file.endswith('.yaml'):
|
|
248
|
+
overlays.append(Overlay.from_file(os.path.join(dir_path, file)))
|
|
249
|
+
|
|
250
|
+
return Overlays(overlays=overlays)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
if __name__ == "__main__":
|
|
254
|
+
import sys
|
|
255
|
+
|
|
256
|
+
if len(sys.argv) < 2:
|
|
257
|
+
print("[red]Usage: python load_overlay.py <overlay.yaml>[/red]")
|
|
258
|
+
sys.exit(1)
|
|
259
|
+
|
|
260
|
+
overlay_file = sys.argv[1]
|
|
261
|
+
overlay = Overlay.from_file(overlay_file)
|
|
262
|
+
|
|
263
|
+
console = Console()
|
|
264
|
+
console.print(overlay)
|
speclogician/main.py
CHANGED
|
@@ -1,47 +1,26 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# main.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
|
-
__version__ = "1.0"
|
|
8
|
-
|
|
9
7
|
import typer
|
|
8
|
+
import os
|
|
10
9
|
from typing import Final
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
13
|
-
from speclogician.
|
|
14
|
-
from speclogician.
|
|
15
|
-
from speclogician.
|
|
16
|
-
from speclogician.
|
|
17
|
-
from speclogician.
|
|
18
|
-
from speclogician.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
SL_HELP : Final = """
|
|
22
|
-
:robot: [bold deep_sky_blue1][italic]SpecLogician[/italic] is an AI framework for data-driven
|
|
23
|
-
formal program specification synthesis, verification, and analysis.[/bold deep_sky_blue1] :rocket:
|
|
24
|
-
|
|
25
|
-
SpecLogician provides an LLM-native alternative to classical predicate abstraction and
|
|
26
|
-
Counterexample-Guided Abstraction Refinement (CEGAR).
|
|
12
|
+
from speclogician.cmd.agent_cmd import app as agent_app
|
|
13
|
+
from speclogician.cmd.data_cmd import app as data_app
|
|
14
|
+
from speclogician.cmd.model_cmd import app as model_app
|
|
15
|
+
from speclogician.cmd.overlay_cmd import app as overlay_app
|
|
16
|
+
from speclogician.cmd.scenario_cmd import app as scenario_app
|
|
17
|
+
from speclogician.cmd.state_cmd import app as state_app
|
|
27
18
|
|
|
28
|
-
|
|
29
|
-
SpecLogician models behavior directly using structured scenarios composed from state and
|
|
30
|
-
action predicates. Specifications are built incrementally by defining a domain base
|
|
31
|
-
(types and core invariants), predicates, transition functions, and scenarios that capture
|
|
32
|
-
intent explicitly.
|
|
19
|
+
from speclogician.utils import console
|
|
33
20
|
|
|
34
|
-
Each change creates a new immutable state, triggers precise logical analysis, and produces
|
|
35
|
-
a semantic diff against the previous state. Using ImandraX’s exact symbolic reasoning and
|
|
36
|
-
region decomposition, SpecLogician computes coverage, conflicts, reachability, and
|
|
37
|
-
behavioral complements—identifying real, reachable behaviors not yet explained by any
|
|
38
|
-
scenario.
|
|
39
21
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
This enables scalable, explainable refinement toward correctness and completeness across
|
|
43
|
-
source code, tests, documentation, and real-world traces—using small, explicit change
|
|
44
|
-
commands instead of manual file editing.
|
|
22
|
+
SL_HELP : Final = """
|
|
23
|
+
:robot: [bold deep_sky_blue1][italic]SpecLogician[/italic] is an AI framework for data-driven formal program specification synthesis, verification and analysis.[/bold deep_sky_blue1] :rocket:
|
|
45
24
|
|
|
46
25
|
Learn more at [bold italic deep_sky_blue1]https://www.speclogician.dev![/bold italic deep_sky_blue1]
|
|
47
26
|
"""
|
|
@@ -49,91 +28,46 @@ Learn more at [bold italic deep_sky_blue1]https://www.speclogician.dev![/bold it
|
|
|
49
28
|
app = typer.Typer(
|
|
50
29
|
name="SpecLogician",
|
|
51
30
|
help=SL_HELP,
|
|
52
|
-
rich_markup_mode="rich"
|
|
53
|
-
no_args_is_help=True
|
|
31
|
+
rich_markup_mode="rich"
|
|
54
32
|
)
|
|
55
33
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"""
|
|
63
|
-
SpecLogician CLI entry point.
|
|
64
|
-
"""
|
|
65
|
-
require_imandra_key()
|
|
66
|
-
|
|
67
|
-
@app.callback()
|
|
68
|
-
def main(ctx: typer.Context, state_json: Path | None = typer.Option(None, "--state-json", help="...")):
|
|
69
|
-
ctx.obj = ctx.ensure_object(AppCtx)
|
|
70
|
-
|
|
71
|
-
ctx.obj.state_json_path = state_json
|
|
72
|
-
|
|
73
|
-
if state_json:
|
|
74
|
-
try:
|
|
75
|
-
ctx.obj.state = load_state(path=state_json)
|
|
76
|
-
except Exception:
|
|
77
|
-
ctx.obj.state = None
|
|
78
|
-
else:
|
|
79
|
-
ctx.obj.state = None # ✅ ensure attribute exists
|
|
80
|
-
|
|
81
|
-
app.add_typer(view_app , name="view" , help="View components of the state")
|
|
82
|
-
app.add_typer(ch_app , name="ch" , help="Apply change to the state")
|
|
83
|
-
app.add_typer(find_app , name="find" , help="Search for a components of the state")
|
|
84
|
-
app.add_typer(demo_app , name="demo" , help="A collection of SpecLogician demos")
|
|
85
|
-
app.add_typer(preview_app , name="preview" , hidden=True) # This is an internal tool for manually inspecting renderers
|
|
86
|
-
|
|
34
|
+
app.add_typer(agent_app , name="agent" , help="Agentic functions")
|
|
35
|
+
app.add_typer(overlay_app , name="overlay" , help="Available overlays")
|
|
36
|
+
app.add_typer(data_app , name="data" , help="Functions for data artifacts")
|
|
37
|
+
app.add_typer(model_app , name="model" , help="Functions related to model")
|
|
38
|
+
app.add_typer(scenario_app , name="scenario" , help="Scenario access and modification functions")
|
|
39
|
+
app.add_typer(state_app , name="state" , help="State access and modification functions")
|
|
87
40
|
|
|
88
41
|
@app.command(help="Run the TUI")
|
|
89
42
|
def tui ():
|
|
90
|
-
"""
|
|
91
|
-
|
|
92
|
-
|
|
43
|
+
"""
|
|
44
|
+
Launch the TUI view of the state
|
|
45
|
+
"""
|
|
46
|
+
from tui.tui import SpecLogicianApp
|
|
47
|
+
tui_app = SpecLogicianApp(state)
|
|
93
48
|
tui_app.run()
|
|
94
49
|
|
|
95
50
|
@app.command(help="Generate a prompt for helping agents use SpecLogician")
|
|
96
|
-
def prompt(
|
|
97
|
-
out: Path | None = typer.Option(
|
|
98
|
-
None,
|
|
99
|
-
"--out",
|
|
100
|
-
help="Write the prompt to a file instead of stdout.",
|
|
101
|
-
),
|
|
102
|
-
):
|
|
51
|
+
def prompt():
|
|
103
52
|
"""
|
|
104
|
-
Generate a prompt for helping CLI agents use SpecLogician
|
|
53
|
+
Generate a prompt for helping CLI agents use SpecLogician
|
|
105
54
|
"""
|
|
106
55
|
|
|
107
|
-
|
|
108
|
-
repo_root = here.parents[0]
|
|
109
|
-
prompt_path = repo_root / "utils" / "prompt.md"
|
|
56
|
+
prompt_path = Path(os.path.abspath(__file__)).parent / "utils/prompt.md"
|
|
110
57
|
|
|
111
58
|
try:
|
|
112
|
-
|
|
59
|
+
contents = prompt_path.read_text()
|
|
113
60
|
except Exception as e:
|
|
114
|
-
console.print(f"🛑 Failed to read
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if out:
|
|
118
|
-
try:
|
|
119
|
-
out.write_text(txt)
|
|
120
|
-
except Exception as e:
|
|
121
|
-
console.print(f"🛑 Failed to write prompt to {out}: {e}")
|
|
122
|
-
raise typer.Exit(2)
|
|
123
|
-
typer.echo(str(out))
|
|
124
|
-
else:
|
|
125
|
-
typer.echo(txt)
|
|
61
|
+
console.print(f"🛑 Failed to read in the prompt contents: {e}")
|
|
62
|
+
return
|
|
126
63
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
json_only: bool = typer.Option(False, "--json", help="Output JSON"),
|
|
131
|
-
):
|
|
132
|
-
"""Print SpecLogician version"""
|
|
133
|
-
if json_only:
|
|
134
|
-
typer.echo(f'{{"version": "{__version__}"}}')
|
|
135
|
-
else:
|
|
136
|
-
typer.echo(__version__)
|
|
64
|
+
console.rule("[bold]Start of SpecLogician prompt[/bold]")
|
|
65
|
+
console.print(contents)
|
|
66
|
+
console.rule("[bold]End of prompt[/bold]")
|
|
137
67
|
|
|
138
68
|
if __name__ == '__main__':
|
|
139
69
|
app()
|
|
70
|
+
import sys
|
|
71
|
+
sys.exit(0)
|
|
72
|
+
print ("hello")
|
|
73
|
+
state.save(dirpath=os.getcwd())
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# Imandra Inc.
|
|
3
|
-
#
|
|
4
|
-
# speclogician/modeling/__init__.py
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import re
|
|
9
|
-
from typing import List
|
|
10
|
-
|
|
11
|
-
IML_TYPE_REGEX = re.compile(
|
|
12
|
-
r"""
|
|
13
|
-
^\s*type\s+ # 'type' keyword
|
|
14
|
-
(?P<name>[a-zA-Z_][a-zA-Z0-9_]*) # type name
|
|
15
|
-
\s*= # '=' sign
|
|
16
|
-
""",
|
|
17
|
-
re.MULTILINE | re.VERBOSE,
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
def strip_iml_comments(text: str) -> str:
|
|
21
|
-
"""Remove OCaml-style (* ... *) and // comments."""
|
|
22
|
-
# Remove block comments
|
|
23
|
-
text = re.sub(r"\(\*.*?\*\)", "", text, flags=re.DOTALL)
|
|
24
|
-
# Remove line comments
|
|
25
|
-
text = re.sub(r"//.*", "", text)
|
|
26
|
-
return text
|
|
27
|
-
|
|
28
|
-
def extract_declared_types(iml_text: str) -> List[str]:
|
|
29
|
-
"""Return all declared type names in an IML file."""
|
|
30
|
-
clean = strip_iml_comments(iml_text)
|
|
31
|
-
return [m.group("name") for m in IML_TYPE_REGEX.finditer(clean)]
|
|
@@ -1,71 +1,15 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# component.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
7
|
import uuid
|
|
8
|
-
import
|
|
9
|
-
from datetime import datetime, timezone
|
|
10
|
-
from pydantic import BaseModel, Field, field_validator, ConfigDict
|
|
11
|
-
from ..utils import IMLValidity
|
|
12
|
-
|
|
13
|
-
_NAME_RE = re.compile(r"^[a-z][a-z0-9_]*$")
|
|
14
|
-
|
|
15
|
-
# Conservative reserved set (OCaml-ish + common IML surface syntax).
|
|
16
|
-
# Add more as you run into them.
|
|
17
|
-
_IML_RESERVED: set[str] = {
|
|
18
|
-
# core keywords / syntax
|
|
19
|
-
"let", "in", "and", "rec",
|
|
20
|
-
"type", "match", "with", "function",
|
|
21
|
-
"if", "then", "else",
|
|
22
|
-
"try", "raise",
|
|
23
|
-
"module", "open", "include",
|
|
24
|
-
"begin", "end",
|
|
25
|
-
"as", "of", "when",
|
|
26
|
-
|
|
27
|
-
# booleans / common literals
|
|
28
|
-
"true", "false",
|
|
29
|
-
|
|
30
|
-
# common types / constructors that are often special-cased
|
|
31
|
-
"some", "none",
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
def validate_iml_identifier(name: str) -> str:
|
|
35
|
-
if not _NAME_RE.fullmatch(name):
|
|
36
|
-
raise ValueError(
|
|
37
|
-
f"Invalid name '{name}'. Must start with a lowercase letter and contain only "
|
|
38
|
-
"lowercase letters, digits, and underscores."
|
|
39
|
-
)
|
|
40
|
-
if name in _IML_RESERVED:
|
|
41
|
-
raise ValueError(f"Invalid name '{name}': reserved keyword in IML/OCaml surface syntax.")
|
|
42
|
-
return name
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
43
9
|
|
|
44
10
|
class ModelComponent(BaseModel):
|
|
45
11
|
"""
|
|
46
|
-
Base class for all model components (Predicates, transitions and scenarios)
|
|
47
12
|
"""
|
|
48
|
-
model_config = ConfigDict(validate_assignment=True) # optional but recommended
|
|
49
|
-
|
|
50
|
-
comp_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
51
|
-
name: str
|
|
52
|
-
|
|
53
|
-
# Track last update time (UTC)
|
|
54
|
-
last_updated: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
55
|
-
|
|
56
|
-
@field_validator("name")
|
|
57
|
-
@classmethod
|
|
58
|
-
def validate_name(cls, v: str) -> str:
|
|
59
|
-
return validate_iml_identifier(v)
|
|
60
|
-
|
|
61
|
-
def touch(self) -> None:
|
|
62
|
-
self.last_updated = datetime.now(timezone.utc)
|
|
63
|
-
|
|
64
|
-
class SrcComponent(ModelComponent):
|
|
65
|
-
src_code: str = ""
|
|
66
|
-
is_iml_valid : IMLValidity = IMLValidity.UNKNOWN
|
|
67
|
-
iml_error: str | None = None
|
|
68
13
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
self.touch()
|
|
14
|
+
comp_id : str = Field(default_factory=lambda: str(uuid.uuid4())) # assigned task ID, new one created if not provided
|
|
15
|
+
|
|
@@ -1,26 +1,12 @@
|
|
|
1
1
|
#
|
|
2
2
|
# Imandra Inc.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# conflict.py
|
|
5
5
|
#
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel
|
|
8
|
-
from typing import Literal, TypeAlias
|
|
9
8
|
|
|
10
|
-
class
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
scenario_name2 : str
|
|
15
|
-
|
|
16
|
-
details : str
|
|
17
|
-
|
|
18
|
-
class ScenarioConsumed(BaseModel):
|
|
19
|
-
""" One scenario fully absorbs another """
|
|
20
|
-
kind: Literal["consumed"] = "consumed"
|
|
21
|
-
scenario_name1 : str
|
|
22
|
-
scenario_name2 : str
|
|
23
|
-
|
|
24
|
-
details : str
|
|
25
|
-
|
|
26
|
-
ScenarioConflict : TypeAlias = ScenarioOverlap | ScenarioConsumed
|
|
9
|
+
class Conflict(BaseModel):
|
|
10
|
+
"""
|
|
11
|
+
"""
|
|
12
|
+
pass
|