@isl-lang/repl 0.1.0 → 0.1.1

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.
package/dist/cli.js CHANGED
@@ -26,6 +26,12 @@ var Session = class {
26
26
  loadedFiles = /* @__PURE__ */ new Set();
27
27
  /** Session configuration */
28
28
  config;
29
+ /** Evaluation context (set by .context command) */
30
+ evalContext = {};
31
+ /** Pre-state context for old() expressions */
32
+ preContext = null;
33
+ /** Loaded domain AST (from real parser) */
34
+ domainAST = null;
29
35
  constructor(config = {}) {
30
36
  this.config = {
31
37
  colors: true,
@@ -303,6 +309,97 @@ var Session = class {
303
309
  }
304
310
  }
305
311
  // ─────────────────────────────────────────────────────────────────────────
312
+ // Evaluation Context Management
313
+ // ─────────────────────────────────────────────────────────────────────────
314
+ /**
315
+ * Set evaluation context from JSON string
316
+ */
317
+ setEvalContext(json) {
318
+ try {
319
+ const parsed = JSON.parse(json);
320
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
321
+ return { success: false, count: 0, error: "Context must be a JSON object" };
322
+ }
323
+ this.evalContext = parsed;
324
+ for (const [key, value] of Object.entries(this.evalContext)) {
325
+ this.variables.set(key, value);
326
+ }
327
+ return { success: true, count: Object.keys(parsed).length };
328
+ } catch (e) {
329
+ return { success: false, count: 0, error: e instanceof Error ? e.message : String(e) };
330
+ }
331
+ }
332
+ /**
333
+ * Set pre-state context for old() expressions
334
+ */
335
+ setPreContext(json) {
336
+ try {
337
+ const parsed = JSON.parse(json);
338
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
339
+ return { success: false, error: "Pre-context must be a JSON object" };
340
+ }
341
+ this.preContext = parsed;
342
+ return { success: true };
343
+ } catch (e) {
344
+ return { success: false, error: e instanceof Error ? e.message : String(e) };
345
+ }
346
+ }
347
+ /**
348
+ * Get evaluation context
349
+ */
350
+ getEvalContext() {
351
+ return { ...this.evalContext };
352
+ }
353
+ /**
354
+ * Get pre-state context
355
+ */
356
+ getPreContext() {
357
+ return this.preContext ? { ...this.preContext } : null;
358
+ }
359
+ /**
360
+ * Resolve a dot-path against the evaluation context
361
+ */
362
+ resolveValue(dotPath) {
363
+ const parts = dotPath.split(".");
364
+ let current = this.evalContext;
365
+ for (const part of parts) {
366
+ if (current === null || current === void 0 || typeof current !== "object") {
367
+ return { found: false, value: void 0 };
368
+ }
369
+ current = current[part];
370
+ }
371
+ return { found: current !== void 0, value: current };
372
+ }
373
+ /**
374
+ * Resolve a dot-path against the pre-state context
375
+ */
376
+ resolvePreValue(dotPath) {
377
+ if (!this.preContext) {
378
+ return { found: false, value: void 0 };
379
+ }
380
+ const parts = dotPath.split(".");
381
+ let current = this.preContext;
382
+ for (const part of parts) {
383
+ if (current === null || current === void 0 || typeof current !== "object") {
384
+ return { found: false, value: void 0 };
385
+ }
386
+ current = current[part];
387
+ }
388
+ return { found: current !== void 0, value: current };
389
+ }
390
+ /**
391
+ * Set domain AST (from real parser)
392
+ */
393
+ setDomainAST(ast) {
394
+ this.domainAST = ast;
395
+ }
396
+ /**
397
+ * Get domain AST
398
+ */
399
+ getDomainAST() {
400
+ return this.domainAST;
401
+ }
402
+ // ─────────────────────────────────────────────────────────────────────────
306
403
  // State Management
307
404
  // ─────────────────────────────────────────────────────────────────────────
