godcode-engine 4.0.0__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.
- godcode/__init__.py +86 -0
- godcode/__main__.py +7 -0
- godcode/agentics.py +235 -0
- godcode/ast.py +227 -0
- godcode/chain.py +210 -0
- godcode/cli.py +698 -0
- godcode/environment.py +74 -0
- godcode/errors.py +69 -0
- godcode/interpreter.py +1104 -0
- godcode/ledger.py +88 -0
- godcode/lexer.py +218 -0
- godcode/lsp.py +677 -0
- godcode/parser.py +525 -0
- godcode/plugins.py +255 -0
- godcode/registry.py +515 -0
- godcode/sandbox.py +385 -0
- godcode/scrolls/covenant.god +10 -0
- godcode/scrolls/covenant.toml +6 -0
- godcode/scrolls/lists.god +53 -0
- godcode/scrolls/lists.toml +6 -0
- godcode/scrolls/math.god +64 -0
- godcode/scrolls/math.toml +6 -0
- godcode/scrolls/prophecy.god +14 -0
- godcode/scrolls/prophecy.toml +6 -0
- godcode/scrolls/strings.god +32 -0
- godcode/scrolls/strings.toml +6 -0
- godcode/scrolls/time.god +10 -0
- godcode/scrolls/time.toml +6 -0
- godcode/spirit.py +180 -0
- godcode/tokens.py +91 -0
- godcode/tools.py +378 -0
- godcode/values.py +56 -0
- godcode_engine-4.0.0.dist-info/METADATA +212 -0
- godcode_engine-4.0.0.dist-info/RECORD +38 -0
- godcode_engine-4.0.0.dist-info/WHEEL +5 -0
- godcode_engine-4.0.0.dist-info/entry_points.txt +2 -0
- godcode_engine-4.0.0.dist-info/licenses/LICENSE +201 -0
- godcode_engine-4.0.0.dist-info/top_level.txt +1 -0
godcode/cli.py
ADDED
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
"""Command-line interface for the God Code engine.
|
|
2
|
+
|
|
3
|
+
Subcommands: run, check, repl, fmt, ledger verify.
|
|
4
|
+
Sibling modules (lexer, parser, ast, interpreter, ledger, errors) are imported
|
|
5
|
+
lazily inside each command so `--help` works even before they land.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
# run
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# --- v3: agentics --json ---
|
|
18
|
+
def _make_interpreter(log_path):
|
|
19
|
+
"""Build an Interpreter the way `godcode run` always has, shared by the
|
|
20
|
+
plain and --json run paths so they can never drift apart."""
|
|
21
|
+
from godcode.interpreter import Interpreter
|
|
22
|
+
|
|
23
|
+
kwargs: dict = {}
|
|
24
|
+
if log_path:
|
|
25
|
+
kwargs["log_path"] = log_path
|
|
26
|
+
# Bind the Spirit and the covenant ledger by default; degrade
|
|
27
|
+
# gracefully if either cannot be raised in this environment.
|
|
28
|
+
try:
|
|
29
|
+
from godcode.spirit import SpiritEngine
|
|
30
|
+
kwargs["spirit"] = SpiritEngine()
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
try:
|
|
34
|
+
from godcode.ledger import CovenantLedger
|
|
35
|
+
kwargs["ledger"] = CovenantLedger()
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
return Interpreter(**kwargs)
|
|
39
|
+
# --- end v3: agentics --json ---
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def cmd_run(args: argparse.Namespace) -> int:
|
|
43
|
+
from godcode.errors import GodCodeError
|
|
44
|
+
|
|
45
|
+
# --- v3: sandbox commands ---
|
|
46
|
+
if getattr(args, "sandbox", False):
|
|
47
|
+
return _cmd_run_sandboxed(args)
|
|
48
|
+
# --- end v3: sandbox commands ---
|
|
49
|
+
|
|
50
|
+
# --- v3: agentics --json ---
|
|
51
|
+
if getattr(args, "json", False):
|
|
52
|
+
from godcode import agentics
|
|
53
|
+
return agentics.cmd_run_json(args)
|
|
54
|
+
# --- end v3: agentics --json ---
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
source = Path(args.file).read_text(encoding="utf-8")
|
|
58
|
+
except OSError as exc:
|
|
59
|
+
print(f"godcode: cannot read '{args.file}': {exc.strerror or exc}",
|
|
60
|
+
file=sys.stderr)
|
|
61
|
+
return 1
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
_make_interpreter(args.log).run_source(source, source_name=args.file)
|
|
65
|
+
except GodCodeError as err:
|
|
66
|
+
print(str(err), file=sys.stderr)
|
|
67
|
+
return 1
|
|
68
|
+
return 0
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# --- v3: sandbox commands ---
|
|
72
|
+
def _cmd_run_sandboxed(args: argparse.Namespace) -> int:
|
|
73
|
+
"""Run a scroll under the strict sandbox policy.
|
|
74
|
+
|
|
75
|
+
Spirit, covenant ledger, and the audit log stay unbound: they write
|
|
76
|
+
to the host world, which the sandbox does not permit. The CLI itself
|
|
77
|
+
reads the scroll file before the sandbox is entered — that read is
|
|
78
|
+
the invoker's own act, not the creation's.
|
|
79
|
+
"""
|
|
80
|
+
from godcode.errors import GodCodeError
|
|
81
|
+
from godcode.sandbox import SandboxPolicy, run_sandboxed
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
source = Path(args.file).read_text(encoding="utf-8")
|
|
85
|
+
except OSError as exc:
|
|
86
|
+
if getattr(args, "json", False):
|
|
87
|
+
from godcode import agentics
|
|
88
|
+
agentics.emit(agentics.run_payload(
|
|
89
|
+
args.file, False, [], [],
|
|
90
|
+
agentics._file_error_diagnostic(args.file, exc), 0))
|
|
91
|
+
else:
|
|
92
|
+
print(f"godcode: cannot read '{args.file}': {exc.strerror or exc}",
|
|
93
|
+
file=sys.stderr)
|
|
94
|
+
return 1
|
|
95
|
+
|
|
96
|
+
source_dir = str(Path(args.file).resolve().parent)
|
|
97
|
+
policy = SandboxPolicy.strict(
|
|
98
|
+
source_dir=source_dir,
|
|
99
|
+
timeout_seconds=args.sandbox_timeout,
|
|
100
|
+
)
|
|
101
|
+
if getattr(args, "json", False):
|
|
102
|
+
# Machine-readable report; stdout carries exactly one JSON document.
|
|
103
|
+
import contextlib
|
|
104
|
+
import io
|
|
105
|
+
import time as _time
|
|
106
|
+
|
|
107
|
+
from godcode import agentics
|
|
108
|
+
from godcode.sandbox import run_sandboxed_with_interpreter
|
|
109
|
+
|
|
110
|
+
error = None
|
|
111
|
+
output: list[str] = []
|
|
112
|
+
intents: list[dict] = []
|
|
113
|
+
start = _time.perf_counter()
|
|
114
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
115
|
+
try:
|
|
116
|
+
output, interp = run_sandboxed_with_interpreter(
|
|
117
|
+
source, policy, source_name=args.file)
|
|
118
|
+
intents = list(getattr(interp, "intent_checks", []) or [])
|
|
119
|
+
except GodCodeError as err:
|
|
120
|
+
error = agentics.diagnostic(err)
|
|
121
|
+
ms = int((_time.perf_counter() - start) * 1000)
|
|
122
|
+
agentics.emit(agentics.run_payload(
|
|
123
|
+
args.file, error is None, output, [], error, ms,
|
|
124
|
+
intents=intents))
|
|
125
|
+
return 0 if error is None else 1
|
|
126
|
+
try:
|
|
127
|
+
run_sandboxed(source, policy, source_name=args.file)
|
|
128
|
+
except GodCodeError as err:
|
|
129
|
+
print(str(err), file=sys.stderr)
|
|
130
|
+
return 1
|
|
131
|
+
return 0
|
|
132
|
+
# --- end v3: sandbox commands ---
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
# check
|
|
137
|
+
# ---------------------------------------------------------------------------
|
|
138
|
+
def cmd_check(args: argparse.Namespace) -> int:
|
|
139
|
+
from godcode.errors import GodCodeError
|
|
140
|
+
from godcode.lexer import Lexer
|
|
141
|
+
from godcode.parser import Parser
|
|
142
|
+
|
|
143
|
+
# --- v3: agentics --json ---
|
|
144
|
+
if getattr(args, "json", False):
|
|
145
|
+
from godcode import agentics
|
|
146
|
+
return agentics.cmd_check_json(args)
|
|
147
|
+
# --- end v3: agentics --json ---
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
source = Path(args.file).read_text(encoding="utf-8")
|
|
151
|
+
except OSError as exc:
|
|
152
|
+
print(f"godcode: cannot read '{args.file}': {exc.strerror or exc}",
|
|
153
|
+
file=sys.stderr)
|
|
154
|
+
return 1
|
|
155
|
+
try:
|
|
156
|
+
Parser(Lexer(source).lex()).parse()
|
|
157
|
+
except GodCodeError as err:
|
|
158
|
+
print(str(err), file=sys.stderr)
|
|
159
|
+
return 1
|
|
160
|
+
print(f"✓ {args.file} is pure.")
|
|
161
|
+
return 0
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# repl
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
def cmd_repl(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
168
|
+
from godcode.errors import GodCodeError
|
|
169
|
+
from godcode.interpreter import Interpreter
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
import readline # noqa: F401 (enables history + line editing)
|
|
173
|
+
except ImportError:
|
|
174
|
+
pass
|
|
175
|
+
|
|
176
|
+
print("God Code Live Mode 🕊")
|
|
177
|
+
print("Speak your creation; end each utterance with a blank line. "
|
|
178
|
+
"(:quit to ascend)")
|
|
179
|
+
interp = Interpreter(interactive=True)
|
|
180
|
+
buf: list[str] = []
|
|
181
|
+
while True:
|
|
182
|
+
try:
|
|
183
|
+
line = input("godcode> " if not buf else "...... ")
|
|
184
|
+
except EOFError:
|
|
185
|
+
print()
|
|
186
|
+
break
|
|
187
|
+
except KeyboardInterrupt:
|
|
188
|
+
print()
|
|
189
|
+
break
|
|
190
|
+
if line.strip() in (":quit", ":q"):
|
|
191
|
+
break
|
|
192
|
+
if not line.strip():
|
|
193
|
+
if buf:
|
|
194
|
+
chunk = "\n".join(buf)
|
|
195
|
+
buf = []
|
|
196
|
+
try:
|
|
197
|
+
interp.run_source(chunk, source_name="<repl>")
|
|
198
|
+
except GodCodeError as err:
|
|
199
|
+
print(str(err), file=sys.stderr)
|
|
200
|
+
continue
|
|
201
|
+
buf.append(line)
|
|
202
|
+
print("🕊 The sanctuary rests.")
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
# ---------------------------------------------------------------------------
|
|
207
|
+
# fmt — AST -> canonical God Code
|
|
208
|
+
# ---------------------------------------------------------------------------
|
|
209
|
+
_PRECEDENCE = {
|
|
210
|
+
"or": 1, "and": 2,
|
|
211
|
+
"==": 3, "!=": 3, "<": 3, ">": 3, "<=": 3, ">=": 3,
|
|
212
|
+
"+": 4, "-": 4, "*": 5, "/": 5, "%": 5,
|
|
213
|
+
}
|
|
214
|
+
_UNARY_PREC = 6
|
|
215
|
+
_BLOCK_NODES = {"CreationBlock", "IfStmt", "ForLoop", "WhileLoop", "DefineRite"}
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _escape(text: str) -> str:
|
|
219
|
+
return (text.replace("\\", "\\\\").replace('"', '\\"')
|
|
220
|
+
.replace("\n", "\\n").replace("\t", "\\t"))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class CanonicalFormatter:
|
|
224
|
+
"""Re-emit an AST as canonical God Code: keywords UPPER, 2-space indent,
|
|
225
|
+
one statement per line, blank line between top-level blocks."""
|
|
226
|
+
|
|
227
|
+
def __init__(self) -> None:
|
|
228
|
+
self._lines: list[str] = []
|
|
229
|
+
self._depth = 0
|
|
230
|
+
|
|
231
|
+
# -- driver ---------------------------------------------------------
|
|
232
|
+
def format(self, program) -> str:
|
|
233
|
+
prev_was_block = False
|
|
234
|
+
for i, stmt in enumerate(program.statements):
|
|
235
|
+
is_block = type(stmt).__name__ in _BLOCK_NODES
|
|
236
|
+
if i and (is_block or prev_was_block):
|
|
237
|
+
self._lines.append("")
|
|
238
|
+
self._emit_stmt(stmt)
|
|
239
|
+
prev_was_block = is_block
|
|
240
|
+
return "\n".join(self._lines) + "\n"
|
|
241
|
+
|
|
242
|
+
def _emit_stmt(self, node) -> None:
|
|
243
|
+
meth = getattr(self, "_stmt_" + type(node).__name__, None)
|
|
244
|
+
if meth is None:
|
|
245
|
+
raise ValueError(
|
|
246
|
+
f"the formatter knows not this node: {type(node).__name__}")
|
|
247
|
+
meth(node)
|
|
248
|
+
|
|
249
|
+
def _line(self, text: str) -> None:
|
|
250
|
+
self._lines.append(" " * self._depth + text)
|
|
251
|
+
|
|
252
|
+
def _block(self, stmts) -> None:
|
|
253
|
+
self._depth += 1
|
|
254
|
+
for stmt in stmts:
|
|
255
|
+
self._emit_stmt(stmt)
|
|
256
|
+
self._depth -= 1
|
|
257
|
+
|
|
258
|
+
# -- expressions ----------------------------------------------------
|
|
259
|
+
def _expr(self, node, parent_prec: int = 0) -> str:
|
|
260
|
+
kind = type(node).__name__
|
|
261
|
+
if kind == "BinaryOp":
|
|
262
|
+
prec = _PRECEDENCE[node.op]
|
|
263
|
+
op = node.op.upper() if node.op in ("and", "or") else node.op
|
|
264
|
+
text = (f"{self._expr(node.left, prec)} {op} "
|
|
265
|
+
f"{self._expr(node.right, prec + 1)}")
|
|
266
|
+
return f"({text})" if prec < parent_prec else text
|
|
267
|
+
if kind == "UnaryOp":
|
|
268
|
+
inner = self._expr(node.operand, _UNARY_PREC)
|
|
269
|
+
if type(node.operand).__name__ == "UnaryOp":
|
|
270
|
+
inner = f"({inner})"
|
|
271
|
+
return f"NOT {inner}" if node.op == "not" else f"-{inner}"
|
|
272
|
+
if kind == "Literal":
|
|
273
|
+
return self._literal(node.value)
|
|
274
|
+
if kind == "Identifier":
|
|
275
|
+
return node.name
|
|
276
|
+
if kind == "ListLiteral":
|
|
277
|
+
return "[" + ", ".join(self._expr(i) for i in node.items) + "]"
|
|
278
|
+
if kind == "Index":
|
|
279
|
+
return f"{self._expr(node.obj, _UNARY_PREC)}[{self._expr(node.index)}]"
|
|
280
|
+
if kind == "CallExpr":
|
|
281
|
+
return (f"{node.callee}("
|
|
282
|
+
+ ", ".join(self._expr(a) for a in node.args) + ")")
|
|
283
|
+
raise ValueError(
|
|
284
|
+
f"the formatter knows not this expression: {kind}")
|
|
285
|
+
|
|
286
|
+
@staticmethod
|
|
287
|
+
def _literal(value) -> str:
|
|
288
|
+
if isinstance(value, str):
|
|
289
|
+
return f'"{_escape(value)}"'
|
|
290
|
+
if value is True:
|
|
291
|
+
return "true"
|
|
292
|
+
if value is False:
|
|
293
|
+
return "false"
|
|
294
|
+
if value is None:
|
|
295
|
+
return "void"
|
|
296
|
+
return repr(value)
|
|
297
|
+
|
|
298
|
+
# -- statements -----------------------------------------------------
|
|
299
|
+
def _stmt_CreationBlock(self, node) -> None:
|
|
300
|
+
self._line("BEGIN CREATION")
|
|
301
|
+
self._block(node.statements)
|
|
302
|
+
self._line("END CREATION")
|
|
303
|
+
|
|
304
|
+
def _stmt_Declare(self, node) -> None:
|
|
305
|
+
value = node.value
|
|
306
|
+
if type(value).__name__ == "ListLiteral":
|
|
307
|
+
rhs = ", ".join(self._expr(i) for i in value.items)
|
|
308
|
+
else:
|
|
309
|
+
rhs = self._expr(value)
|
|
310
|
+
self._line(f"DECLARE {node.name} AS {rhs}")
|
|
311
|
+
|
|
312
|
+
def _stmt_DeclareIntent(self, node) -> None: # v4.0
|
|
313
|
+
self._line(f'DECLARE INTENT "{_escape(node.text)}" ON {node.rite}')
|
|
314
|
+
|
|
315
|
+
def _stmt_Breathe(self, node) -> None:
|
|
316
|
+
self._line(f"BREATHE LIFE INTO {node.name}")
|
|
317
|
+
|
|
318
|
+
def _stmt_Reveal(self, node) -> None:
|
|
319
|
+
self._line(f"REVEAL({self._expr(node.expr)})")
|
|
320
|
+
|
|
321
|
+
def _stmt_Prophesy(self, node) -> None:
|
|
322
|
+
self._line(f"PROPHESY {node.text}".rstrip())
|
|
323
|
+
|
|
324
|
+
def _stmt_Ascend(self, node) -> None:
|
|
325
|
+
self._line("ASCEND")
|
|
326
|
+
|
|
327
|
+
def _stmt_Reflect(self, node) -> None:
|
|
328
|
+
self._line("REFLECT")
|
|
329
|
+
|
|
330
|
+
def _stmt_Bless(self, node) -> None:
|
|
331
|
+
self._line(f"BLESS {node.name}")
|
|
332
|
+
|
|
333
|
+
def _stmt_Anoint(self, node) -> None:
|
|
334
|
+
self._line(f"ANOINT {node.name}")
|
|
335
|
+
|
|
336
|
+
def _stmt_SealStmt(self, node) -> None:
|
|
337
|
+
self._line(f"SEAL {self._expr(node.expr)}")
|
|
338
|
+
|
|
339
|
+
def _stmt_Testify(self, node) -> None:
|
|
340
|
+
self._line(f"TESTIFY {self._expr(node.expr)}")
|
|
341
|
+
|
|
342
|
+
def _stmt_IfStmt(self, node) -> None:
|
|
343
|
+
self._line(f"IF {self._expr(node.cond)} THEN")
|
|
344
|
+
self._block(node.then_body)
|
|
345
|
+
if node.else_body:
|
|
346
|
+
self._line("ELSE")
|
|
347
|
+
self._block(node.else_body)
|
|
348
|
+
self._line("ENDIF")
|
|
349
|
+
|
|
350
|
+
def _stmt_ForLoop(self, node) -> None:
|
|
351
|
+
self._line(f"FOR {node.var} IN {self._expr(node.iterable)}")
|
|
352
|
+
self._block(node.body)
|
|
353
|
+
self._line("ENDFOR")
|
|
354
|
+
|
|
355
|
+
def _stmt_WhileLoop(self, node) -> None:
|
|
356
|
+
self._line(f"WHILE {self._expr(node.cond)} DO")
|
|
357
|
+
self._block(node.body)
|
|
358
|
+
self._line("ENDWHILE")
|
|
359
|
+
|
|
360
|
+
def _stmt_DefineRite(self, node) -> None:
|
|
361
|
+
params = ", ".join(node.params)
|
|
362
|
+
self._line(f"DEFINE RITE {node.name}({params})")
|
|
363
|
+
self._block(node.body)
|
|
364
|
+
self._line("END RITE")
|
|
365
|
+
|
|
366
|
+
def _stmt_Return(self, node) -> None:
|
|
367
|
+
self._line("RETURN" if node.expr is None
|
|
368
|
+
else f"RETURN {self._expr(node.expr)}")
|
|
369
|
+
|
|
370
|
+
def _stmt_Import(self, node) -> None:
|
|
371
|
+
self._line(f'IMPORT "{_escape(node.path)}"')
|
|
372
|
+
|
|
373
|
+
def _stmt_ExprStmt(self, node) -> None:
|
|
374
|
+
expr = node.expr
|
|
375
|
+
if type(expr).__name__ == "CallExpr":
|
|
376
|
+
args = ", ".join(self._expr(a) for a in expr.args)
|
|
377
|
+
self._line(f"INVOKE {expr.callee}({args})")
|
|
378
|
+
else:
|
|
379
|
+
self._line(self._expr(expr))
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def cmd_fmt(args: argparse.Namespace) -> int:
|
|
383
|
+
from godcode.errors import GodCodeError
|
|
384
|
+
from godcode.lexer import Lexer
|
|
385
|
+
from godcode.parser import Parser
|
|
386
|
+
|
|
387
|
+
src_path = Path(args.file)
|
|
388
|
+
try:
|
|
389
|
+
source = src_path.read_text(encoding="utf-8")
|
|
390
|
+
except OSError as exc:
|
|
391
|
+
print(f"godcode: cannot read '{args.file}': {exc.strerror or exc}",
|
|
392
|
+
file=sys.stderr)
|
|
393
|
+
return 1
|
|
394
|
+
try:
|
|
395
|
+
program = Parser(Lexer(source).lex()).parse()
|
|
396
|
+
except GodCodeError as err:
|
|
397
|
+
print(str(err), file=sys.stderr)
|
|
398
|
+
return 1
|
|
399
|
+
|
|
400
|
+
canonical = CanonicalFormatter().format(program)
|
|
401
|
+
if args.in_place:
|
|
402
|
+
src_path.write_text(canonical, encoding="utf-8")
|
|
403
|
+
else:
|
|
404
|
+
sys.stdout.write(canonical)
|
|
405
|
+
return 0
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# ---------------------------------------------------------------------------
|
|
409
|
+
# ledger verify
|
|
410
|
+
# ---------------------------------------------------------------------------
|
|
411
|
+
def cmd_ledger_verify(args: argparse.Namespace) -> int:
|
|
412
|
+
# v4.0: verifies the covenant chain AND the anchor chain, reporting both.
|
|
413
|
+
from godcode.chain import SimulatedChainAdapter
|
|
414
|
+
from godcode.ledger import CovenantLedger
|
|
415
|
+
|
|
416
|
+
cov_ok, cov_message = CovenantLedger(args.file).verify()
|
|
417
|
+
anc_ok, anc_message = SimulatedChainAdapter(args.anchor_file).verify_chain()
|
|
418
|
+
print(cov_message)
|
|
419
|
+
print(anc_message)
|
|
420
|
+
return 0 if (cov_ok and anc_ok) else 1
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
# --- v3: scroll commands ---
|
|
424
|
+
# Pillar 2 — Scroll Registry: publish/install/info/list installable scrolls.
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _scroll_registry():
|
|
428
|
+
from godcode.registry import ScrollRegistry
|
|
429
|
+
|
|
430
|
+
return ScrollRegistry()
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def cmd_scroll_list(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
434
|
+
from godcode.registry import ScrollError
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
rows = _scroll_registry().list_installed()
|
|
438
|
+
except ScrollError as exc:
|
|
439
|
+
print(f"godcode: {exc}", file=sys.stderr)
|
|
440
|
+
return 1
|
|
441
|
+
if not rows:
|
|
442
|
+
print("No scrolls installed. "
|
|
443
|
+
"Publish one with `godcode scroll publish <dir>`, "
|
|
444
|
+
"then `godcode scroll install <name>`.")
|
|
445
|
+
return 0
|
|
446
|
+
for row in rows:
|
|
447
|
+
loc = ",".join(row["locations"])
|
|
448
|
+
vers = ", ".join(row["versions"])
|
|
449
|
+
print(f"{row['name']} {vers} [{loc}]")
|
|
450
|
+
return 0
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def cmd_scroll_install(args: argparse.Namespace) -> int:
|
|
454
|
+
from godcode.registry import ScrollError
|
|
455
|
+
|
|
456
|
+
try:
|
|
457
|
+
receipt = _scroll_registry().install(
|
|
458
|
+
args.name, version=args.version, project=args.project)
|
|
459
|
+
except ScrollError as exc:
|
|
460
|
+
print(f"godcode: {exc}", file=sys.stderr)
|
|
461
|
+
return 1
|
|
462
|
+
where = "project-local" if args.project else "user-global"
|
|
463
|
+
print(f"Installed {receipt['name']} {receipt['version']} "
|
|
464
|
+
f"({where}: {receipt['manifest']['description'][:60]}...)")
|
|
465
|
+
return 0
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def cmd_scroll_publish(args: argparse.Namespace) -> int:
|
|
469
|
+
from godcode.registry import ScrollError
|
|
470
|
+
|
|
471
|
+
try:
|
|
472
|
+
manifest = _scroll_registry().publish(args.dir)
|
|
473
|
+
except ScrollError as exc:
|
|
474
|
+
print(f"godcode: {exc}", file=sys.stderr)
|
|
475
|
+
return 1
|
|
476
|
+
print(f"Published {manifest['name']} {manifest['version']} "
|
|
477
|
+
f"to the local registry.")
|
|
478
|
+
return 0
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def cmd_scroll_info(args: argparse.Namespace) -> int:
|
|
482
|
+
from godcode.registry import ScrollError
|
|
483
|
+
|
|
484
|
+
try:
|
|
485
|
+
info = _scroll_registry().info(args.name)
|
|
486
|
+
except ScrollError as exc:
|
|
487
|
+
print(f"godcode: {exc}", file=sys.stderr)
|
|
488
|
+
return 1
|
|
489
|
+
print(f"name: {info['name']}")
|
|
490
|
+
if info["manifest"]:
|
|
491
|
+
manifest = info["manifest"]
|
|
492
|
+
print(f"version: {info['latest']} (latest published)")
|
|
493
|
+
print(f"author: {manifest['author']}")
|
|
494
|
+
print(f"entry: {manifest['entry']}")
|
|
495
|
+
print(f"godcode: {manifest['godcode']}")
|
|
496
|
+
print(f"description: {manifest['description']}")
|
|
497
|
+
else:
|
|
498
|
+
print("published: (not in the local registry)")
|
|
499
|
+
if info["published"]:
|
|
500
|
+
print(f"published: {', '.join(info['published'])}")
|
|
501
|
+
if info["installed"]:
|
|
502
|
+
print(f"installed: {', '.join(info['installed'])} "
|
|
503
|
+
f"(project: {', '.join(info['installed_project']) or '--'}; "
|
|
504
|
+
f"user: {', '.join(info['installed_user']) or '--'})")
|
|
505
|
+
else:
|
|
506
|
+
print(f"installed: (nowhere -- `godcode scroll install "
|
|
507
|
+
f"{info['name']}` to receive it)")
|
|
508
|
+
return 0
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _add_scroll_commands(sub) -> None:
|
|
512
|
+
p_scroll = sub.add_parser("scroll", help="Scroll Registry commands")
|
|
513
|
+
scroll_sub = p_scroll.add_subparsers(dest="scroll_command", required=True)
|
|
514
|
+
|
|
515
|
+
p_list = scroll_sub.add_parser("list", help="List installed scrolls")
|
|
516
|
+
p_list.set_defaults(func=cmd_scroll_list)
|
|
517
|
+
|
|
518
|
+
p_install = scroll_sub.add_parser("install",
|
|
519
|
+
help="Install a scroll from the registry")
|
|
520
|
+
p_install.add_argument("name", help="Scroll name, e.g. json-tools")
|
|
521
|
+
p_install.add_argument("--version", default=None, metavar="X.Y.Z",
|
|
522
|
+
help="Exact version (default: latest published)")
|
|
523
|
+
p_install.add_argument("--project", action="store_true",
|
|
524
|
+
help="Install project-local (.godcode/scrolls/) "
|
|
525
|
+
"instead of user-global (~/.godcode/scrolls/)")
|
|
526
|
+
p_install.set_defaults(func=cmd_scroll_install)
|
|
527
|
+
|
|
528
|
+
p_publish = scroll_sub.add_parser("publish",
|
|
529
|
+
help="Publish a scroll dir to the registry")
|
|
530
|
+
p_publish.add_argument("dir", help="Directory holding scroll.toml")
|
|
531
|
+
p_publish.set_defaults(func=cmd_scroll_publish)
|
|
532
|
+
|
|
533
|
+
p_info = scroll_sub.add_parser("info",
|
|
534
|
+
help="Show a scroll's manifest and state")
|
|
535
|
+
p_info.add_argument("name", help="Scroll name")
|
|
536
|
+
p_info.set_defaults(func=cmd_scroll_info)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
# --- end v3: scroll commands ---
|
|
540
|
+
|
|
541
|
+
# --- v3: lsp commands ---
|
|
542
|
+
def cmd_lsp(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
543
|
+
from godcode.lsp import serve
|
|
544
|
+
|
|
545
|
+
return serve()
|
|
546
|
+
# --- end v3: lsp commands ---
|
|
547
|
+
|
|
548
|
+
# --- v4.0: intent command ---
|
|
549
|
+
def cmd_intent(args: argparse.Namespace) -> int:
|
|
550
|
+
"""`godcode intent "some words" [--json]`: resolve intent via the Spirit."""
|
|
551
|
+
import json as _json
|
|
552
|
+
|
|
553
|
+
from godcode.spirit import SpiritEngine
|
|
554
|
+
|
|
555
|
+
result = SpiritEngine().resolve_intent(args.text)
|
|
556
|
+
if getattr(args, "json", False):
|
|
557
|
+
print(_json.dumps(
|
|
558
|
+
{"tool": "godcode", "command": "intent",
|
|
559
|
+
"text": args.text, "result": result},
|
|
560
|
+
ensure_ascii=False))
|
|
561
|
+
return 0
|
|
562
|
+
pct = round(result["confidence"] * 100)
|
|
563
|
+
print(f"[INTENT] The Spirit discerns: '{result['intent']}' "
|
|
564
|
+
f"({pct}% certainty).")
|
|
565
|
+
print(f"Spiritual intent: {result['spiritual_intent']}")
|
|
566
|
+
print(f"Counsel: {result['suggestion']}")
|
|
567
|
+
aligned = result.get("aligned_with") or []
|
|
568
|
+
if aligned:
|
|
569
|
+
for entry in aligned:
|
|
570
|
+
print(f"Aligned with: {entry['rite']} (\"{entry['declared']}\")")
|
|
571
|
+
else:
|
|
572
|
+
print("Aligned with no declared intent.")
|
|
573
|
+
return 0
|
|
574
|
+
# --- end v4.0: intent command ---
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
# --- v4.0: tools + bridge commands (implemented in godcode.tools) ---
|
|
578
|
+
def cmd_tools(args: argparse.Namespace) -> int:
|
|
579
|
+
from godcode.tools import cmd_tools as _cmd_tools
|
|
580
|
+
|
|
581
|
+
return _cmd_tools(args)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def cmd_bridge(args: argparse.Namespace) -> int:
|
|
585
|
+
from godcode.tools import cmd_bridge as _cmd_bridge
|
|
586
|
+
|
|
587
|
+
return _cmd_bridge(args)
|
|
588
|
+
# --- end v4.0 ---
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
# ---------------------------------------------------------------------------
|
|
592
|
+
# parser assembly
|
|
593
|
+
# ---------------------------------------------------------------------------
|
|
594
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
595
|
+
parser = argparse.ArgumentParser(
|
|
596
|
+
prog="godcode",
|
|
597
|
+
description="God Code — the language of divine computation 🕊")
|
|
598
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
599
|
+
|
|
600
|
+
p_run = sub.add_parser("run", help="Execute a God Code scroll")
|
|
601
|
+
p_run.add_argument("file", help="Path to the .god scroll")
|
|
602
|
+
p_run.add_argument("--log", default=None, metavar="PATH",
|
|
603
|
+
help="Audit log path (default: logs/godcode.log)")
|
|
604
|
+
# --- v3: sandbox commands ---
|
|
605
|
+
p_run.add_argument("--sandbox", action="store_true",
|
|
606
|
+
help="Run under the strict sandbox policy "
|
|
607
|
+
"(deny fs writes, network, subprocesses, "
|
|
608
|
+
"stdin; scroll imports limited to the "
|
|
609
|
+
"scroll's own directory and the stdlib "
|
|
610
|
+
"scrolls; time and step budgets enforced)")
|
|
611
|
+
p_run.add_argument("--sandbox-timeout", type=float, default=5.0,
|
|
612
|
+
metavar="SECS",
|
|
613
|
+
help="Wall-clock grant for --sandbox runs "
|
|
614
|
+
"(default: 5.0 seconds)")
|
|
615
|
+
# --- end v3: sandbox commands ---
|
|
616
|
+
# --- v3: agentics --json ---
|
|
617
|
+
p_run.add_argument("--json", action="store_true",
|
|
618
|
+
help="Emit a machine-readable JSON report on stdout")
|
|
619
|
+
# --- end v3: agentics --json ---
|
|
620
|
+
p_run.set_defaults(func=cmd_run)
|
|
621
|
+
|
|
622
|
+
p_check = sub.add_parser("check",
|
|
623
|
+
help="Lex and parse a scroll without running it")
|
|
624
|
+
p_check.add_argument("file", help="Path to the .god scroll")
|
|
625
|
+
# --- v3: agentics --json ---
|
|
626
|
+
p_check.add_argument("--json", action="store_true",
|
|
627
|
+
help="Emit a machine-readable JSON report on stdout")
|
|
628
|
+
# --- end v3: agentics --json ---
|
|
629
|
+
p_check.set_defaults(func=cmd_check)
|
|
630
|
+
|
|
631
|
+
p_repl = sub.add_parser("repl", help="Enter the live sanctuary")
|
|
632
|
+
p_repl.set_defaults(func=cmd_repl)
|
|
633
|
+
|
|
634
|
+
p_fmt = sub.add_parser("fmt", help="Re-emit a scroll in canonical form")
|
|
635
|
+
p_fmt.add_argument("file", help="Path to the .god scroll")
|
|
636
|
+
p_fmt.add_argument("--in-place", "-w", dest="in_place",
|
|
637
|
+
action="store_true",
|
|
638
|
+
help="Rewrite the file instead of printing")
|
|
639
|
+
p_fmt.set_defaults(func=cmd_fmt)
|
|
640
|
+
|
|
641
|
+
p_ledger = sub.add_parser("ledger", help="Covenant ledger commands")
|
|
642
|
+
ledger_sub = p_ledger.add_subparsers(dest="ledger_command", required=True)
|
|
643
|
+
p_verify = ledger_sub.add_parser("verify",
|
|
644
|
+
help="Verify the covenant chain")
|
|
645
|
+
p_verify.add_argument("--file", default="covenant.chain", metavar="PATH",
|
|
646
|
+
help="Chain file (default: covenant.chain)")
|
|
647
|
+
# --- v4.0: the anchor chain is verified alongside the covenant chain ---
|
|
648
|
+
p_verify.add_argument("--anchor-file", default="anchors.chain",
|
|
649
|
+
metavar="PATH",
|
|
650
|
+
help="Anchor chain file (default: anchors.chain)")
|
|
651
|
+
# --- end v4.0 ---
|
|
652
|
+
p_verify.set_defaults(func=cmd_ledger_verify)
|
|
653
|
+
|
|
654
|
+
# --- v4.0: intent, tools, bridge commands ---
|
|
655
|
+
p_intent = sub.add_parser("intent",
|
|
656
|
+
help="Resolve the intent behind words "
|
|
657
|
+
"with the Spirit Engine")
|
|
658
|
+
p_intent.add_argument("text", help="Words to resolve the intent of")
|
|
659
|
+
p_intent.add_argument("--json", action="store_true",
|
|
660
|
+
help="Emit a machine-readable JSON report on stdout")
|
|
661
|
+
p_intent.set_defaults(func=cmd_intent)
|
|
662
|
+
|
|
663
|
+
p_tools = sub.add_parser("tools",
|
|
664
|
+
help="Show the MCP-compatible agent tool schemas")
|
|
665
|
+
p_tools.add_argument("--json", action="store_true",
|
|
666
|
+
help="Emit the schemas as JSON")
|
|
667
|
+
p_tools.set_defaults(func=cmd_tools)
|
|
668
|
+
|
|
669
|
+
p_bridge = sub.add_parser("bridge",
|
|
670
|
+
help="Serve the agent tool bridge "
|
|
671
|
+
"(JSON-RPC 2.0 over stdio)")
|
|
672
|
+
p_bridge.set_defaults(func=cmd_bridge)
|
|
673
|
+
# --- end v4.0 ---
|
|
674
|
+
|
|
675
|
+
# --- v3: scroll commands ---
|
|
676
|
+
_add_scroll_commands(sub)
|
|
677
|
+
# --- end v3: scroll commands ---
|
|
678
|
+
|
|
679
|
+
# --- v3: lsp commands ---
|
|
680
|
+
p_lsp = sub.add_parser("lsp",
|
|
681
|
+
help="Start the language server over stdio")
|
|
682
|
+
p_lsp.set_defaults(func=cmd_lsp)
|
|
683
|
+
# --- end v3: lsp commands ---
|
|
684
|
+
|
|
685
|
+
return parser
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def main(argv=None) -> int:
|
|
689
|
+
"""Entry point. Returns 0 on success; raises SystemExit(code) on failure."""
|
|
690
|
+
args = build_parser().parse_args(argv)
|
|
691
|
+
code = args.func(args)
|
|
692
|
+
if code:
|
|
693
|
+
raise SystemExit(code)
|
|
694
|
+
return 0
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
if __name__ == "__main__":
|
|
698
|
+
sys.exit(main())
|