speclogician 0.0.0b1__py3-none-any.whl → 0.0.0.dev1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. speclogician/agent/funcs.py +29 -0
  2. speclogician/cmd/agent_cmd.py +89 -0
  3. speclogician/cmd/data_cmd.py +24 -0
  4. speclogician/cmd/model_cmd.py +42 -0
  5. speclogician/cmd/overlay_cmd.py +30 -0
  6. speclogician/cmd/scenario_cmd.py +61 -0
  7. speclogician/cmd/state_cmd.py +52 -0
  8. speclogician/data/artifact.py +8 -50
  9. speclogician/data/container.py +18 -384
  10. speclogician/data/mapping.py +18 -17
  11. speclogician/data/refs.py +12 -11
  12. speclogician/data/reports.py +11 -0
  13. speclogician/data/traces.py +15 -6
  14. speclogician/llms/llmtools.py +102 -0
  15. speclogician/llms/overlay.py +264 -0
  16. speclogician/main.py +36 -102
  17. speclogician/modeling/__init__.py +0 -31
  18. speclogician/modeling/component.py +4 -60
  19. speclogician/modeling/conflict.py +5 -19
  20. speclogician/modeling/domain.py +93 -280
  21. speclogician/modeling/model.py +206 -0
  22. speclogician/modeling/predicates.py +20 -22
  23. speclogician/modeling/report.py +33 -0
  24. speclogician/modeling/scenario.py +119 -87
  25. speclogician/sl_cmd.py +76 -0
  26. speclogician/state/change.py +98 -378
  27. speclogician/state/state.py +183 -399
  28. speclogician/tui/box.tcss +10 -0
  29. speclogician/tui/tui.py +131 -0
  30. speclogician/utils/__init__.py +1 -70
  31. speclogician/utils/imx.py +195 -0
  32. speclogician/utils/load.py +25 -147
  33. speclogician/utils/prompt.md +1 -325
  34. speclogician-0.0.0.dev1.dist-info/METADATA +21 -0
  35. speclogician-0.0.0.dev1.dist-info/RECORD +43 -0
  36. speclogician/commands/__init__.py +0 -15
  37. speclogician/commands/cmd_ch.py +0 -616
  38. speclogician/commands/cmd_find.py +0 -256
  39. speclogician/commands/cmd_view.py +0 -202
  40. speclogician/commands/runner.py +0 -149
  41. speclogician/commands/utils.py +0 -101
  42. speclogician/demos/.DS_Store +0 -0
  43. speclogician/demos/cmd_demo.py +0 -278
  44. speclogician/demos/loader.py +0 -135
  45. speclogician/demos/model.py +0 -27
  46. speclogician/demos/runner.py +0 -51
  47. speclogician/logic/__init__.py +0 -11
  48. speclogician/logic/api/__init__.py +0 -29
  49. speclogician/logic/api/client.py +0 -606
  50. speclogician/logic/api/decomp.py +0 -67
  51. speclogician/logic/api/scenario.py +0 -102
  52. speclogician/logic/api/traces.py +0 -59
  53. speclogician/logic/lib/__init__.py +0 -19
  54. speclogician/logic/lib/complement.py +0 -107
  55. speclogician/logic/lib/domain_model.py +0 -59
  56. speclogician/logic/lib/predicates.py +0 -151
  57. speclogician/logic/lib/scenarios.py +0 -369
  58. speclogician/logic/lib/traces.py +0 -114
  59. speclogician/logic/lib/transitions.py +0 -104
  60. speclogician/logic/main.py +0 -246
  61. speclogician/logic/strings.py +0 -194
  62. speclogician/logic/utils.py +0 -135
  63. speclogician/modeling/complement.py +0 -104
  64. speclogician/modeling/spec.py +0 -306
  65. speclogician/modeling/spec_stats.py +0 -39
  66. speclogician/presentation/api.py +0 -244
  67. speclogician/presentation/builders/_links.py +0 -44
  68. speclogician/presentation/builders/container.py +0 -53
  69. speclogician/presentation/builders/data_artifact.py +0 -42
  70. speclogician/presentation/builders/domain.py +0 -54
  71. speclogician/presentation/builders/instances_list.py +0 -38
  72. speclogician/presentation/builders/predicate.py +0 -51
  73. speclogician/presentation/builders/recommendations.py +0 -41
  74. speclogician/presentation/builders/scenario.py +0 -41
  75. speclogician/presentation/builders/scenario_complement.py +0 -82
  76. speclogician/presentation/builders/smart_find.py +0 -39
  77. speclogician/presentation/builders/spec.py +0 -39
  78. speclogician/presentation/builders/state_diff.py +0 -150
  79. speclogician/presentation/builders/state_instance.py +0 -42
  80. speclogician/presentation/builders/state_instance_summary.py +0 -84
  81. speclogician/presentation/builders/trace.py +0 -58
  82. speclogician/presentation/ctx.py +0 -38
  83. speclogician/presentation/models/container.py +0 -44
  84. speclogician/presentation/models/data_artifact.py +0 -33
  85. speclogician/presentation/models/domain.py +0 -50
  86. speclogician/presentation/models/instances_list.py +0 -23
  87. speclogician/presentation/models/predicate.py +0 -60
  88. speclogician/presentation/models/recommendations.py +0 -34
  89. speclogician/presentation/models/scenario.py +0 -31
  90. speclogician/presentation/models/scenario_complement.py +0 -40
  91. speclogician/presentation/models/smart_find.py +0 -34
  92. speclogician/presentation/models/spec.py +0 -32
  93. speclogician/presentation/models/state_diff.py +0 -34
  94. speclogician/presentation/models/state_instance.py +0 -31
  95. speclogician/presentation/models/state_instance_summary.py +0 -102
  96. speclogician/presentation/models/trace.py +0 -42
  97. speclogician/presentation/preview/__init__.py +0 -13
  98. speclogician/presentation/preview/cli.py +0 -50
  99. speclogician/presentation/preview/fixtures/__init__.py +0 -205
  100. speclogician/presentation/preview/fixtures/artifact_container.py +0 -150
  101. speclogician/presentation/preview/fixtures/data_artifact.py +0 -144
  102. speclogician/presentation/preview/fixtures/domain_model.py +0 -162
  103. speclogician/presentation/preview/fixtures/instances_list.py +0 -162
  104. speclogician/presentation/preview/fixtures/predicate.py +0 -184
  105. speclogician/presentation/preview/fixtures/scenario.py +0 -84
  106. speclogician/presentation/preview/fixtures/scenario_complement.py +0 -81
  107. speclogician/presentation/preview/fixtures/smart_find.py +0 -140
  108. speclogician/presentation/preview/fixtures/spec.py +0 -95
  109. speclogician/presentation/preview/fixtures/state_diff.py +0 -158
  110. speclogician/presentation/preview/fixtures/state_instance.py +0 -128
  111. speclogician/presentation/preview/fixtures/state_instance_summary.py +0 -80
  112. speclogician/presentation/preview/fixtures/trace.py +0 -206
  113. speclogician/presentation/preview/registry.py +0 -42
  114. speclogician/presentation/renderers/__init__.py +0 -24
  115. speclogician/presentation/renderers/container.py +0 -136
  116. speclogician/presentation/renderers/data_artifact.py +0 -144
  117. speclogician/presentation/renderers/domain.py +0 -123
  118. speclogician/presentation/renderers/instances_list.py +0 -120
  119. speclogician/presentation/renderers/predicate.py +0 -180
  120. speclogician/presentation/renderers/recommendations.py +0 -90
  121. speclogician/presentation/renderers/scenario.py +0 -94
  122. speclogician/presentation/renderers/scenario_complement.py +0 -59
  123. speclogician/presentation/renderers/smart_find.py +0 -307
  124. speclogician/presentation/renderers/spec.py +0 -105
  125. speclogician/presentation/renderers/state_diff.py +0 -102
  126. speclogician/presentation/renderers/state_instance.py +0 -82
  127. speclogician/presentation/renderers/state_instance_summary.py +0 -143
  128. speclogician/presentation/renderers/trace.py +0 -122
  129. speclogician/shell/app.py +0 -170
  130. speclogician/shell/shell_ch.py +0 -263
  131. speclogician/shell/shell_view.py +0 -153
  132. speclogician/state/change_result.py +0 -32
  133. speclogician/state/diff.py +0 -191
  134. speclogician/state/inst.py +0 -574
  135. speclogician/state/recommendation.py +0 -13
  136. speclogician/state/recommender.py +0 -577
  137. speclogician/state/state_stats.py +0 -133
  138. speclogician/tui/__init__.py +0 -0
  139. speclogician/tui/app.py +0 -257
  140. speclogician/tui/app.tcss +0 -160
  141. speclogician/tui/demo.py +0 -45
  142. speclogician/tui/images/speclogician-full.png +0 -0
  143. speclogician/tui/images/speclogician-minimal.png +0 -0
  144. speclogician/tui/main_screen.py +0 -454
  145. speclogician/tui/splash_screen.py +0 -51
  146. speclogician/tui/stats_screen.py +0 -125
  147. speclogician/utils/testing.py +0 -151
  148. speclogician-0.0.0b1.dist-info/METADATA +0 -116
  149. speclogician-0.0.0b1.dist-info/RECORD +0 -139
  150. /speclogician/{presentation → agent}/__init__.py +0 -0
  151. /speclogician/{presentation/builders → cmd}/__init__.py +0 -0
  152. /speclogician/{presentation/models → llms}/__init__.py +0 -0
  153. {speclogician-0.0.0b1.dist-info → speclogician-0.0.0.dev1.dist-info}/WHEEL +0 -0