308
405
  /**
@@ -313,6 +410,9 @@ var Session = class {
313
410
  this.variables.clear();
314
411
  this.lastResult = void 0;
315
412
  this.loadedFiles.clear();
413
+ this.evalContext = {};
414
+ this.preContext = null;
415
+ this.domainAST = null;
316
416
  }
317
417
  /**
318
418
  * Get session summary
@@ -513,6 +613,10 @@ var History = class {
513
613
  };
514
614
 
515
615
  // src/completions.ts
616
+ import * as fs4 from "fs";
617
+ import * as path4 from "path";
618
+
619
+ // src/commands.ts
516
620
  import * as fs3 from "fs";
517
621
  import * as path3 from "path";
518
622
 
@@ -600,34 +704,205 @@ function formatIntent(intent) {
600
704
  function highlightExpression(expr) {
601
705
  return expr.replace(/\b(and|or|not|implies)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/(>=|<=|==|!=|>|<)/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(true|false|null)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\b(forall|exists|in)\b/g, `${colors.yellow}$1${colors.reset}`).replace(/\b(\d+(?:\.\d+)?)\b/g, `${colors.cyan}$1${colors.reset}`).replace(/"([^"]*)"/g, `${colors.green}"$1"${colors.reset}`).replace(/\.(\w+)\(/g, `.${colors.blue}$1${colors.reset}(`).replace(/\.(\w+)(?!\()/g, `.${colors.cyan}$1${colors.reset}`);
602
706
  }
707
+ function formatParseError(source, message, line, column) {
708
+ const lines = source.split("\n");
709
+ const errorLine = lines[line - 1] || "";
710
+ const output = [
711
+ formatError(message),
712
+ "",
713
+ `${colors.gray}${String(line).padStart(4)} \u2502${colors.reset} ${errorLine}`,
714
+ `${colors.gray} \u2502${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`
715
+ ];
716
+ return output.join("\n");
717
+ }
718
+ function formatValue(value, indent = 0) {
719
+ const pad = " ".repeat(indent);
720
+ if (value === null) return `${colors.gray}null${colors.reset}`;
721
+ if (value === void 0) return `${colors.gray}undefined${colors.reset}`;
722
+ if (typeof value === "string") {
723
+ return `${colors.green}"${value}"${colors.reset}`;
724
+ }
725
+ if (typeof value === "number") {
726
+ return `${colors.cyan}${value}${colors.reset}`;
727
+ }
728
+ if (typeof value === "boolean") {
729
+ return `${colors.magenta}${value}${colors.reset}`;
730
+ }
731
+ if (Array.isArray(value)) {
732
+ if (value.length === 0) return "[]";
733
+ const items = value.map((v) => formatValue(v, indent + 2));
734
+ return `[
735
+ ${pad} ${items.join(`,
736
+ ${pad} `)}
737
+ ${pad}]`;
738
+ }
739
+ if (typeof value === "object") {
740
+ const entries = Object.entries(value);
741
+ if (entries.length === 0) return "{}";
742
+ const items = entries.map(
743
+ ([k, v]) => `${colors.blue}${k}${colors.reset}: ${formatValue(v, indent + 2)}`
744
+ );
745
+ return `{
746
+ ${pad} ${items.join(`,
747
+ ${pad} `)}
748
+ ${pad}}`;
749
+ }
750
+ return String(value);
751
+ }
603
752
 
604
753
  // src/commands.ts
754
+ function evaluateExpression(expr, session) {
755
+ const trimmed = expr.trim();
756
+ const oldMatch = trimmed.match(/^old\((.+)\)$/);
757
+ if (oldMatch) {
758
+ const innerPath = oldMatch[1].trim();
759
+ if (!session.getPreContext()) {
760
+ return {
761
+ value: void 0,
762
+ error: "old() requires pre-state. Set with .context --pre <json>"
763
+ };
764
+ }
765
+ const { found, value } = session.resolvePreValue(innerPath);
766
+ if (!found) {
767
+ return { value: void 0, error: `Cannot resolve '${innerPath}' in pre-state context` };
768
+ }
769
+ return { value };
770
+ }
771
+ if (trimmed.startsWith("(") && trimmed.endsWith(")")) {
772
+ return evaluateExpression(trimmed.slice(1, -1), session);
773
+ }
774
+ if (trimmed.startsWith("!") || trimmed.startsWith("not ")) {
775
+ const inner = trimmed.startsWith("!") ? trimmed.slice(1) : trimmed.slice(4);
776
+ const result = evaluateExpression(inner.trim(), session);
777
+ if (result.error) return result;
778
+ return { value: !result.value };
779
+ }
780
+ for (const [opStr, opFn] of BINARY_OPS) {
781
+ const idx = findOperator(trimmed, opStr);
782
+ if (idx !== -1) {
783
+ const left = trimmed.slice(0, idx).trim();
784
+ const right = trimmed.slice(idx + opStr.length).trim();
785
+ const lResult = evaluateExpression(left, session);
786
+ if (lResult.error) return lResult;
787
+ const rResult = evaluateExpression(right, session);
788
+ if (rResult.error) return rResult;
789
+ return { value: opFn(lResult.value, rResult.value) };
790
+ }
791
+ }
792
+ if (trimmed === "true") return { value: true };
793
+ if (trimmed === "false") return { value: false };
794
+ if (trimmed === "null") return { value: null };
795
+ if (/^-?\d+$/.test(trimmed)) return { value: parseInt(trimmed, 10) };
796
+ if (/^-?\d+\.\d+$/.test(trimmed)) return { value: parseFloat(trimmed) };
797
+ if (/^"([^"]*)"$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
798
+ if (/^'([^']*)'$/.test(trimmed)) return { value: trimmed.slice(1, -1) };
799
+ if (/^[\w.]+$/.test(trimmed)) {
800
+ const { found, value } = session.resolveValue(trimmed);
801
+ if (found) return { value };
802
+ if (session.hasVariable(trimmed)) {
803
+ return { value: session.getVariable(trimmed) };
804
+ }
805
+ }
806
+ return { value: void 0, error: `Cannot evaluate: ${trimmed}` };
807
+ }
808
+ var BINARY_OPS = [
809
+ // Logical (lowest precedence — scanned first so they split outermost)
810
+ [" || ", (a, b) => Boolean(a) || Boolean(b)],
811
+ [" or ", (a, b) => Boolean(a) || Boolean(b)],
812
+ [" && ", (a, b) => Boolean(a) && Boolean(b)],
813
+ [" and ", (a, b) => Boolean(a) && Boolean(b)],
814
+ // Equality
815
+ [" == ", (a, b) => a === b || String(a) === String(b)],
816
+ [" != ", (a, b) => a !== b && String(a) !== String(b)],
817
+ // Comparison
818
+ [" >= ", (a, b) => Number(a) >= Number(b)],
819
+ [" <= ", (a, b) => Number(a) <= Number(b)],
820
+ [" > ", (a, b) => Number(a) > Number(b)],
821
+ [" < ", (a, b) => Number(a) < Number(b)],
822
+ // Arithmetic
823
+ [" + ", (a, b) => {
824
+ if (typeof a === "string" || typeof b === "string") return String(a) + String(b);
825
+ return Number(a) + Number(b);
826
+ }],
827
+ [" - ", (a, b) => Number(a) - Number(b)],
828
+ [" * ", (a, b) => Number(a) * Number(b)],
829
+ [" / ", (a, b) => {
830
+ const d = Number(b);
831
+ if (d === 0) return Infinity;
832
+ return Number(a) / d;
833
+ }]
834
+ ];
835
+ function findOperator(expr, op) {
836
+ let depth = 0;
837
+ let inString = null;
838
+ for (let i = expr.length - 1; i >= 0; i--) {
839
+ const ch = expr[i];
840
+ if (inString) {
841
+ if (ch === inString && (i === 0 || expr[i - 1] !== "\\")) inString = null;
842
+ continue;
843
+ }
844
+ if (ch === '"' || ch === "'") {
845
+ inString = ch;
846
+ continue;
847
+ }
848
+ if (ch === "(") depth--;
849
+ if (ch === ")") depth++;
850
+ if (depth === 0 && i + op.length <= expr.length && expr.slice(i, i + op.length) === op) {
851
+ return i;
852
+ }
853
+ }
854
+ return -1;
855
+ }
856
+ function prettyPrintAST(node, indent = 0) {
857
+ const pad = " ".repeat(indent);
858
+ if (node === null || node === void 0) return `${pad}${colors.gray}null${colors.reset}`;
859
+ if (typeof node === "string") return `${pad}${colors.green}"${node}"${colors.reset}`;
860
+ if (typeof node === "number") return `${pad}${colors.cyan}${node}${colors.reset}`;
861
+ if (typeof node === "boolean") return `${pad}${colors.magenta}${node}${colors.reset}`;
862
+ if (Array.isArray(node)) {
863
+ if (node.length === 0) return `${pad}[]`;
864
+ const items = node.map((item) => prettyPrintAST(item, indent + 1));
865
+ return `${pad}[
866
+ ${items.join(",\n")}
867
+ ${pad}]`;
868
+ }
869
+ if (typeof node === "object") {
870
+ const obj = node;
871
+ const kind = obj["kind"];
872
+ const entries = Object.entries(obj).filter(
873
+ ([k, v]) => k !== "location" && v !== void 0 && !(Array.isArray(v) && v.length === 0)
874
+ );
875
+ if (entries.length === 0) return `${pad}{}`;
876
+ const header = kind ? `${pad}${colors.yellow}${kind}${colors.reset} {` : `${pad}{`;
877
+ const body = entries.filter(([k]) => k !== "kind").map(([k, v]) => {
878
+ const valStr = typeof v === "object" && v !== null ? "\n" + prettyPrintAST(v, indent + 2) : " " + prettyPrintAST(v, 0).trim();
879
+ return `${pad} ${colors.blue}${k}${colors.reset}:${valStr}`;
880
+ });
881
+ return `${header}
882
+ ${body.join("\n")}
883
+ ${pad}}`;
884
+ }
885
+ return `${pad}${String(node)}`;
886
+ }
605
887
  var metaCommands = [
888
+ // ─── .help ──────────────────────────────────────────────────────────────
606
889
  {
607
890
  name: "help",
608
891
  aliases: ["h", "?"],
609
- description: "Show help for commands",
892
+ description: "Show commands",
610
893
  usage: ".help [command]",
611
- handler: (args, session) => {
894
+ handler: (args) => {
612
895
  if (args.length > 0) {
613
- const cmdName = args[0].toLowerCase();
614
- const metaCmd = metaCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
615
- if (metaCmd) {
896
+ const cmdName = args[0].toLowerCase().replace(/^\./, "");
897
+ const cmd = metaCommands.find(
898
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
899
+ );
900
+ if (cmd) {
616
901
  return {
617
902
  output: [
618
- `${colors.cyan}.${metaCmd.name}${colors.reset} - ${metaCmd.description}`,
619
- `Usage: ${metaCmd.usage}`,
620
- metaCmd.aliases.length > 0 ? `Aliases: ${metaCmd.aliases.map((a) => "." + a).join(", ")}` : ""
621
- ].filter(Boolean).join("\n")
622
- };
623
- }
624
- const islCmd = islCommands.find((c) => c.name === cmdName || c.aliases.includes(cmdName));
625
- if (islCmd) {
626
- return {
627
- output: [
628
- `${colors.cyan}:${islCmd.name}${colors.reset} - ${islCmd.description}`,
629
- `Usage: ${islCmd.usage}`,
630
- islCmd.aliases.length > 0 ? `Aliases: ${islCmd.aliases.map((a) => ":" + a).join(", ")}` : ""
903
+ `${colors.cyan}.${cmd.name}${colors.reset} \u2014 ${cmd.description}`,
904
+ `Usage: ${cmd.usage}`,
905
+ cmd.aliases.length > 0 ? `Aliases: ${cmd.aliases.map((a) => "." + a).join(", ")}` : ""
631
906
  ].filter(Boolean).join("\n")
632
907
  };
633
908
  }
@@ -635,194 +910,232 @@ var metaCommands = [
635
910
  }
636
911
  const lines = [
637
912
  "",
638
- `${colors.bold}Meta Commands${colors.reset} ${colors.gray}(REPL control)${colors.reset}`,
639
- "",
640
- ...metaCommands.map((c) => ` ${colors.cyan}.${c.name.padEnd(10)}${colors.reset} ${c.description}`),
913
+ `${colors.bold}REPL Commands${colors.reset}`,
641
914
  "",
642
- `${colors.bold}ISL Commands${colors.reset} ${colors.gray}(specification operations)${colors.reset}`,
915
+ ...metaCommands.map(
916
+ (c) => ` ${colors.cyan}.${c.name.padEnd(12)}${colors.reset} ${c.description}`
917
+ ),
643
918
  "",
644
- ...islCommands.map((c) => ` ${colors.cyan}:${c.name.padEnd(10)}${colors.reset} ${c.description}`),
919
+ `${colors.bold}ISL Input${colors.reset}`,
645
920
  "",
646
- `${colors.bold}ISL Syntax${colors.reset}`,
921
+ ` Type ISL directly \u2014 multi-line supported (braces auto-detect):`,
647
922
  "",
648
- ` ${colors.yellow}intent${colors.reset} Name {`,
649
- ` ${colors.magenta}pre${colors.reset}: condition`,
650
- ` ${colors.magenta}post${colors.reset}: condition`,
923
+ ` ${colors.yellow}domain${colors.reset} Example {`,
924
+ ` ${colors.yellow}entity${colors.reset} User {`,
925
+ ` id: ${colors.green}UUID${colors.reset}`,
926
+ ` name: ${colors.green}String${colors.reset}`,
927
+ ` }`,
651
928
  ` }`,
652
929
  "",
653
- `Type ${colors.cyan}.help <command>${colors.reset} for detailed help.`,
930
+ `Type ${colors.cyan}.help <command>${colors.reset} for details.`,
654
931
  ""
655
932
  ];
656
933
  return { output: lines.join("\n") };
657
934
  }
658
935
  },
936
+ // ─── .parse ─────────────────────────────────────────────────────────────
659
937
  {
660
- name: "exit",
661
- aliases: ["quit", "q"],
662
- description: "Exit the REPL",
663
- usage: ".exit",
664
- handler: () => {
665
- return { exit: true };
666
- }
667
- },
668
- {
669
- name: "clear",
670
- aliases: ["cls"],
671
- description: "Clear session state (intents, variables)",
672
- usage: ".clear",
938
+ name: "parse",
939
+ aliases: ["p", "ast"],
940
+ description: "Parse ISL and show AST",
941
+ usage: ".parse <isl>",
673
942
  handler: (args, session) => {
674
- session.clear();
675
- return { output: formatSuccess("Session cleared") };
943
+ const input = args.join(" ").trim();
944
+ if (!input) {
945
+ return { output: 'Usage: .parse <isl code>\nExample: .parse domain Foo { version: "1.0" }' };
946
+ }
947
+ try {
948
+ const { parse } = __require("@isl-lang/parser");
949
+ const result = parse(input, "<repl>");
950
+ if (!result.success || result.errors.length > 0) {
951
+ const errLines = result.errors.map((e) => {
952
+ const loc = e.location;
953
+ if (loc) {
954
+ return formatParseError(input, e.message, loc.line, loc.column);
955
+ }
956
+ return formatError(e.message);
957
+ });
958
+ return { output: errLines.join("\n") };
959
+ }
960
+ if (result.domain) {
961
+ session.setDomainAST(result.domain);
962
+ return {
963
+ output: formatSuccess("Parsed successfully") + "\n" + prettyPrintAST(result.domain)
964
+ };
965
+ }
966
+ return { output: formatWarning("Parse returned no AST") };
967
+ } catch {
968
+ return {
969
+ output: formatWarning(
970
+ "Real parser not available \u2014 install @isl-lang/parser.\nFalling back to simple parse."
971
+ )
972
+ };
973
+ }
676
974
  }
677
975
  },
976
+ // ─── .eval ──────────────────────────────────────────────────────────────
678
977
  {
679
- name: "history",
680
- aliases: ["hist"],
681
- description: "Show command history",
682
- usage: ".history [n]",
978
+ name: "eval",
979
+ aliases: ["e"],
980
+ description: "Evaluate expression against context",
981
+ usage: ".eval <expression>",
683
982
  handler: (args, session) => {
684
- const count = args.length > 0 ? parseInt(args[0]) : 10;
685
- const history = session.getHistory(count);
686
- if (history.length === 0) {
687
- return { output: "No history." };
983
+ const expr = args.join(" ").trim();
984
+ if (!expr) {
985
+ return {
986
+ output: [
987
+ "Usage: .eval <expression>",
988
+ "",
989
+ "Examples:",
990
+ ' .eval user.email == "test@x.com"',
991
+ " .eval user.age > 30",
992
+ " .eval old(user.age)",
993
+ "",
994
+ 'Set context first: .context { "user": { "email": "test@x.com" } }'
995
+ ].join("\n")
996
+ };
688
997
  }
689
- const lines = [
690
- `${colors.bold}History${colors.reset} (last ${history.length} entries)`,
691
- "",
692
- ...history.map((entry, i) => {
693
- const num = String(i + 1).padStart(3);
694
- const preview = entry.split("\n")[0];
695
- const more = entry.includes("\n") ? ` ${colors.gray}...${colors.reset}` : "";
696
- return ` ${colors.gray}${num}${colors.reset} ${preview}${more}`;
697
- })
698
- ];
699
- return { output: lines.join("\n") };
998
+ const result = evaluateExpression(expr, session);
999
+ if (result.error) {
1000
+ return { output: formatError(result.error) };
1001
+ }
1002
+ session.setLastResult(result.value);
1003
+ return {
1004
+ output: `${colors.cyan}\u2192${colors.reset} ${formatValue(result.value)}`
1005
+ };
700
1006
  }
701
- }
702
- ];
703
- var islCommands = [
1007
+ },
1008
+ // ─── .check ─────────────────────────────────────────────────────────────
704
1009
  {
705
1010
  name: "check",
706
1011
  aliases: ["c"],
707
- description: "Type check an intent",
708
- usage: ":check <intent>",
1012
+ description: "Type check the current session",
1013
+ usage: ".check [intent]",
709
1014
  handler: (args, session) => {
710
- if (args.length === 0) {
711
- const intents = session.getAllIntents();
712
- if (intents.length === 0) {
713
- return { output: formatWarning("No intents defined. Define one with: intent Name { ... }") };
1015
+ if (args.length > 0) {
1016
+ const intentName = args[0];
1017
+ const intent = session.getIntent(intentName);
1018
+ if (!intent) {
1019
+ const available = session.getIntentNames().join(", ") || "(none)";
1020
+ return {
1021
+ output: formatError(`Unknown intent: ${intentName}
1022
+ Available: ${available}`)
1023
+ };
714
1024
  }
715
- const lines2 = [
716
- formatSuccess("Type check passed"),
717
- ""
718
- ];
719
- for (const intent2 of intents) {
720
- lines2.push(`${colors.bold}${intent2.name}${colors.reset}`);
721
- for (const pre of intent2.preconditions) {
722
- lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightCondition(pre.expression)}`);
723
- }
724
- for (const post of intent2.postconditions) {
725
- lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightCondition(post.expression)}`);
726
- }
727
- lines2.push("");
1025
+ const lines2 = [formatSuccess("Type check passed"), ""];
1026
+ for (const pre of intent.preconditions) {
1027
+ lines2.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
1028
+ }
1029
+ for (const post of intent.postconditions) {
1030
+ lines2.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
728
1031
  }
729
1032
  return { output: lines2.join("\n") };
730
1033
  }
731
- const intentName = args[0];
732
- const intent = session.getIntent(intentName);
733
- if (!intent) {
734
- const available = session.getIntentNames().join(", ") || "(none)";
1034
+ const intents = session.getAllIntents();
1035
+ if (intents.length === 0) {
735
1036
  return {
736
- output: formatError(`Unknown intent: ${intentName}
737
- Available: ${available}`)
1037
+ output: formatWarning("No intents defined. Write ISL or use .load <file>")
738
1038
  };
739
1039
  }
740
- const lines = [
741
- formatSuccess("Type check passed"),
742
- ""
743
- ];
744
- for (const pre of intent.preconditions) {
745
- lines.push(` pre: ${highlightCondition(pre.expression)} ${colors.green}\u2713${colors.reset}`);
746
- }
747
- for (const post of intent.postconditions) {
748
- lines.push(` post: ${highlightCondition(post.expression)} ${colors.green}\u2713${colors.reset}`);
1040
+ const lines = [formatSuccess(`Type check passed \u2014 ${intents.length} intent(s)`), ""];
1041
+ for (const intent of intents) {
1042
+ lines.push(`${colors.bold}${intent.name}${colors.reset}`);
1043
+ for (const pre of intent.preconditions) {
1044
+ lines.push(` ${colors.green}\u2713${colors.reset} pre: ${highlightExpression(pre.expression)}`);
1045
+ }
1046
+ for (const post of intent.postconditions) {
1047
+ lines.push(` ${colors.green}\u2713${colors.reset} post: ${highlightExpression(post.expression)}`);
1048
+ }
1049
+ lines.push("");
749
1050
  }
750
1051
  return { output: lines.join("\n") };
751
1052
  }
752
1053
  },
1054
+ // ─── .gen ───────────────────────────────────────────────────────────────
753
1055
  {
754
1056
  name: "gen",
755
1057
  aliases: ["generate", "g"],
756
- description: "Generate code from an intent",
757
- usage: ":gen <target> <intent>",
1058
+ description: "Generate TypeScript from intent",
1059
+ usage: ".gen [intent]",
758
1060
  handler: (args, session) => {
759
- if (args.length < 2) {
760
- return {
761
- output: [
762
- "Usage: :gen <target> <intent>",
763
- "",
764
- "Targets:",
765
- " typescript Generate TypeScript contract",
766
- " rust Generate Rust contract",
767
- " go Generate Go contract",
768
- " openapi Generate OpenAPI schema"
769
- ].join("\n")
770
- };
771
- }
772
- const target = args[0].toLowerCase();
773
- const intentName = args[1];
774
- const intent = session.getIntent(intentName);
775
- if (!intent) {
776
- const available = session.getIntentNames().join(", ") || "(none)";
1061
+ const intents = args.length > 0 ? [session.getIntent(args[0])].filter(Boolean) : session.getAllIntents();
1062
+ if (intents.length === 0) {
777
1063
  return {
778
- output: formatError(`Unknown intent: ${intentName}
779
- Available: ${available}`)
1064
+ output: args.length > 0 ? formatError(`Unknown intent: ${args[0]}
1065
+ Available: ${session.getIntentNames().join(", ") || "(none)"}`) : formatWarning("No intents defined. Write ISL or use .load <file>")
780
1066
  };
781
1067
  }
782
- switch (target) {
783
- case "typescript":
784
- case "ts":
785
- return { output: generateTypeScript(intent) };
786
- case "rust":
787
- case "rs":
788
- return { output: generateRust(intent) };
789
- case "go":
790
- return { output: generateGo(intent) };
791
- case "openapi":
792
- case "oas":
793
- return { output: generateOpenAPI(intent) };
794
- default:
795
- return {
796
- output: formatError(`Unknown target: ${target}
797
- Available: typescript, rust, go, openapi`)
798
- };
1068
+ const lines = [`${colors.gray}// Generated TypeScript${colors.reset}`, ""];
1069
+ for (const intent of intents) {
1070
+ lines.push(`interface ${intent.name}Contract {`);
1071
+ if (intent.preconditions.length > 0) {
1072
+ lines.push(" /** Preconditions */");
1073
+ for (const pre of intent.preconditions) {
1074
+ lines.push(` checkPre(): boolean; // ${pre.expression}`);
1075
+ }
1076
+ }
1077
+ if (intent.postconditions.length > 0) {
1078
+ lines.push(" /** Postconditions */");
1079
+ for (const post of intent.postconditions) {
1080
+ lines.push(` checkPost(): boolean; // ${post.expression}`);
1081
+ }
1082
+ }
1083
+ if (intent.invariants.length > 0) {
1084
+ lines.push(" /** Invariants */");
1085
+ for (const inv of intent.invariants) {
1086
+ lines.push(` checkInvariant(): boolean; // ${inv.expression}`);
1087
+ }
1088
+ }
1089
+ lines.push("}");
1090
+ lines.push("");
799
1091
  }