@@ -1,256 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/commands/find_cmd.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- from typing import Any
10
-
11
- import typer
12
- from rich.table import Table
13
-
14
- from .utils import get_state
15
- from ..utils import console
16
-
17
- app = typer.Typer(help="Find various components of the state")
18
-
19
-
20
- # -----------------------------------------------------------------------------
21
- # helpers
22
- # -----------------------------------------------------------------------------
23
-
24
- def _matches_component(q: str, *, name: str, src_code: str) -> bool:
25
- qq = (q or "").lower()
26
- return (qq in (name or "").lower()) or (qq in (src_code or "").lower())
27
-
28
-
29
- def _emit_json(payload: dict[str, Any]) -> None:
30
- # Keep stdout clean for machine mode (matches your convention elsewhere)
31
- import json as _json
32
- typer.echo(_json.dumps(payload, indent=2, sort_keys=True))
33
-
34
-
35
- def _preview(src: str, n: int = 160) -> str:
36
- return (src or "")[:n].replace("\n", " ")
37
-
38
-
39
- # -----------------------------------------------------------------------------
40
- # predicates
41
- # -----------------------------------------------------------------------------
42
-
43
- @app.command(name="predicates", help="Search state/action predicates by name or source text")
44
- def search_predicates(
45
- ctx: typer.Context,
46
- q: str = typer.Argument(..., help="Search string (name or source snippet)"),
47
- kind: str = typer.Option(
48
- "all",
49
- "--kind",
50
- help="Search scope: all|state|action",
51
- ),
52
- json_only: bool = typer.Option(
53
- False,
54
- "--json",
55
- help="Emit JSON only (no rich output)",
56
- ),
57
- ) -> None:
58
- state = get_state(ctx=ctx)
59
- dm = state.curr_state().spec.domain_model
60
-
61
- k = (kind or "all").lower().strip()
62
- if k not in ("all", "state", "action"):
63
- raise typer.BadParameter("kind must be one of: all|state|action")
64
-
65
- matches: list[dict[str, Any]] = []
66
-
67
- if k in ("all", "state"):
68
- for p in getattr(dm, "state_preds", []):
69
- name = getattr(p, "name", "")
70
- src_code = getattr(p, "src_code", "")
71
- if _matches_component(q, name=name, src_code=src_code):
72
- matches.append(
73
- {
74
- "type": "StatePredicate",
75
- "kind": getattr(p, "kind", "state_predicate"),
76
- "name": name,
77
- "src_preview": _preview(src_code),
78
- }
79
- )
80
-
81
- if k in ("all", "action"):
82
- for p in getattr(dm, "action_preds", []):
83
- name = getattr(p, "name", "")
84
- src_code = getattr(p, "src_code", "")
85
- if _matches_component(q, name=name, src_code=src_code):
86
- matches.append(
87
- {
88
- "type": "ActionPredicate",
89
- "kind": getattr(p, "kind", "action_predicate"),
90
- "name": name,
91
- "src_preview": _preview(src_code),
92
- }
93
- )
94
-
95
- # Deterministic output (nice for tests)
96
- matches.sort(key=lambda m: (m.get("type", ""), m.get("name", "")))
97
-
98
- if json_only:
99
- _emit_json(
100
- {
101
- "query": q,
102
- "kind": k,
103
- "count": len(matches),
104
- "results": matches,
105
- }
106
- )
107
- return
108
-
109
- t = Table(title=f"Predicate search for {q!r}", show_header=True, header_style="bold")
110
- t.add_column("Type", no_wrap=True)
111
- t.add_column("Name", no_wrap=True)
112
- t.add_column("Preview")
113
- for m in matches:
114
- prev = m["src_preview"]
115
- t.add_row(m["type"], m["name"], prev + ("…" if len(prev) == 160 else ""))
116
- console.print(t)
117
-
118
-
119
- # -----------------------------------------------------------------------------
120
- # transitions
121
- # -----------------------------------------------------------------------------
122
-
123
- @app.command(name="transitions", help="Search transition functions by name or source text")
124
- def search_transitions(
125
- ctx: typer.Context,
126
- q: str = typer.Argument(..., help="Search string (name or source snippet)"),
127
- json_only: bool = typer.Option(
128
- False,
129
- "--json",
130
- help="Emit JSON only (no rich output)",
131
- ),
132
- ) -> None:
133
- state = get_state(ctx=ctx)
134
- dm = state.curr_state().spec.domain_model
135
-
136
- matches: list[dict[str, Any]] = []
137
-
138
- for tr in getattr(dm, "transitions", []):
139
- name = getattr(tr, "name", "")
140
- src_code = getattr(tr, "src_code", "")
141
- if _matches_component(q, name=name, src_code=src_code):
142
- matches.append(
143
- {
144
- "type": "Transition",
145
- "kind": getattr(tr, "kind", "transition"),
146
- "name": name,
147
- "src_preview": _preview(src_code),
148
- }
149
- )
150
-
151
- matches.sort(key=lambda m: m.get("name", ""))
152
-
153
- if json_only:
154
- _emit_json(
155
- {
156
- "query": q,
157
- "count": len(matches),
158
- "results": matches,
159
- }
160
- )
161
- return
162
-
163
- t = Table(title=f"Transition search for {q!r}", show_header=True, header_style="bold")
164
- t.add_column("Name", no_wrap=True)
165
- t.add_column("Preview")
166
- for m in matches:
167
- prev = m["src_preview"]
168
- t.add_row(m["name"], prev + ("…" if len(prev) == 160 else ""))
169
- console.print(t)
170
-
171
-
172
- # -----------------------------------------------------------------------------
173
- # artifacts (smart_find + PM renderer)
174
- # -----------------------------------------------------------------------------
175
-
176
- @app.command("artifacts")
177
- def search_artifacts(
178
- ctx: typer.Context,
179
- q: str = typer.Argument(..., help="Search string"),
180
- kind: str = typer.Option(
181
- "all",
182
- "--kind",
183
- help="Search scope: all|tests|logs|docs|src",
184
- ),
185
- json_only: bool = typer.Option(
186
- False,
187
- "--json",
188
- help="Emit JSON only (no rich output)",
189
- ),
190
- ids: bool = typer.Option(
191
- False,
192
- "--ids",
193
- help="Show artifact IDs in rich output",
194
- ),
195
- compact: bool = typer.Option(
196
- False,
197
- "--compact",
198
- help="Use compact tables/panels in rich output",
199
- ),
200
- show_empty: bool = typer.Option(
201
- False,
202
- "--show-empty",
203
- help="Render empty sections in rich output",
204
- ),
205
- max_per_type: int = typer.Option(
206
- 50,
207
- "--max-per-type",
208
- min=1,
209
- help="Maximum results to show per artifact type",
210
- ),
211
- ) -> None:
212
- """Smart text search across artifacts (tests/logs/docs/src)."""
213
- state = get_state(ctx=ctx)
214
- ac = state.curr_state().art_container
215
-
216
- k = (kind or "all").lower().strip()
217
- include: set[str] | None
218
- if k in ("all", "*"):
219
- include = None
220
- elif k in ("tests", "test"):
221
- include = {"test"}
222
- elif k in ("logs", "log"):
223
- include = {"log"}
224
- elif k in ("docs", "doc"):
225
- include = {"doc"}
226
- elif k in ("src", "source"):
227
- include = {"src"}
228
- else:
229
- raise typer.BadParameter("kind must be one of: all|tests|logs|docs|src")
230
-
231
- # --- search ---
232
- res = ac.smart_find(q, include=include, max_per_type=max_per_type)
233
-
234
- # --- build PM + render ---
235
- from speclogician.presentation.ctx import RenderCtx
236
- from speclogician.presentation.builders.smart_find import build_smart_find_pm
237
- from speclogician.presentation.renderers.smart_find import render_smart_find
238
-
239
- rctx = RenderCtx(
240
- show_art_ids=ids,
241
- show_ids=ids,
242
- compact=compact,
243
- show_empty_sections=show_empty, # now a real field on RenderCtx
244
- )
245
-
246
- pm = build_smart_find_pm(res, rctx)
247
-
248
- if json_only:
249
- payload = pm.model_dump()
250
- # test-friendly extra metadata
251
- payload["query"] = q
252
- payload["kind"] = k
253
- _emit_json(payload)
254
- return
255
-
256
- console.print(render_smart_find(pm, ctx=rctx))
@@ -1,202 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/cli/view_cmd.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- import json
10
- from typing import Optional
11
- import typer
12
-
13
- from speclogician.presentation.ctx import RenderCtx
14
- from speclogician.presentation.api import (
15
- present_state_instance,
16
- present_spec,
17
- present_domain_model,
18
- present_artifact_container,
19
- present_state_diff,
20
- present_instances_list,
21
- )
22
-
23
- from .utils import get_state
24
-
25
- app = typer.Typer(help="View components of the current state (rich or JSON).")
26
-
27
-
28
- def _mk_ctx(
29
- *,
30
- compact: bool,
31
- show_stats_only: bool,
32
- show_code: bool,
33
- show_ids: bool,
34
- show_art_ids: bool,
35
- ) -> RenderCtx:
36
- return RenderCtx(
37
- compact=compact,
38
- show_stats_only=show_stats_only,
39
- show_code=show_code,
40
- show_ids=show_ids,
41
- show_art_ids=show_art_ids,
42
- )
43
-
44
-
45
- def _emit(obj: object, *, json_only: bool) -> None:
46
- if json_only:
47
- typer.echo(json.dumps(obj, indent=2, default=str))
48
- else:
49
- # rich renderables can be printed by your console; if you want,
50
- # import speclogician.utils.console and console.print(obj)
51
- from speclogician.utils import console
52
- console.print(obj)
53
-
54
-
55
- # ---------------------------
56
- # view state (full)
57
- # ---------------------------
58
-
59
- @app.command("state")
60
- def view_state(
61
- ctx: typer.Context,
62
- idx: Optional[int] = typer.Option(None, "--idx", help="State instance index (default: latest/0)"),
63
- json_only: bool = typer.Option(False, "--json", help="Emit JSON only"),
64
- compact: bool = typer.Option(False, "--compact"),
65
- show_stats_only: bool = typer.Option(False, "--stats-only"),
66
- show_code: bool = typer.Option(True, "--code/--no-code"),
67
- show_ids: bool = typer.Option(False, "--ids"),
68
- show_art_ids: bool = typer.Option(False, "--art-ids"),
69
- ) -> None:
70
- st = get_state(ctx)
71
- i = 0 if idx is None else idx
72
- si = st.instances[i]
73
-
74
- rctx = _mk_ctx(
75
- compact=compact,
76
- show_stats_only=show_stats_only,
77
- show_code=show_code,
78
- show_ids=show_ids,
79
- show_art_ids=show_art_ids,
80
- )
81
-
82
- out = present_state_instance(si, ctx=rctx, json_only=json_only)
83
- _emit(out, json_only=json_only)
84
-
85
-
86
- # ---------------------------
87
- # view spec
88
- # ---------------------------
89
-
90
- @app.command("spec")
91
- def view_spec_cmd(
92
- ctx: typer.Context,
93
- idx: Optional[int] = typer.Option(None, "--idx"),
94
- json_only: bool = typer.Option(False, "--json"),
95
- compact: bool = typer.Option(False, "--compact"),
96
- show_stats_only: bool = typer.Option(False, "--stats-only"),
97
- show_code: bool = typer.Option(True, "--code/--no-code"),
98
- show_ids: bool = typer.Option(False, "--ids"),
99
- show_art_ids: bool = typer.Option(False, "--art-ids"),
100
- ) -> None:
101
- st = get_state(ctx)
102
- i = 0 if idx is None else idx
103
- si = st.instances[i]
104
-
105
- rctx = _mk_ctx(compact=compact, show_stats_only=show_stats_only, show_code=show_code, show_ids=show_ids, show_art_ids=show_art_ids)
106
- out = present_spec(si.spec, ctx=rctx, json_only=json_only)
107
- _emit(out, json_only=json_only)
108
-
109
-
110
- # ---------------------------
111
- # view domain model
112
- # ---------------------------
113
-
114
- @app.command("domain")
115
- def view_domain_cmd(
116
- ctx: typer.Context,
117
- idx: Optional[int] = typer.Option(None, "--idx"),
118
- json_only: bool = typer.Option(False, "--json"),
119
- compact: bool = typer.Option(False, "--compact"),
120
- show_stats_only: bool = typer.Option(False, "--stats-only"),
121
- show_code: bool = typer.Option(True, "--code/--no-code"),
122
- show_ids: bool = typer.Option(False, "--ids"),
123
- show_art_ids: bool = typer.Option(False, "--art-ids"),
124
- ) -> None:
125
- st = get_state(ctx)
126
- i = 0 if idx is None else idx
127
- si = st.instances[i]
128
-
129
- rctx = _mk_ctx(compact=compact, show_stats_only=show_stats_only, show_code=show_code, show_ids=show_ids, show_art_ids=show_art_ids)
130
- out = present_domain_model(si.spec.domain_model, ctx=rctx, json_only=json_only)
131
- _emit(out, json_only=json_only)
132
-
133
-
134
- # ---------------------------
135
- # view artifacts container
136
- # ---------------------------
137
-
138
- @app.command("artifacts")
139
- def view_artifacts_cmd(
140
- ctx: typer.Context,
141
- idx: Optional[int] = typer.Option(None, "--idx"),
142
- json_only: bool = typer.Option(False, "--json"),
143
- compact: bool = typer.Option(False, "--compact"),
144
- show_stats_only: bool = typer.Option(False, "--stats-only"),
145
- show_code: bool = typer.Option(True, "--code/--no-code"),
146
- show_ids: bool = typer.Option(False, "--ids"),
147
- show_art_ids: bool = typer.Option(False, "--art-ids"),
148
- ) -> None:
149
- st = get_state(ctx)
150
- i = 0 if idx is None else idx
151
- si = st.instances[i]
152
-
153
- rctx = _mk_ctx(compact=compact, show_stats_only=show_stats_only, show_code=show_code, show_ids=show_ids, show_art_ids=show_art_ids)
154
- out = present_artifact_container(si.art_container, ctx=rctx, json_only=json_only)
155
- _emit(out, json_only=json_only)
156
-
157
-
158
- # ---------------------------
159
- # view diff (latest instance must have it computed/stored, or compute outside)
160
- # ---------------------------
161
-
162
- @app.command("diff")
163
- def view_diff_cmd(
164
- ctx: typer.Context,
165
- idx: Optional[int] = typer.Option(None, "--idx"),
166
- json_only: bool = typer.Option(False, "--json"),
167
- compact: bool = typer.Option(False, "--compact"),
168
- show_stats_only: bool = typer.Option(False, "--stats-only"),
169
- show_code: bool = typer.Option(True, "--code/--no-code"),
170
- show_ids: bool = typer.Option(False, "--ids"),
171
- show_art_ids: bool = typer.Option(False, "--art-ids"),
172
- ) -> None:
173
- st = get_state(ctx)
174
- i = 0 if idx is None else idx
175
- si = st.instances[i]
176
-
177
- if si.state_diff is None:
178
- raise typer.BadParameter("No state_diff available on this instance.")
179
-
180
- rctx = _mk_ctx(compact=compact, show_stats_only=show_stats_only, show_code=show_code, show_ids=show_ids, show_art_ids=show_art_ids)
181
- out = present_state_diff(si.state_diff, ctx=rctx, json_only=json_only)
182
- _emit(out, json_only=json_only)
183
-
184
-
185
- # ---------------------------
186
- # view list of instances
187
- # ---------------------------
188
-
189
- @app.command("instances")
190
- def view_instances_cmd(
191
- ctx: typer.Context,
192
- json_only: bool = typer.Option(False, "--json"),
193
- compact: bool = typer.Option(False, "--compact"),
194
- show_stats_only: bool = typer.Option(False, "--stats-only"),
195
- show_code: bool = typer.Option(True, "--code/--no-code"),
196
- show_ids: bool = typer.Option(False, "--ids"),
197
- show_art_ids: bool = typer.Option(False, "--art-ids"),
198
- ) -> None:
199
- st = get_state(ctx)
200
- rctx = _mk_ctx(compact=compact, show_stats_only=show_stats_only, show_code=show_code, show_ids=show_ids, show_art_ids=show_art_ids)
201
- out = present_instances_list(st.instances, ctx=rctx, json_only=json_only)
202
- _emit(out, json_only=json_only)
@@ -1,149 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/commands/runner.py
5
- #
6
-
7
- from __future__ import annotations
8
-
9
- import json
10
- import typer
11
- from enum import Enum
12
- from datetime import datetime
13
- from typing import NoReturn, Any
14
- from rich.console import Console
15
-
16
- from speclogician.state.change import StateChange
17
- from speclogician.state.state import State # adjust import
18
- from speclogician.state.change_result import (
19
- ProcessChangeResult,
20
- ProcessChangeError,
21
- ProcessChangeSuccess,
22
- )
23
-
24
- from speclogician.presentation.ctx import RenderCtx
25
- from speclogician.presentation.builders.state_instance import build_state_instance_pm
26
- from speclogician.presentation.builders.state_diff import build_state_diff_pm
27
-
28
- from speclogician.presentation.renderers.state_instance import render_state_instance
29
- from speclogician.presentation.renderers.state_diff import render_state_diff # assuming you have it
30
-
31
- console = Console()
32
-
33
-
34
- def _dump_json(obj: Any) -> str:
35
- def _default(o: Any):
36
- if hasattr(o, "model_dump"):
37
- return o.model_dump(mode="json")
38
- if isinstance(o, Enum):
39
- return o.value
40
- if isinstance(o, datetime):
41
- return o.isoformat()
42
- raise TypeError(f"Object of type {type(o).__name__} is not JSON serializable")
43
-
44
- return json.dumps(
45
- obj,
46
- default=_default,
47
- indent=2,
48
- sort_keys=True,
49
- )
50
-
51
- def run_change_and_render(
52
- st: State,
53
- change: StateChange,
54
- *,
55
- json_only: bool,
56
- ) -> NoReturn:
57
- """
58
- Unified runner for CLI commands.
59
-
60
- - json_only=True: emits JSON PM payload to stdout (stable, parseable).
61
- - json_only=False: pretty-prints PMs via Rich renderers.
62
- """
63
-
64
- # Always take the object path so we can drive presentation from PMs.
65
- result_obj: ProcessChangeResult = st.process_change(change, json_only=json_only)
66
- st.save()
67
-
68
- # -------------------------
69
- # Error branch
70
- # -------------------------
71
- if not result_obj["ok"]:
72
- err_res: ProcessChangeError = result_obj
73
-
74
- # Build ctx from the old state if possible
75
- old_state = err_res["old_state"]
76
- ctx = RenderCtx(
77
- art_cont=old_state.art_container,
78
- art_map=old_state.art_map,
79
- )
80
-
81
- old_pm = build_state_instance_pm(old_state, ctx=ctx)
82
-
83
- if json:
84
- payload = {
85
- "ok": False,
86
- "stage": err_res["stage"],
87
- "change": err_res["change"].model_dump(mode="json"),
88
- "error": {
89
- "type": type(err_res["error"]).__name__,
90
- "message": str(err_res["error"]),
91
- },
92
- "old_state": old_pm.model_dump(mode="json"),
93
- }
94
- typer.echo(_dump_json(payload))
95
- raise typer.Exit(code=1)
96
-
97
- # human output
98
- console.print(f"[bold red]Error[/bold red] stage={err_res['stage']}")
99
- console.print(f"[red]{type(err_res['error']).__name__}: {err_res['error']}[/red]\n")
100
- console.print("[bold]Old state[/bold]")
101
- console.print(render_state_instance(old_pm, ctx=ctx))
102
- raise typer.Exit(code=1)
103
-
104
- # -------------------------
105
- # Success branch
106
- # -------------------------
107
- ok_res: ProcessChangeSuccess = result_obj
108
-
109
- old_state = ok_res["old_state"]
110
- new_state = ok_res["new_state"]
111
- sd = ok_res["state_diff"]
112
-
113
- # Prefer new-state context for presentation (has freshest art_map/art_cont)
114
- ctx = RenderCtx(
115
- art_cont=new_state.art_container,
116
- art_map=new_state.art_map,
117
- )
118
-
119
- old_pm = build_state_instance_pm(old_state, ctx=ctx)
120
- new_pm = build_state_instance_pm(new_state, ctx=ctx)
121
- diff_pm = build_state_diff_pm(sd, ctx=ctx)
122
-
123
- if json:
124
- payload = {
125
- "ok": True,
126
- "stage": "ok",
127
- "change": ok_res["change"].model_dump(mode="json"),
128
- "old_state": old_pm.model_dump(mode="json"),
129
- "new_state": new_pm.model_dump(mode="json"),
130
- "state_diff": diff_pm.model_dump(mode="json"),
131
- "new_num_instances": ok_res["new_num_instances"],
132
- }
133
- typer.echo(_dump_json(payload))
134
- raise typer.Exit(code=0)
135
-
136
- # human output
137
- console.print("[bold]Old state[/bold]")
138
- console.print(render_state_instance(old_pm, ctx=ctx))
139
- console.print()
140
-
141
- console.print("[bold]New state[/bold]")
142
- console.print(render_state_instance(new_pm, ctx=ctx))
143
- console.print()
144
-
145
- console.print("[bold]Diff[/bold]")
146
- console.print(render_state_diff(diff_pm, ctx=ctx))
147
- console.print()
148
-
149
- raise typer.Exit(code=0)
@@ -1,101 +0,0 @@
1
- #
2
- # Imandra Inc.
3
- #
4
- # speclogician/commands/utils.py
5
- #
6
-
7
- from __future__ import annotations
8
- from collections.abc import Mapping, Sequence
9
-
10
- import json
11
- from pathlib import Path
12
- from typing import TypeVar
13
-
14
- import typer
15
- from ..utils.load import load_state
16
- from ..state.change import StateChange
17
- from ..state.state import State
18
-
19
- from ..modeling.scenario import ScenarioDelta
20
-
21
- T = TypeVar("T", bound=StateChange)
22
-
23
- def get_state(ctx: typer.Context) -> State:
24
- state = ctx.obj.state
25
- if state is None:
26
- state = load_state()
27
- return state
28
-
29
- def normalize_string_escapes(v: str) -> str:
30
- """
31
- Convert escaped sequences like '\\n', '\\t' into actual characters.
32
- """
33
- try:
34
- return v.encode("utf-8").decode("unicode_escape")
35
- except Exception:
36
- return v
37
-
38
- def normalize_data(obj):
39
- if isinstance(obj, str):
40
- return normalize_string_escapes(obj)
41
-
42
- if isinstance(obj, Mapping):
43
- return {k: normalize_data(v) for k, v in obj.items()}
44
-
45
- if isinstance(obj, Sequence) and not isinstance(obj, (str, bytes)):
46
- return [normalize_data(v) for v in obj]
47
-
48
- return obj
49
-
50
-
51
- def parse_scenario_delta(value: str | None) -> ScenarioDelta | None:
52
- """
53
- Parse a ScenarioDelta from CLI input.
54
-
55
- Accepted forms:
56
- 1) None / "" -> None
57
- 2) Comma-separated tokens:
58
- "+p1,+p2,-p3" => add=["p1","p2"], remove=["p3"]
59
- "p1,p2" => add=["p1","p2"], remove=[]
60
- "-p3" => add=[], remove=["p3"]
61
- 3) JSON:
62
- '{"add":["p1"],"remove":["p2"]}'
63
- or a path to a JSON file containing that object.
64
-
65
- Validation (dedup, identifier check, overlap) is enforced by ScenarioDelta itself.
66
- """
67
- if value is None:
68
- return None
69
-
70
- s = value.strip()
71
- if not s:
72
- return None
73
-
74
- # File path to JSON
75
- p = Path(s)
76
- if p.exists() and p.is_file():
77
- data = json.loads(p.read_text())
78
- delta = ScenarioDelta.model_validate(data)
79
- return None if delta.is_empty() else delta
80
-
81
- # Inline JSON
82
- if s.startswith("{"):
83
- data = json.loads(s)
84
- delta = ScenarioDelta.model_validate(data)
85
- return None if delta.is_empty() else delta
86
-
87
- # Token format: "+a,+b,-c" or "a,b"
88
- add: list[str] = []
89
- remove: list[str] = []
90
-
91
- for tok in (t.strip() for t in s.split(",") if t.strip()):
92
- if tok.startswith("+"):
93
- add.append(tok[1:].strip())
94
- elif tok.startswith("-"):
95
- remove.append(tok[1:].strip())
96
- else:
97
- # default: treat as add
98
- add.append(tok)
99
-
100
- delta = ScenarioDelta(add=add, remove=remove)
101
- return None if delta.is_empty() else delta
Binary file