1092
+ return { output: lines.join("\n") };
800
1093
  }
801
1094
  },
1095
+ // ─── .load ──────────────────────────────────────────────────────────────
802
1096
  {
803
1097
  name: "load",
804
1098
  aliases: ["l"],
805
- description: "Load intents from a file",
806
- usage: ":load <file.isl>",
1099
+ description: "Load an .isl file",
1100
+ usage: ".load <file.isl>",
807
1101
  handler: (args, session) => {
808
1102
  if (args.length === 0) {
809
- return { output: "Usage: :load <file.isl>" };
1103
+ return { output: "Usage: .load <file.isl>" };
810
1104
  }
811
1105
  const filePath = args[0];
812
- let result = { intents: [], errors: [] };
813
- session.loadFile(filePath).then((r) => {
814
- result = r;
815
- }).catch((e) => {
816
- result.errors.push(String(e));
817
- });
818
- const fs4 = __require("fs");
819
- const path4 = __require("path");
1106
+ const resolvedPath = path3.isAbsolute(filePath) ? filePath : path3.resolve(process.cwd(), filePath);
1107
+ if (!fs3.existsSync(resolvedPath)) {
1108
+ return { output: formatError(`File not found: ${resolvedPath}`) };
1109
+ }
820
1110
  try {
821
- const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
822
- if (!fs4.existsSync(resolvedPath)) {
823
- return { output: formatError(`File not found: ${resolvedPath}`) };
1111
+ const content = fs3.readFileSync(resolvedPath, "utf-8");
1112
+ try {
1113
+ const { parse } = __require("@isl-lang/parser");
1114
+ const result = parse(content, resolvedPath);
1115
+ if (!result.success || result.errors.length > 0) {
1116
+ const errLines = result.errors.map((e) => {
1117
+ const loc = e.location;
1118
+ if (loc) {
1119
+ return formatParseError(content, e.message, loc.line, loc.column);
1120
+ }
1121
+ return formatError(e.message);
1122
+ });
1123
+ return { output: errLines.join("\n") };
1124
+ }
1125
+ if (result.domain) {
1126
+ session.setDomainAST(result.domain);
1127
+ const domain = result.domain;
1128
+ const name = domain.name?.name ?? "Unknown";
1129
+ const entityCount = domain.entities?.length ?? 0;
1130
+ const behaviorCount = domain.behaviors?.length ?? 0;
1131
+ return {
1132
+ output: formatSuccess(
1133
+ `Loaded: ${name} (${entityCount} entities, ${behaviorCount} behaviors) from ${path3.basename(filePath)}`
1134
+ )
1135
+ };
1136
+ }
1137
+ } catch {
824
1138
  }
825
- const content = fs4.readFileSync(resolvedPath, "utf-8");
826
1139
  const intentRegex = /(?:intent|behavior)\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
827
1140
  let match;
828
1141
  let count = 0;
@@ -834,37 +1147,125 @@ Available: typescript, rust, go, openapi`)
834
1147
  }
835
1148
  }
836
1149
  if (count === 0) {
837
- return { output: formatWarning("No intents found in file") };
1150
+ return { output: formatWarning("No intents/behaviors found in file") };
838
1151
  }
839
1152
  return {
840
- output: formatSuccess(`Loaded ${count} intent(s) from ${filePath}`)
1153
+ output: formatSuccess(`Loaded ${count} intent(s) from ${path3.basename(filePath)}`)
841
1154
  };
842
1155
  } catch (error) {
843
1156
  return {
844
- output: formatError(`Failed to load: ${error instanceof Error ? error.message : String(error)}`)
1157
+ output: formatError(
1158
+ `Failed to load: ${error instanceof Error ? error.message : String(error)}`
1159
+ )
845
1160
  };
846
1161
  }
847
1162
  }
848
1163
  },
1164
+ // ─── .context ───────────────────────────────────────────────────────────
1165
+ {
1166
+ name: "context",
1167
+ aliases: ["ctx"],
1168
+ description: "Set evaluation context (JSON)",
1169
+ usage: ".context <json> | .context --pre <json>",
1170
+ handler: (args, session) => {
1171
+ const input = args.join(" ").trim();
1172
+ if (!input) {
1173
+ const ctx = session.getEvalContext();
1174
+ const pre = session.getPreContext();
1175
+ if (Object.keys(ctx).length === 0 && !pre) {
1176
+ return {
1177
+ output: [
1178
+ "No context set.",
1179
+ "",
1180
+ "Usage:",
1181
+ ' .context { "user": { "email": "test@x.com", "age": 25 } }',
1182
+ ' .context --pre { "user": { "age": 20 } }'
1183
+ ].join("\n")
1184
+ };
1185
+ }
1186
+ const lines = [];
1187
+ if (Object.keys(ctx).length > 0) {
1188
+ lines.push(`${colors.bold}Context:${colors.reset}`);
1189
+ lines.push(formatValue(ctx));
1190
+ }
1191
+ if (pre) {
1192
+ lines.push(`${colors.bold}Pre-state:${colors.reset}`);
1193
+ lines.push(formatValue(pre));
1194
+ }
1195
+ return { output: lines.join("\n") };
1196
+ }
1197
+ if (input.startsWith("--pre ")) {
1198
+ const json = input.slice(6).trim();
1199
+ const result2 = session.setPreContext(json);
1200
+ if (!result2.success) {
1201
+ return { output: formatError(`Invalid JSON: ${result2.error}`) };
1202
+ }
1203
+ return { output: formatSuccess("Pre-state context set") };
1204
+ }
1205
+ const result = session.setEvalContext(input);
1206
+ if (!result.success) {
1207
+ return { output: formatError(`Invalid JSON: ${result.error}`) };
1208
+ }
1209
+ return {
1210
+ output: formatSuccess(
1211
+ `Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`
1212
+ )
1213
+ };
1214
+ }
1215
+ },
1216
+ // ─── .clear ─────────────────────────────────────────────────────────────
1217
+ {
1218
+ name: "clear",
1219
+ aliases: ["cls", "reset"],
1220
+ description: "Reset session state",
1221
+ usage: ".clear",
1222
+ handler: (_args, session) => {
1223
+ session.clear();
1224
+ return { output: formatSuccess("Session cleared") };
1225
+ }
1226
+ },
1227
+ // ─── .history ───────────────────────────────────────────────────────────
1228
+ {
1229
+ name: "history",
1230
+ aliases: ["hist"],
1231
+ description: "Show command history",
1232
+ usage: ".history [n]",
1233
+ handler: (args, session) => {
1234
+ const count = args.length > 0 ? parseInt(args[0], 10) : 10;
1235
+ const history = session.getHistory(count);
1236
+ if (history.length === 0) {
1237
+ return { output: "No history." };
1238
+ }
1239
+ const lines = [
1240
+ `${colors.bold}History${colors.reset} (last ${history.length} entries)`,
1241
+ "",
1242
+ ...history.map((entry, i) => {
1243
+ const num = String(i + 1).padStart(3);
1244
+ const preview = entry.split("\n")[0];
1245
+ const more = entry.includes("\n") ? ` ${colors.gray}...${colors.reset}` : "";
1246
+ return ` ${colors.gray}${num}${colors.reset} ${preview}${more}`;
1247
+ })
1248
+ ];
1249
+ return { output: lines.join("\n") };
1250
+ }
1251
+ },
1252
+ // ─── .list ──────────────────────────────────────────────────────────────
849
1253
  {
850
1254
  name: "list",
851
1255
  aliases: ["ls"],
852
- description: "List all defined intents",
853
- usage: ":list",
854
- handler: (args, session) => {
1256
+ description: "List defined intents",
1257
+ usage: ".list",
1258
+ handler: (_args, session) => {
855
1259
  const intents = session.getAllIntents();
856
1260
  if (intents.length === 0) {
857
1261
  return { output: "No intents defined." };
858
1262
  }
859
1263
  const lines = [""];
860
1264
  for (const intent of intents) {
861
- const preCount = intent.preconditions.length;
862
- const postCount = intent.postconditions.length;
863
- const invCount = intent.invariants.length;
864
1265
  const parts = [];
865
- if (preCount > 0) parts.push(`${preCount} pre`);
866
- if (postCount > 0) parts.push(`${postCount} post`);
867
- if (invCount > 0) parts.push(`${invCount} invariant`);
1266
+ if (intent.preconditions.length > 0) parts.push(`${intent.preconditions.length} pre`);
1267
+ if (intent.postconditions.length > 0) parts.push(`${intent.postconditions.length} post`);
1268
+ if (intent.invariants.length > 0) parts.push(`${intent.invariants.length} invariant`);
868
1269
  const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
869
1270
  lines.push(` ${colors.cyan}${intent.name}${colors.reset}${summary}`);
870
1271
  }
@@ -872,31 +1273,26 @@ Available: typescript, rust, go, openapi`)
872
1273
  return { output: lines.join("\n") };
873
1274
  }
874
1275
  },
1276
+ // ─── .inspect ───────────────────────────────────────────────────────────
875
1277
  {
876
1278
  name: "inspect",
877
1279
  aliases: ["i", "show"],
878
1280
  description: "Show full details of an intent",
879
- usage: ":inspect <intent>",
1281
+ usage: ".inspect [intent]",
880
1282
  handler: (args, session) => {
881
1283
  if (args.length === 0) {
882
1284
  const summary = session.getSummary();
883
- const files = session.getLoadedFiles();
1285
+ const ctx = session.getEvalContext();
884
1286
  const lines = [
885
1287
  "",
886
1288
  `${colors.bold}Session Summary${colors.reset}`,
887
1289
  "",
888
1290
  ` Intents: ${summary.intentCount}`,
889
1291
  ` Variables: ${summary.variableCount}`,
890
- ` History: ${summary.historyCount} entries`
1292
+ ` Context: ${Object.keys(ctx).length} keys`,
1293
+ ` History: ${summary.historyCount} entries`,
1294
+ ""
891
1295
  ];
892
- if (files.length > 0) {
893
- lines.push("");
894
- lines.push(`${colors.bold}Loaded Files${colors.reset}`);
895
- for (const file of files) {
896
- lines.push(` ${file}`);
897
- }
898
- }
899
- lines.push("");
900
1296
  return { output: lines.join("\n") };
901
1297
  }
902
1298
  const intentName = args[0];
@@ -911,178 +1307,17 @@ Available: ${available}`)
911
1307
  return { output: formatIntent(intent) };
912
1308
  }
913
1309
  },
1310
+ // ─── .exit ──────────────────────────────────────────────────────────────
914
1311
  {
915
- name: "export",
916
- aliases: ["save"],
917
- description: "Export intents to a file",
918
- usage: ":export <file.isl>",
919
- handler: (args, session) => {
920
- if (args.length === 0) {
921
- return { output: "Usage: :export <file.isl>" };
922
- }
923
- const filePath = args[0];
924
- const fs4 = __require("fs");
925
- const path4 = __require("path");
926
- try {
927
- const resolvedPath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
928
- const intents = session.getAllIntents();
929
- if (intents.length === 0) {
930
- return { output: formatWarning("No intents to export") };
931
- }
932
- const lines = [];
933
- lines.push("// Exported ISL intents");
934
- lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
935
- lines.push("");
936
- for (const intent of intents) {
937
- lines.push(`intent ${intent.name} {`);
938
- for (const pre of intent.preconditions) {
939
- lines.push(` pre: ${pre.expression}`);
940
- }
941
- for (const post of intent.postconditions) {
942
- lines.push(` post: ${post.expression}`);
943
- }
944
- for (const inv of intent.invariants) {
945
- lines.push(` invariant: ${inv.expression}`);
946
- }
947
- lines.push("}");
948
- lines.push("");
949
- }
950
- fs4.writeFileSync(resolvedPath, lines.join("\n"));
951
- return { output: formatSuccess(`Exported ${intents.length} intent(s) to ${filePath}`) };
952
- } catch (error) {
953
- return {
954
- output: formatError(`Failed to export: ${error instanceof Error ? error.message : String(error)}`)
955
- };
956
- }
1312
+ name: "exit",
1313
+ aliases: ["quit", "q"],
1314
+ description: "Exit the REPL",
1315
+ usage: ".exit",
1316
+ handler: () => {
1317
+ return { exit: true };
957
1318
  }
958
1319
  }
959
1320
  ];
960
- function generateTypeScript(intent) {
961
- const lines = [
962
- `${colors.gray}// Generated TypeScript${colors.reset}`,
963
- `interface ${intent.name}Contract {`
964
- ];
965
- if (intent.preconditions.length > 0) {
966
- for (const pre of intent.preconditions) {
967
- const varName = extractVariableName(pre.expression);
968
- const type = inferType(pre.expression);
969
- lines.push(` pre: (${varName}: ${type}) => boolean;`);
970
- }
971
- }
972
- if (intent.postconditions.length > 0) {
973
- for (const post of intent.postconditions) {
974
- const varName = extractVariableName(post.expression);
975
- const type = inferType(post.expression);
976
- lines.push(` post: (${varName}: ${type}) => boolean;`);
977
- }
978
- }
979
- lines.push("}");
980
- return lines.join("\n");
981
- }
982
- function generateRust(intent) {
983
- const lines = [
984
- `${colors.gray}// Generated Rust${colors.reset}`,
985
- `pub trait ${intent.name}Contract {`
986
- ];
987
- if (intent.preconditions.length > 0) {
988
- for (const pre of intent.preconditions) {
989
- const varName = extractVariableName(pre.expression);
990
- const type = inferRustType(pre.expression);
991
- lines.push(` fn check_pre(&self, ${varName}: ${type}) -> bool;`);
992
- }
993
- }
994
- if (intent.postconditions.length > 0) {
995
- for (const post of intent.postconditions) {
996
- const varName = extractVariableName(post.expression);
997
- const type = inferRustType(post.expression);
998
- lines.push(` fn check_post(&self, ${varName}: ${type}) -> bool;`);
999
- }
1000
- }
1001
- lines.push("}");
1002
- return lines.join("\n");
1003
- }
1004
- function generateGo(intent) {
1005
- const lines = [
1006
- `${colors.gray}// Generated Go${colors.reset}`,
1007
- `type ${intent.name}Contract interface {`
1008
- ];
1009
- if (intent.preconditions.length > 0) {
1010
- for (const pre of intent.preconditions) {
1011
- const varName = extractVariableName(pre.expression);
1012
- const type = inferGoType(pre.expression);
1013
- lines.push(` CheckPre(${varName} ${type}) bool`);
1014
- }
1015
- }
1016
- if (intent.postconditions.length > 0) {
1017
- for (const post of intent.postconditions) {
1018
- const varName = extractVariableName(post.expression);
1019
- const type = inferGoType(post.expression);
1020
- lines.push(` CheckPost(${varName} ${type}) bool`);
1021
- }
1022
- }
1023
- lines.push("}");
1024
- return lines.join("\n");
1025
- }
1026
- function generateOpenAPI(intent) {
1027
- const lines = [
1028
- `${colors.gray}# Generated OpenAPI${colors.reset}`,
1029
- `openapi: 3.0.0`,
1030
- `paths:`,
1031
- ` /${intent.name.toLowerCase()}:`,
1032
- ` post:`,
1033
- ` summary: ${intent.name}`,
1034
- ` requestBody:`,
1035
- ` content:`,
1036
- ` application/json:`,
1037
- ` schema:`,
1038
- ` type: object`
1039
- ];
1040
- if (intent.preconditions.length > 0) {
1041
- lines.push(` # Preconditions:`);
1042
- for (const pre of intent.preconditions) {
1043
- lines.push(` # - ${pre.expression}`);
1044
- }
1045
- }
1046
- lines.push(` responses:`);
1047
- lines.push(` '200':`);
1048
- lines.push(` description: Success`);
1049
- if (intent.postconditions.length > 0) {
1050
- lines.push(` # Postconditions:`);
1051
- for (const post of intent.postconditions) {
1052
- lines.push(` # - ${post.expression}`);
1053
- }
1054
- }
1055
- return lines.join("\n");
1056
- }
1057
- function extractVariableName(expression) {
1058
- const match = expression.match(/^(\w+)/);
1059
- return match ? match[1] : "input";
1060
- }
1061
- function inferType(expression) {
1062
- if (expression.includes(".length")) return "string";
1063
- if (expression.includes(".startsWith")) return "string";
1064
- if (expression.includes(".endsWith")) return "string";
1065
- if (expression.includes(".includes")) return "string";
1066
- if (expression.includes(" > ") || expression.includes(" < ")) return "number";
1067
- return "unknown";
1068
- }
1069
- function inferRustType(expression) {
1070
- if (expression.includes(".length") || expression.includes(".len()")) return "&str";
1071
- if (expression.includes(".starts_with")) return "&str";
1072
- if (expression.includes(" > ") || expression.includes(" < ")) return "i32";
1073
- return "&str";
1074
- }
1075
- function inferGoType(expression) {
1076
- if (expression.includes(".length") || expression.includes("len(")) return "string";
1077
- if (expression.includes(" > ") || expression.includes(" < ")) return "int";
1078
- return "string";
1079
- }
1080
- function highlightCondition(expression) {
1081
- return expression.replace(
1082
- /(\w+)\s*(>|<|>=|<=|==|!=)\s*(\d+|"[^"]*")/g,
1083
- `${colors.blue}$1${colors.reset} ${colors.yellow}$2${colors.reset} ${colors.green}$3${colors.reset}`
1084
- ).replace(/\b(true|false)\b/g, `${colors.magenta}$1${colors.reset}`).replace(/\.(length|startsWith|endsWith|includes)/g, `.${colors.cyan}$1${colors.reset}`);
1085
- }
1086
1321
  function levenshteinDistance(a, b) {
1087
1322
  const matrix = [];
1088
1323
  for (let i = 0; i <= b.length; i++) {
@@ -1103,9 +1338,8 @@ function levenshteinDistance(a, b) {
1103
1338
  }
1104
1339
  return matrix[b.length][a.length];
1105
1340
  }
1106
- function findSimilarCommand(input, type) {
1107
- const commands = type === "meta" ? metaCommands : islCommands;
1108
- const names = commands.flatMap((c) => [c.name, ...c.aliases]);
1341
+ function findSimilarCommand(input, _type) {
1342
+ const names = metaCommands.flatMap((c) => [c.name, ...c.aliases]);
1109
1343
  let bestMatch = null;
1110
1344
  let bestDistance = Infinity;
1111
1345
  for (const name of names) {
@@ -1120,11 +1354,31 @@ function findSimilarCommand(input, type) {
1120
1354
 
1121
1355
  // src/completions.ts
1122
1356
  var KEYWORDS = [
1123
- { text: "intent", type: "keyword", description: "Define an intent" },
1357
+ // Structure keywords
1358
+ { text: "domain", type: "keyword", description: "Define a domain" },
1359
+ { text: "entity", type: "keyword", description: "Define an entity" },
1124
1360
  { text: "behavior", type: "keyword", description: "Define a behavior" },
1361
+ { text: "intent", type: "keyword", description: "Define an intent" },
1362
+ { text: "input", type: "keyword", description: "Input block" },
1363
+ { text: "output", type: "keyword", description: "Output block" },
1125
1364
  { text: "pre", type: "keyword", description: "Precondition" },
1126
1365
  { text: "post", type: "keyword", description: "Postcondition" },
1127
1366
  { text: "invariant", type: "keyword", description: "Invariant" },
1367
+ { text: "scenario", type: "keyword", description: "Scenario block" },
1368
+ { text: "version", type: "keyword", description: "Version declaration" },
1369
+ // Types
1370
+ { text: "String", type: "keyword", description: "String type" },
1371
+ { text: "Number", type: "keyword", description: "Number type" },
1372
+ { text: "Int", type: "keyword", description: "Integer type" },
1373
+ { text: "Decimal", type: "keyword", description: "Decimal type" },
1374
+ { text: "Boolean", type: "keyword", description: "Boolean type" },
1375
+ { text: "UUID", type: "keyword", description: "UUID type" },
1376
+ { text: "Timestamp", type: "keyword", description: "Timestamp type" },
1377
+ { text: "Duration", type: "keyword", description: "Duration type" },
1378
+ { text: "List", type: "keyword", description: "List<T> type" },
1379
+ { text: "Map", type: "keyword", description: "Map<K,V> type" },
1380
+ { text: "Optional", type: "keyword", description: "Optional<T> type" },
1381
+ // Literals and operators
1128
1382
  { text: "true", type: "keyword", description: "Boolean true" },
1129
1383
  { text: "false", type: "keyword", description: "Boolean false" },
1130
1384
  { text: "null", type: "keyword", description: "Null value" },
@@ -1134,19 +1388,16 @@ var KEYWORDS = [
1134
1388
  { text: "implies", type: "keyword", description: "Logical implication" },
1135
1389
  { text: "forall", type: "keyword", description: "Universal quantifier" },
1136
1390
  { text: "exists", type: "keyword", description: "Existential quantifier" },
1137
- { text: "in", type: "keyword", description: "Membership test" }
1391
+ { text: "in", type: "keyword", description: "Membership test" },
1392
+ { text: "old", type: "keyword", description: "Pre-state value (old(x))" }
1138
1393
  ];
1139
1394
  var META_COMMANDS = metaCommands.map((cmd) => ({
1140
1395
  text: `.${cmd.name}`,
1141
1396
  type: "command",
1142
1397
  description: cmd.description
1143
1398
  }));
1144
- var ISL_COMMANDS = islCommands.map((cmd) => ({
1145
- text: `:${cmd.name}`,
1146
- type: "command",
1147
- description: cmd.description
1148
- }));
1149
- var COMMANDS = [...META_COMMANDS, ...ISL_COMMANDS];
1399
+ var ISL_COMMANDS = [];
1400
+ var COMMANDS = [...META_COMMANDS];
1150
1401
  var GEN_TARGETS = [
1151
1402
  { text: "typescript", type: "keyword", description: "Generate TypeScript contract" },
1152
1403
  { text: "rust", type: "keyword", description: "Generate Rust contract" },
@@ -1172,7 +1423,7 @@ var CompletionProvider = class {
1172
1423
  return this.completeMetaCommand(trimmed);
1173
1424
  }
1174
1425
  if (trimmed.startsWith(":")) {
1175
- return this.completeISLCommand(trimmed);
1426
+ return this.completeMetaCommand("." + trimmed.slice(1));
1176
1427
  }
1177
1428
  return this.completeExpression(trimmed);
1178
1429
  }
@@ -1257,18 +1508,18 @@ var CompletionProvider = class {
1257
1508
  */
1258
1509
  completeFilePath(partial) {
1259
1510
  try {
1260
- const dir = path3.dirname(partial) || ".";
1261
- const base = path3.basename(partial);
1262
- const resolvedDir = path3.resolve(this.session.getConfig().cwd || process.cwd(), dir);
1263
- if (!fs3.existsSync(resolvedDir)) {
1511
+ const dir = path4.dirname(partial) || ".";
1512
+ const base = path4.basename(partial);
1513
+ const resolvedDir = path4.resolve(this.session.getConfig().cwd || process.cwd(), dir);
1514
+ if (!fs4.existsSync(resolvedDir)) {
1264
1515
  return [[], partial];
1265
1516
  }
1266
- const entries = fs3.readdirSync(resolvedDir, { withFileTypes: true });
1517
+ const entries = fs4.readdirSync(resolvedDir, { withFileTypes: true });
1267
1518
  const items = entries.filter((e) => {
1268
1519
  const name = e.name.toLowerCase();
1269
1520
  return name.startsWith(base.toLowerCase()) && (e.isDirectory() || name.endsWith(".isl"));
1270
1521
  }).map((e) => ({
1271
- text: path3.join(dir, e.name + (e.isDirectory() ? "/" : "")),
1522
+ text: path4.join(dir, e.name + (e.isDirectory() ? "/" : "")),
1272
1523
  type: "file",
1273
1524
  description: e.isDirectory() ? "Directory" : "ISL file"
1274
1525
  }));
@@ -1349,7 +1600,10 @@ var ISLREPL = class {
1349
1600
  this.options = {
1350
1601
  colors: options.colors !== false,
1351
1602
  verbose: options.verbose ?? false,
1352
- historyFile: options.historyFile
1603
+ historyFile: options.historyFile,
1604
+ load: options.load,
1605
+ context: options.context,
1606
+ parseOnly: options.parseOnly ?? false
1353
1607
  };
1354
1608
  this.session = new Session({ colors: this.options.colors });
1355
1609
  this.history = new History({
@@ -1363,6 +1617,11 @@ var ISLREPL = class {
1363
1617
  start() {
1364
1618
  if (this.running) return;
1365
1619
  this.running = true;
1620
+ this.applyStartupOptions();
1621
+ if (this.options.parseOnly || !process.stdin.isTTY) {
1622
+ this.runPipeMode();
1623
+ return;
1624
+ }
1366
1625
  this.history.load();
1367
1626
  this.rl = readline.createInterface({
1368
1627
  input: process.stdin,
@@ -1387,15 +1646,71 @@ var ISLREPL = class {
1387
1646
  if (this.buffer.length > 0) {
1388
1647
  this.buffer = [];
1389
1648
  this.braceCount = 0;
1390
- console.log("\n" + formatWarning("Input cancelled"));
1649
+ process.stdout.write("\n" + formatWarning("Input cancelled") + "\n");
1391
1650
  this.rl.setPrompt(PROMPT);
1392
1651
  this.rl.prompt();
1393
1652
  } else {
1394
- console.log("\n" + formatWarning("Use .exit or :quit to exit"));
1653
+ process.stdout.write("\n" + formatWarning("Use .exit to quit") + "\n");
1395
1654
  this.rl.prompt();
1396
1655
  }
1397
1656
  });
1398
1657
  }
1658
+ /**
1659
+ * Apply startup options (--load, --context)
1660
+ */
1661
+ applyStartupOptions() {
1662
+ if (this.options.context) {
1663
+ const result = this.session.setEvalContext(this.options.context);
1664
+ if (result.success) {
1665
+ process.stdout.write(
1666
+ formatSuccess(`Context set (${result.count} variable${result.count !== 1 ? "s" : ""})`) + "\n"
1667
+ );
1668
+ } else {
1669
+ process.stdout.write(formatError(`Invalid context JSON: ${result.error}`) + "\n");
1670
+ }
1671
+ }
1672
+ if (this.options.load) {
1673
+ const loadCmd = metaCommands.find((c) => c.name === "load");
1674
+ if (loadCmd) {
1675
+ const result = loadCmd.handler([this.options.load], this.session, this);
1676
+ if (result.output) {
1677
+ process.stdout.write(result.output + "\n");
1678
+ }
1679
+ }
1680
+ }
1681
+ }
1682
+ /**
1683
+ * Run in pipe mode (read all stdin, parse, and output)
1684
+ */
1685
+ runPipeMode() {
1686
+ let input = "";
1687
+ process.stdin.setEncoding("utf-8");
1688
+ process.stdin.on("data", (chunk) => {
1689
+ input += chunk;
1690
+ });
1691
+ process.stdin.on("end", () => {
1692
+ const trimmed = input.trim();
1693
+ if (!trimmed) {
1694
+ process.exit(0);
1695
+ return;
1696
+ }
1697
+ if (this.options.parseOnly) {
1698
+ const parseCmd = metaCommands.find((c) => c.name === "parse");
1699
+ if (parseCmd) {
1700
+ const result = parseCmd.handler(trimmed.split(" "), this.session, this);
1701
+ if (result.output) {
1702
+ process.stdout.write(result.output + "\n");
1703
+ }
1704
+ }
1705
+ } else {
1706
+ const lines = trimmed.split("\n");
1707
+ for (const line of lines) {
1708
+ this.handleLine(line);
1709
+ }
1710
+ }
1711
+ process.exit(0);
1712
+ });
1713
+ }
1399
1714
  /**
1400
1715
  * Print the welcome banner
1401
1716
  */
@@ -1415,7 +1730,7 @@ ${colors.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
1415
1730
  ${colors.bold}ISL v${VERSION}${colors.reset} \u2014 Intent Specification Language
1416
1731
  Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${colors.reset} to quit
1417
1732
  `;
1418
- console.log(banner);
1733
+ process.stdout.write(banner);
1419
1734
  }
1420
1735
  /**
1421
1736
  * Handle a line of input
@@ -1426,11 +1741,11 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
1426
1741
  return;
1427
1742
  }
1428
1743
  if (trimmed.startsWith(".") && this.buffer.length === 0) {
1429
- this.handleMetaCommand(trimmed);
1744
+ this.handleDotCommand(trimmed);
1430
1745
  return;
1431
1746
  }
1432
1747
  if (trimmed.startsWith(":") && this.buffer.length === 0) {
1433
- this.handleISLCommand(trimmed);
1748
+ this.handleDotCommand("." + trimmed.slice(1));
1434
1749
  return;
1435
1750
  }
1436
1751
  this.braceCount += (line.match(/\{/g) || []).length;
@@ -1446,78 +1761,134 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
1446
1761
  }
1447
1762
  }
1448
1763
  /**
1449
- * Handle a meta command (. prefix)
1764
+ * Handle a dot command (. prefix)
1450
1765
  */
1451
- handleMetaCommand(input) {
1766
+ handleDotCommand(input) {
1452
1767
  const parts = input.slice(1).split(/\s+/);
1453
1768
  const cmdName = parts[0]?.toLowerCase() || "";
1454
1769
  const args = parts.slice(1);
1770
+ const rawArgs = input.slice(1 + (cmdName.length || 0)).trim();
1455
1771
  const command = metaCommands.find(
1456
1772
  (c) => c.name === cmdName || c.aliases.includes(cmdName)
1457
1773
  );
1458
1774
  if (command) {
1459
1775
  this.history.add(input);
1460
- const result = command.handler(args, this.session, this);
1776
+ const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
1777
+ const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : args;
1778
+ const result = command.handler(effectiveArgs, this.session, this);
1461
1779
  if (result.output) {
1462
- console.log(result.output);
1780
+ process.stdout.write(result.output + "\n");
1463
1781
  }
1464
1782
  if (result.exit) {
1465
1783
  this.exit();
1466
1784
  }
1467
1785
  } else {
1468
- const suggestion = findSimilarCommand(cmdName, "meta");
1786
+ const suggestion = findSimilarCommand(cmdName);
1469
1787
  if (suggestion) {
1470
- console.log(formatError(`Unknown command: .${cmdName}`));
1471
- console.log(formatWarning(`Did you mean: .${suggestion}?`));
1788
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1789
+ process.stdout.write(formatWarning(`Did you mean: .${suggestion}?`) + "\n");
1472
1790
  } else {
1473
- console.log(formatError(`Unknown command: .${cmdName}`));
1474
- console.log(`Type ${colors.cyan}.help${colors.reset} for available commands`);
1475
- }
1476
- }
1477
- }
1478
- /**
1479
- * Handle an ISL command (: prefix)
1480
- */
1481
- handleISLCommand(input) {
1482
- const parts = input.slice(1).split(/\s+/);
1483
- const cmdName = parts[0]?.toLowerCase() || "";
1484
- const args = parts.slice(1);
1485
- const command = islCommands.find(
1486
- (c) => c.name === cmdName || c.aliases.includes(cmdName)
1487
- );
1488
- if (command) {
1489
- this.history.add(input);
1490
- const result = command.handler(args, this.session, this);
1491
- if (result.output) {
1492
- console.log(result.output);
1493
- }
1494
- } else {
1495
- const suggestion = findSimilarCommand(cmdName, "isl");
1496
- if (suggestion) {
1497
- console.log(formatError(`Unknown command: :${cmdName}`));
1498
- console.log(formatWarning(`Did you mean: :${suggestion}?`));
1499
- } else {
1500
- console.log(formatError(`Unknown command: :${cmdName}`));
1501
- console.log(`Type ${colors.cyan}.help${colors.reset} for available commands`);
1791
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1792
+ process.stdout.write(`Type ${colors.cyan}.help${colors.reset} for available commands
1793
+ `);
1502
1794
  }
1503
1795
  }
1504
1796
  }
1505
1797
  /**
1506
- * Evaluate ISL code
1798
+ * Evaluate ISL code (multi-line input or bare expressions)
1507
1799
  */
1508
1800
  evaluate(code) {
1509
1801
  try {
1510
1802
  const trimmed = code.trim();
1511
- if (trimmed.startsWith("intent ")) {
1803
+ try {
1804
+ const { parse } = __require("@isl-lang/parser");
1805
+ let parseInput = trimmed;
1806
+ const needsWrapper = !trimmed.startsWith("domain ");
1807
+ if (needsWrapper) {
1808
+ parseInput = `domain _REPL { version: "0.0.1"
1809
+ ${trimmed}
1810
+ }`;
1811
+ }
1812
+ const result = parse(parseInput, "<repl>");
1813
+ if (result.errors.length > 0) {
1814
+ for (const err of result.errors) {
1815
+ const loc = err.location;
1816
+ if (loc) {
1817
+ const adjustedLine = needsWrapper ? Math.max(1, loc.line - 1) : loc.line;
1818
+ const lines = trimmed.split("\n");
1819
+ const errorLine = lines[adjustedLine - 1] || "";
1820
+ process.stdout.write(
1821
+ `${colors.red}\u2717 Error at line ${adjustedLine}, col ${loc.column}:${colors.reset}
1822
+ `
1823
+ );
1824
+ process.stdout.write(` ${errorLine}
1825
+ `);
1826
+ process.stdout.write(` ${" ".repeat(Math.max(0, loc.column - 1))}${colors.red}^^^^^${colors.reset}
1827
+ `);
1828
+ const typeMatch = err.message.match(/Unknown type '(\w+)'/i) || err.message.match(/unexpected.*'(\w+)'/i);
1829
+ if (typeMatch) {
1830
+ const suggestion = suggestCorrection(typeMatch[1]);
1831
+ if (suggestion) {
1832
+ process.stdout.write(
1833
+ ` ${colors.yellow}Did you mean '${suggestion}'?${colors.reset}
1834
+ `
1835
+ );
1836
+ }
1837
+ } else {
1838
+ process.stdout.write(` ${err.message}
1839
+ `);
1840
+ }
1841
+ } else {
1842
+ process.stdout.write(formatError(err.message) + "\n");
1843
+ }
1844
+ }
1845
+ return;
1846
+ }
1847
+ if (result.domain) {
1848
+ this.session.setDomainAST(result.domain);
1849
+ const domain = result.domain;
1850
+ if (needsWrapper) {
1851
+ const entityCount = domain.entities?.length ?? 0;
1852
+ const behaviorCount = domain.behaviors?.length ?? 0;
1853
+ const parts = [];
1854
+ if (entityCount > 0) parts.push(`${entityCount} entit${entityCount === 1 ? "y" : "ies"}`);
1855
+ if (behaviorCount > 0) parts.push(`${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"}`);
1856
+ if (parts.length > 0) {
1857
+ process.stdout.write(
1858
+ formatSuccess(`Parsed: ${parts.join(", ")}`) + "\n"
1859
+ );
1860
+ } else {
1861
+ process.stdout.write(formatSuccess("Parsed successfully") + "\n");
1862
+ }
1863
+ } else {
1864
+ const name = domain.name?.name ?? "Unknown";
1865
+ const entityCount = domain.entities?.length ?? 0;
1866
+ const behaviorCount = domain.behaviors?.length ?? 0;
1867
+ process.stdout.write(
1868
+ formatSuccess(
1869
+ `Parsed: domain ${name} (${entityCount} entit${entityCount === 1 ? "y" : "ies"}, ${behaviorCount} behavior${behaviorCount === 1 ? "" : "s"})`
1870
+ ) + "\n"
1871
+ );
1872
+ }
1873
+ return;
1874
+ }
1875
+ } catch {
1876
+ }
1877
+ if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
1512
1878
  this.evaluateIntent(trimmed);
1513
1879
  return;
1514
1880
  }
1515
- if (trimmed.startsWith("behavior ")) {
1516
- this.evaluateIntent(trimmed);
1881
+ if (trimmed.startsWith("domain ")) {
1882
+ process.stdout.write(formatSuccess("Parsed domain block") + "\n");
1517
1883
  return;
1518
1884
  }
1519
- console.log(formatWarning(`Cannot evaluate: ${trimmed.split("\n")[0]}...`));
1520
- console.log(`Use ${colors.cyan}intent Name { ... }${colors.reset} to define an intent`);
1885
+ process.stdout.write(
1886
+ formatWarning(`Cannot evaluate: ${trimmed.split("\n")[0]}...`) + "\n"
1887
+ );
1888
+ process.stdout.write(
1889
+ `Use ${colors.cyan}.help${colors.reset} for available commands
1890
+ `
1891
+ );
1521
1892
  } catch (error) {
1522
1893
  this.printError(error);
1523
1894
  }
@@ -1537,13 +1908,15 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
1537
1908
  if (postCount > 0) parts.push(`${postCount} post`);
1538
1909
  if (invCount > 0) parts.push(`${invCount} invariant`);
1539
1910
  const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1540
- console.log(formatSuccess(`Intent '${intent.name}' defined${summary}`));
1911
+ process.stdout.write(
1912
+ formatSuccess(`Intent '${intent.name}' defined${summary}`) + "\n"
1913
+ );
1541
1914
  } else {
1542
1915
  const behaviorMatch = code.match(/^behavior\s+(\w+)\s*\{([\s\S]*)\}$/);
1543
1916
  if (behaviorMatch) {
1544
1917
  const name = behaviorMatch[1];
1545
1918
  const body = behaviorMatch[2];
1546
- const intent2 = {
1919
+ const newIntent = {
1547
1920
  name,
1548
1921
  preconditions: [],
1549
1922
  postconditions: [],
@@ -1553,51 +1926,28 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
1553
1926
  };
1554
1927
  const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
1555
1928
  if (preSection) {
1556
- const conditions = preSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
1557
- for (const cond of conditions) {
1558
- const expr = cond.replace(/^-\s*/, "").trim();
1559
- if (expr) {
1560
- intent2.preconditions.push({ expression: expr });
1561
- }
1929
+ for (const line of preSection[1].trim().split("\n")) {
1930
+ const expr = line.trim().replace(/^-\s*/, "").trim();
1931
+ if (expr) newIntent.preconditions.push({ expression: expr });
1562
1932
  }
1563
1933
  }
1564
1934
  const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
1565
1935
  if (postSection) {
1566
- const conditions = postSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
1567
- for (const cond of conditions) {
1568
- const expr = cond.replace(/^-\s*/, "").trim();
1569
- if (expr) {
1570
- intent2.postconditions.push({ expression: expr });
1571
- }
1936
+ for (const line of postSection[1].trim().split("\n")) {
1937
+ const expr = line.trim().replace(/^-\s*/, "").trim();
1938
+ if (expr) newIntent.postconditions.push({ expression: expr });
1572
1939
  }
1573
1940
  }
1574
- this.session.defineIntent(intent2);
1575
- const preCount = intent2.preconditions.length;
1576
- const postCount = intent2.postconditions.length;
1941
+ this.session.defineIntent(newIntent);
1577
1942
  const parts = [];
1578
- if (preCount > 0) parts.push(`${preCount} pre`);
1579
- if (postCount > 0) parts.push(`${postCount} post`);
1943
+ if (newIntent.preconditions.length > 0) parts.push(`${newIntent.preconditions.length} pre`);
1944
+ if (newIntent.postconditions.length > 0) parts.push(`${newIntent.postconditions.length} post`);
1580
1945
  const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1581
- console.log(formatSuccess(`Intent '${intent2.name}' defined${summary}`));
1946
+ process.stdout.write(
1947
+ formatSuccess(`Intent '${name}' defined${summary}`) + "\n"
1948
+ );
1582
1949
  } else {
1583
- this.printParseError(code, "Failed to parse intent definition");
1584
- }
1585
- }
1586
- }
1587
- /**
1588
- * Print a parse error with location info
1589
- */
1590
- printParseError(code, message, line, column) {
1591
- console.log(formatError(message));
1592
- if (line !== void 0 && column !== void 0) {
1593
- const lines = code.split("\n");
1594
- const errorLine = lines[line - 1] || "";
1595
- console.log(` ${colors.gray}${line} |${colors.reset} ${errorLine}`);
1596
- console.log(` ${colors.gray}${" ".repeat(String(line).length)} |${colors.reset} ${" ".repeat(column - 1)}${colors.red}^${colors.reset}`);
1597
- } else {
1598
- const firstLine = code.split("\n")[0];
1599
- if (firstLine) {
1600
- console.log(` ${colors.gray}>${colors.reset} ${firstLine}`);
1950
+ process.stdout.write(formatError("Failed to parse intent definition") + "\n");
1601
1951
  }
1602
1952
  }
1603
1953
  }
@@ -1606,12 +1956,12 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
1606
1956
  */
1607
1957
  printError(error) {
1608
1958
  if (error instanceof Error) {
1609
- console.log(formatError(error.message));
1959
+ process.stdout.write(formatError(error.message) + "\n");
1610
1960
  if (this.options.verbose && error.stack) {
1611
- console.log(colors.gray + error.stack + colors.reset);
1961
+ process.stdout.write(colors.gray + error.stack + colors.reset + "\n");
1612
1962
  }
1613
1963
  } else {
1614
- console.log(formatError(String(error)));
1964
+ process.stdout.write(formatError(String(error)) + "\n");
1615
1965
  }
1616
1966
  }
1617
1967
  /**
@@ -1620,8 +1970,9 @@ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${color
1620
1970
  exit() {
1621
1971
  this.running = false;
1622
1972
  this.history.save();
1623
- console.log(`
1624
- ${colors.yellow}Goodbye!${colors.reset}`);
1973
+ process.stdout.write(`
1974
+ ${colors.yellow}Goodbye!${colors.reset}
1975
+ `);
1625
1976
  if (this.rl) {
1626
1977
  this.rl.close();
1627
1978
  }
@@ -1644,31 +1995,21 @@ ${colors.yellow}Goodbye!${colors.reset}`);
1644
1995
  */
1645
1996
  async executeOnce(input) {
1646
1997
  const trimmed = input.trim();
1647
- if (trimmed.startsWith(".")) {
1648
- const parts = trimmed.slice(1).split(/\s+/);
1998
+ if (trimmed.startsWith(".") || trimmed.startsWith(":")) {
1999
+ const normalized = trimmed.startsWith(":") ? "." + trimmed.slice(1) : trimmed;
2000
+ const parts = normalized.slice(1).split(/\s+/);
1649
2001
  const cmdName = parts[0]?.toLowerCase() || "";
1650
- const args = parts.slice(1);
2002
+ const rawArgs = normalized.slice(1 + (cmdName.length || 0)).trim();
1651
2003
  const command = metaCommands.find(
1652
2004
  (c) => c.name === cmdName || c.aliases.includes(cmdName)
1653
2005
  );
1654
2006
  if (command) {
1655
- const result = command.handler(args, this.session, this);
2007
+ const needsRawArgs = ["context", "ctx", "eval", "e", "parse", "p", "ast", "load", "l"];
2008
+ const effectiveArgs = needsRawArgs.includes(cmdName) && rawArgs ? [rawArgs] : parts.slice(1);
2009
+ const result = command.handler(effectiveArgs, this.session, this);
1656
2010
  return { success: true, output: result.output };
1657
2011
  }
1658
- return { success: false, error: `Unknown command: .${cmdName}` };
1659
- }
1660
- if (trimmed.startsWith(":")) {
1661
- const parts = trimmed.slice(1).split(/\s+/);
1662
- const cmdName = parts[0]?.toLowerCase() || "";
1663
- const args = parts.slice(1);
1664
- const command = islCommands.find(
1665
- (c) => c.name === cmdName || c.aliases.includes(cmdName)
1666
- );
1667
- if (command) {
1668
- const result = command.handler(args, this.session, this);
1669
- return { success: true, output: result.output };
1670
- }
1671
- return { success: false, error: `Unknown command: :${cmdName}` };
2012
+ return { success: false, error: `Unknown command: ${cmdName}` };
1672
2013
  }
1673
2014
  if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
1674
2015
  const intent = this.session.parseIntent(trimmed);
@@ -1681,6 +2022,29 @@ ${colors.yellow}Goodbye!${colors.reset}`);
1681
2022
  return { success: false, error: "Unknown input" };
1682
2023
  }
1683
2024
  };
2025
+ var KNOWN_TYPES = [
2026
+ "String",
2027
+ "Int",
2028
+ "Decimal",
2029
+ "Boolean",
2030
+ "UUID",
2031
+ "Timestamp",
2032
+ "Duration",
2033
+ "List",
2034
+ "Map",
2035
+ "Optional",
2036
+ "Number"
2037
+ ];
2038
+ function suggestCorrection(typo) {
2039
+ const lower = typo.toLowerCase();
2040
+ for (const t of KNOWN_TYPES) {
2041
+ if (t.toLowerCase() === lower) return t;
2042
+ if (t.toLowerCase().startsWith(lower.slice(0, 3)) && Math.abs(t.length - typo.length) <= 2) {
2043
+ return t;
2044
+ }
2045
+ }
2046
+ return null;
2047
+ }
1684
2048
  function startREPL(options) {
1685
2049
  const repl = new ISLREPL(options);
1686
2050
  repl.start();
@@ -1688,44 +2052,102 @@ function startREPL(options) {
1688
2052
  }
1689
2053
 
1690
2054
  // src/cli.ts
1691
- function main() {
1692
- const args = process.argv.slice(2);
2055
+ function parseArgs(argv) {
1693
2056
  const options = {
1694
- colors: !args.includes("--no-color"),
1695
- verbose: args.includes("--verbose") || args.includes("-v")
2057
+ help: false,
2058
+ colors: true,
2059
+ verbose: false,
2060
+ parseOnly: false
1696
2061
  };
1697
- if (args.includes("--help") || args.includes("-h")) {
1698
- console.log(`
2062
+ for (let i = 0; i < argv.length; i++) {
2063
+ const arg = argv[i];
2064
+ switch (arg) {
2065
+ case "--help":
2066
+ case "-h":
2067
+ options.help = true;
2068
+ break;
2069
+ case "--no-color":
2070
+ options.colors = false;
2071
+ break;
2072
+ case "--verbose":
2073
+ case "-v":
2074
+ options.verbose = true;
2075
+ break;
2076
+ case "--load":
2077
+ options.load = argv[++i];
2078
+ break;
2079
+ case "--context":
2080
+ options.context = argv[++i];
2081
+ break;
2082
+ case "--parse":
2083
+ options.parseOnly = true;
2084
+ break;
2085
+ default:
2086
+ if (arg.startsWith("--load=")) {
2087
+ options.load = arg.slice(7);
2088
+ } else if (arg.startsWith("--context=")) {
2089
+ options.context = arg.slice(10);
2090
+ }
2091
+ break;
2092
+ }
2093
+ }
2094
+ return options;
2095
+ }
2096
+ function printHelp() {
2097
+ process.stdout.write(`
1699
2098
  ISL REPL - Intent Specification Language Interactive Shell
1700
2099
 
1701
2100
  Usage: isl-repl [options]
1702
2101
 
1703
2102
  Options:
1704
- --no-color Disable colored output
1705
- -v, --verbose Enable verbose output
1706
- -h, --help Show this help message
2103
+ --load <file> Load an ISL file on start
2104
+ --context <json> Set initial evaluation context
2105
+ --parse Parse mode (non-interactive, for piped input)
2106
+ --no-color Disable colored output
2107
+ -v, --verbose Enable verbose output
2108
+ -h, --help Show this help message
1707
2109
 
1708
2110
  Inside the REPL:
1709
- .help Show all commands
1710
- .exit Exit the REPL
1711
- :check Type check intents
1712
- :gen Generate code
1713
- :load Load ISL file
1714
- :list List intents
2111
+ .help Show all commands
2112
+ .parse <isl> Parse ISL and show AST
2113
+ .eval <expr> Evaluate expression against context
2114
+ .check [intent] Type check intents
2115
+ .gen [intent] Generate TypeScript from intent
2116
+ .load <file> Load an .isl file
2117
+ .context <json> Set evaluation context (mock data)
2118
+ .clear Reset session state
2119
+ .list List defined intents
2120
+ .inspect [intent] Show full details of an intent
2121
+ .history Show command history
2122
+ .exit Exit the REPL
1715
2123
 
1716
- Example:
1717
- $ isl-repl
1718
- isl> intent Greeting {
1719
- ...> pre: name.length > 0
1720
- ...> post: result.startsWith("Hello")
2124
+ Multi-line Input:
2125
+ Type ISL with braces \u2014 the REPL auto-detects multi-line:
2126
+ isl> domain Example {
2127
+ ...> entity User {
2128
+ ...> id: UUID
2129
+ ...> name: String
2130
+ ...> }
1721
2131
  ...> }
1722
- \u2713 Intent 'Greeting' defined (1 pre, 1 post)
2132
+
2133
+ Examples:
2134
+ $ isl-repl
2135
+ $ isl-repl --load auth.isl
2136
+ $ isl-repl --context '{"user": {"id": 1}}'
2137
+ $ echo 'domain X { version: "1.0" }' | isl-repl --parse
1723
2138
  `);
2139
+ }
2140
+ function main() {
2141
+ const args = process.argv.slice(2);
2142
+ const options = parseArgs(args);
2143
+ if (options.help) {
2144
+ printHelp();
1724
2145
  process.exit(0);
1725
2146
  }
1726
2147
  startREPL(options);
1727
2148
  }
1728
- if (__require.main === module) {
2149
+ var isMainModule = typeof __require !== "undefined" ? __require.main === module : process.argv[1]?.includes("cli");
2150
+ if (isMainModule) {
1729
2151
  main();
1730
2152
  }
1731
2153
  export {