@isl-lang/repl 0.0.1 → 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 ADDED
@@ -0,0 +1,2156 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
9
+
10
+ // src/repl.ts
11
+ import * as readline from "readline";
12
+
13
+ // src/session.ts
14
+ import * as fs from "fs";
15
+ import * as path from "path";
16
+ var Session = class {
17
+ /** Defined intents in this session */
18
+ intents = /* @__PURE__ */ new Map();
19
+ /** Variables set during the session */
20
+ variables = /* @__PURE__ */ new Map();
21
+ /** Command history */
22
+ history = [];
23
+ /** Last evaluation result */
24
+ lastResult = void 0;
25
+ /** Loaded files */
26
+ loadedFiles = /* @__PURE__ */ new Set();
27
+ /** Session configuration */
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;
35
+ constructor(config = {}) {
36
+ this.config = {
37
+ colors: true,
38
+ verbose: false,
39
+ cwd: process.cwd(),
40
+ ...config
41
+ };
42
+ }
43
+ // ─────────────────────────────────────────────────────────────────────────
44
+ // Intent Management
45
+ // ─────────────────────────────────────────────────────────────────────────
46
+ /**
47
+ * Define a new intent
48
+ */
49
+ defineIntent(intent) {
50
+ this.intents.set(intent.name, intent);
51
+ }
52
+ /**
53
+ * Get an intent by name
54
+ */
55
+ getIntent(name) {
56
+ return this.intents.get(name);
57
+ }
58
+ /**
59
+ * Get all defined intents
60
+ */
61
+ getAllIntents() {
62
+ return Array.from(this.intents.values());
63
+ }
64
+ /**
65
+ * Check if an intent exists
66
+ */
67
+ hasIntent(name) {
68
+ return this.intents.has(name);
69
+ }
70
+ /**
71
+ * Remove an intent
72
+ */
73
+ removeIntent(name) {
74
+ return this.intents.delete(name);
75
+ }
76
+ /**
77
+ * Get intent names for completion
78
+ */
79
+ getIntentNames() {
80
+ return Array.from(this.intents.keys());
81
+ }
82
+ /**
83
+ * Parse an intent definition from source code
84
+ */
85
+ parseIntent(source) {
86
+ const trimmed = source.trim();
87
+ const match = trimmed.match(/^intent\s+(\w+)\s*\{([\s\S]*)\}$/);
88
+ if (!match) {
89
+ return null;
90
+ }
91
+ const name = match[1];
92
+ const body = match[2];
93
+ const intent = {
94
+ name,
95
+ preconditions: [],
96
+ postconditions: [],
97
+ invariants: [],
98
+ scenarios: [],
99
+ rawSource: source
100
+ };
101
+ const preMatch = body.match(/pre(?:conditions?)?\s*:\s*([^\n]+)/g);
102
+ if (preMatch) {
103
+ for (const pre of preMatch) {
104
+ const expr = pre.replace(/pre(?:conditions?)?\s*:\s*/, "").trim();
105
+ if (expr) {
106
+ intent.preconditions.push({ expression: expr });
107
+ }
108
+ }
109
+ }
110
+ const postMatch = body.match(/post(?:conditions?)?\s*:\s*([^\n]+)/g);
111
+ if (postMatch) {
112
+ for (const post of postMatch) {
113
+ const expr = post.replace(/post(?:conditions?)?\s*:\s*/, "").trim();
114
+ if (expr) {
115
+ intent.postconditions.push({ expression: expr });
116
+ }
117
+ }
118
+ }
119
+ const invMatch = body.match(/invariants?\s*:\s*([^\n]+)/g);
120
+ if (invMatch) {
121
+ for (const inv of invMatch) {
122
+ const expr = inv.replace(/invariants?\s*:\s*/, "").trim();
123
+ if (expr) {
124
+ intent.invariants.push({ expression: expr });
125
+ }
126
+ }
127
+ }
128
+ return intent;
129
+ }
130
+ // ─────────────────────────────────────────────────────────────────────────
131
+ // Variable Management
132
+ // ─────────────────────────────────────────────────────────────────────────
133
+ /**
134
+ * Set a variable
135
+ */
136
+ setVariable(name, value) {
137
+ this.variables.set(name, value);
138
+ }
139
+ /**
140
+ * Get a variable
141
+ */
142
+ getVariable(name) {
143
+ return this.variables.get(name);
144
+ }
145
+ /**
146
+ * Get all variables
147
+ */
148
+ getAllVariables() {
149
+ return new Map(this.variables);
150
+ }
151
+ /**
152
+ * Check if a variable exists
153
+ */
154
+ hasVariable(name) {
155
+ return this.variables.has(name);
156
+ }
157
+ /**
158
+ * Set the last result (accessible as _)
159
+ */
160
+ setLastResult(value) {
161
+ this.lastResult = value;
162
+ this.variables.set("_", value);
163
+ }
164
+ /**
165
+ * Get the last result
166
+ */
167
+ getLastResult() {
168
+ return this.lastResult;
169
+ }
170
+ // ─────────────────────────────────────────────────────────────────────────
171
+ // History Management
172
+ // ─────────────────────────────────────────────────────────────────────────
173
+ /**
174
+ * Add to history
175
+ */
176
+ addToHistory(entry) {
177
+ const trimmed = entry.trim();
178
+ if (trimmed && (this.history.length === 0 || this.history[this.history.length - 1] !== trimmed)) {
179
+ this.history.push(trimmed);
180
+ }
181
+ }
182
+ /**
183
+ * Get history
184
+ */
185
+ getHistory(count) {
186
+ if (count) {
187
+ return this.history.slice(-count);
188
+ }
189
+ return [...this.history];
190
+ }
191
+ // ─────────────────────────────────────────────────────────────────────────
192
+ // File Loading
193
+ // ─────────────────────────────────────────────────────────────────────────
194
+ /**
195
+ * Load intents from an ISL file
196
+ */
197
+ async loadFile(filePath) {
198
+ const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(this.config.cwd, filePath);
199
+ if (!fs.existsSync(resolvedPath)) {
200
+ return { intents: [], errors: [`File not found: ${resolvedPath}`] };
201
+ }
202
+ try {
203
+ const content = fs.readFileSync(resolvedPath, "utf-8");
204
+ const loadedIntents = [];
205
+ const errors = [];
206
+ const intentRegex = /intent\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
207
+ let match;
208
+ while ((match = intentRegex.exec(content)) !== null) {
209
+ const intent = this.parseIntent(match[0]);
210
+ if (intent) {
211
+ this.defineIntent(intent);
212
+ loadedIntents.push(intent);
213
+ } else {
214
+ errors.push(`Failed to parse intent starting at position ${match.index}`);
215
+ }
216
+ }
217
+ const behaviorRegex = /behavior\s+(\w+)\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/g;
218
+ while ((match = behaviorRegex.exec(content)) !== null) {
219
+ const intent = this.parseBehaviorAsIntent(match[1], match[2]);
220
+ if (intent) {
221
+ this.defineIntent(intent);
222
+ loadedIntents.push(intent);
223
+ }
224
+ }
225
+ this.loadedFiles.add(resolvedPath);
226
+ return { intents: loadedIntents, errors };
227
+ } catch (error) {
228
+ return {
229
+ intents: [],
230
+ errors: [`Failed to load file: ${error instanceof Error ? error.message : String(error)}`]
231
+ };
232
+ }
233
+ }
234
+ /**
235
+ * Parse a behavior block as an intent
236
+ */
237
+ parseBehaviorAsIntent(name, body) {
238
+ const intent = {
239
+ name,
240
+ preconditions: [],
241
+ postconditions: [],
242
+ invariants: [],
243
+ scenarios: [],
244
+ rawSource: `behavior ${name} {${body}}`
245
+ };
246
+ const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
247
+ if (preSection) {
248
+ const conditions = preSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
249
+ for (const cond of conditions) {
250
+ const expr = cond.replace(/^-\s*/, "").trim();
251
+ if (expr) {
252
+ intent.preconditions.push({ expression: expr });
253
+ }
254
+ }
255
+ }
256
+ const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
257
+ if (postSection) {
258
+ const conditions = postSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
259
+ for (const cond of conditions) {
260
+ const expr = cond.replace(/^-\s*/, "").trim();
261
+ if (expr) {
262
+ intent.postconditions.push({ expression: expr });
263
+ }
264
+ }
265
+ }
266
+ const invSection = body.match(/invariants?\s*\{([^}]*)\}/s);
267
+ if (invSection) {
268
+ const conditions = invSection[1].trim().split("\n").map((l) => l.trim()).filter(Boolean);
269
+ for (const cond of conditions) {
270
+ const expr = cond.replace(/^-\s*/, "").trim();
271
+ if (expr) {
272
+ intent.invariants.push({ expression: expr });
273
+ }
274
+ }
275
+ }
276
+ return intent;
277
+ }
278
+ /**
279
+ * Export session intents to a file
280
+ */
281
+ async exportToFile(filePath) {
282
+ const resolvedPath = path.isAbsolute(filePath) ? filePath : path.resolve(this.config.cwd, filePath);
283
+ try {
284
+ const lines = [];
285
+ lines.push("// Exported ISL intents");
286
+ lines.push(`// Generated at ${(/* @__PURE__ */ new Date()).toISOString()}`);
287
+ lines.push("");
288
+ for (const intent of this.intents.values()) {
289
+ lines.push(`intent ${intent.name} {`);
290
+ for (const pre of intent.preconditions) {
291
+ lines.push(` pre: ${pre.expression}`);
292
+ }
293
+ for (const post of intent.postconditions) {
294
+ lines.push(` post: ${post.expression}`);
295
+ }
296
+ for (const inv of intent.invariants) {
297
+ lines.push(` invariant: ${inv.expression}`);
298
+ }
299
+ lines.push("}");
300
+ lines.push("");
301
+ }
302
+ fs.writeFileSync(resolvedPath, lines.join("\n"));
303
+ return { success: true };
304
+ } catch (error) {
305
+ return {
306
+ success: false,
307
+ error: `Failed to export: ${error instanceof Error ? error.message : String(error)}`
308
+ };
309
+ }
310
+ }
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
+ // ─────────────────────────────────────────────────────────────────────────
403
+ // State Management
404
+ // ─────────────────────────────────────────────────────────────────────────
405
+ /**
406
+ * Clear all session state
407
+ */
408
+ clear() {
409
+ this.intents.clear();
410
+ this.variables.clear();
411
+ this.lastResult = void 0;
412
+ this.loadedFiles.clear();
413
+ this.evalContext = {};
414
+ this.preContext = null;
415
+ this.domainAST = null;
416
+ }
417
+ /**
418
+ * Get session summary
419
+ */
420
+ getSummary() {
421
+ return {
422
+ intentCount: this.intents.size,
423
+ variableCount: this.variables.size,
424
+ loadedFileCount: this.loadedFiles.size,
425
+ historyCount: this.history.length
426
+ };
427
+ }
428
+ /**
429
+ * Get loaded files
430
+ */
431
+ getLoadedFiles() {
432
+ return Array.from(this.loadedFiles);
433
+ }
434
+ /**
435
+ * Get config
436
+ */
437
+ getConfig() {
438
+ return { ...this.config };
439
+ }
440
+ /**
441
+ * Update config
442
+ */
443
+ setConfig(config) {
444
+ this.config = { ...this.config, ...config };
445
+ }
446
+ };
447
+
448
+ // src/history.ts
449
+ import * as fs2 from "fs";
450
+ import * as path2 from "path";
451
+ import * as os from "os";
452
+ var History = class {
453
+ entries = [];
454
+ position = -1;
455
+ maxSize;
456
+ historyFile;
457
+ unsavedCount = 0;
458
+ autoSaveThreshold = 10;
459
+ constructor(options = {}) {
460
+ this.maxSize = options.maxSize ?? 1e3;
461
+ this.historyFile = options.historyFile ?? this.getDefaultHistoryFile();
462
+ this.autoSaveThreshold = options.autoSaveThreshold ?? 10;
463
+ }
464
+ /**
465
+ * Get default history file path
466
+ */
467
+ getDefaultHistoryFile() {
468
+ const homeDir = os.homedir();
469
+ return path2.join(homeDir, ".isl_repl_history");
470
+ }
471
+ /**
472
+ * Load history from file
473
+ */
474
+ load() {
475
+ try {
476
+ if (fs2.existsSync(this.historyFile)) {
477
+ const content = fs2.readFileSync(this.historyFile, "utf-8");
478
+ this.entries = content.split("\n").filter((line) => line.trim() !== "").slice(-this.maxSize);
479
+ this.position = this.entries.length;
480
+ }
481
+ } catch {
482
+ }
483
+ }
484
+ /**
485
+ * Save history to file
486
+ */
487
+ save() {
488
+ try {
489
+ const dir = path2.dirname(this.historyFile);
490
+ if (!fs2.existsSync(dir)) {
491
+ fs2.mkdirSync(dir, { recursive: true });
492
+ }
493
+ fs2.writeFileSync(this.historyFile, this.entries.join("\n") + "\n");
494
+ this.unsavedCount = 0;
495
+ } catch {
496
+ }
497
+ }
498
+ /**
499
+ * Add entry to history
500
+ */
501
+ add(entry) {
502
+ const trimmed = entry.trim();
503
+ if (trimmed === "") return;
504
+ if (this.entries.length > 0 && this.entries[this.entries.length - 1] === trimmed) {
505
+ this.position = this.entries.length;
506
+ return;
507
+ }
508
+ this.entries.push(trimmed);
509
+ if (this.entries.length > this.maxSize) {
510
+ this.entries = this.entries.slice(-this.maxSize);
511
+ }
512
+ this.position = this.entries.length;
513
+ this.unsavedCount++;
514
+ if (this.unsavedCount >= this.autoSaveThreshold) {
515
+ this.save();
516
+ }
517
+ }
518
+ /**
519
+ * Get previous entry (for up arrow)
520
+ */
521
+ previous() {
522
+ if (this.entries.length === 0) return null;
523
+ if (this.position > 0) {
524
+ this.position--;
525
+ }
526
+ return this.entries[this.position] ?? null;
527
+ }
528
+ /**
529
+ * Get next entry (for down arrow)
530
+ */
531
+ next() {
532
+ if (this.entries.length === 0) return null;
533
+ if (this.position < this.entries.length - 1) {
534
+ this.position++;
535
+ return this.entries[this.position] ?? null;
536
+ }
537
+ this.position = this.entries.length;
538
+ return "";
539
+ }
540
+ /**
541
+ * Reset position to end
542
+ */
543
+ resetPosition() {
544
+ this.position = this.entries.length;
545
+ }
546
+ /**
547
+ * Search history for entries containing text
548
+ */
549
+ search(text) {
550
+ const lower = text.toLowerCase();
551
+ return this.entries.filter(
552
+ (entry) => entry.toLowerCase().includes(lower)
553
+ );
554
+ }
555
+ /**
556
+ * Search backwards from current position
557
+ */
558
+ searchBackward(text) {
559
+ const lower = text.toLowerCase();
560
+ for (let i = this.position - 1; i >= 0; i--) {
561
+ if (this.entries[i].toLowerCase().includes(lower)) {
562
+ this.position = i;
563
+ return this.entries[i];
564
+ }
565
+ }
566
+ return null;
567
+ }
568
+ /**
569
+ * Search forward from current position
570
+ */
571
+ searchForward(text) {
572
+ const lower = text.toLowerCase();
573
+ for (let i = this.position + 1; i < this.entries.length; i++) {
574
+ if (this.entries[i].toLowerCase().includes(lower)) {
575
+ this.position = i;
576
+ return this.entries[i];
577
+ }
578
+ }
579
+ return null;
580
+ }
581
+ /**
582
+ * Get all entries
583
+ */
584
+ getAll() {
585
+ return [...this.entries];
586
+ }
587
+ /**
588
+ * Get recent entries
589
+ */
590
+ getRecent(count) {
591
+ return this.entries.slice(-count);
592
+ }
593
+ /**
594
+ * Clear history
595
+ */
596
+ clear() {
597
+ this.entries = [];
598
+ this.position = 0;
599
+ this.save();
600
+ }
601
+ /**
602
+ * Get history size
603
+ */
604
+ get size() {
605
+ return this.entries.length;
606
+ }
607
+ /**
608
+ * Get current position
609
+ */
610
+ get currentPosition() {
611
+ return this.position;
612
+ }
613
+ };
614
+
615
+ // src/completions.ts
616
+ import * as fs4 from "fs";
617
+ import * as path4 from "path";
618
+
619
+ // src/commands.ts
620
+ import * as fs3 from "fs";
621
+ import * as path3 from "path";
622
+
623
+ // src/formatter.ts
624
+ var colors = {
625
+ reset: "\x1B[0m",
626
+ bold: "\x1B[1m",
627
+ dim: "\x1B[2m",
628
+ italic: "\x1B[3m",
629
+ underline: "\x1B[4m",
630
+ // Foreground colors
631
+ black: "\x1B[30m",
632
+ red: "\x1B[31m",
633
+ green: "\x1B[32m",
634
+ yellow: "\x1B[33m",
635
+ blue: "\x1B[34m",
636
+ magenta: "\x1B[35m",
637
+ cyan: "\x1B[36m",
638
+ white: "\x1B[37m",
639
+ gray: "\x1B[90m",
640
+ // Bright foreground
641
+ brightRed: "\x1B[91m",
642
+ brightGreen: "\x1B[92m",
643
+ brightYellow: "\x1B[93m",
644
+ brightBlue: "\x1B[94m",
645
+ brightMagenta: "\x1B[95m",
646
+ brightCyan: "\x1B[96m",
647
+ brightWhite: "\x1B[97m",
648
+ // Background colors
649
+ bgBlack: "\x1B[40m",
650
+ bgRed: "\x1B[41m",
651
+ bgGreen: "\x1B[42m",
652
+ bgYellow: "\x1B[43m",
653
+ bgBlue: "\x1B[44m",
654
+ bgMagenta: "\x1B[45m",
655
+ bgCyan: "\x1B[46m",
656
+ bgWhite: "\x1B[47m"
657
+ };
658
+ function formatSuccess(message) {
659
+ return `${colors.green}\u2713${colors.reset} ${message}`;
660
+ }
661
+ function formatError(message) {
662
+ return `${colors.red}\u2717 Error:${colors.reset} ${message}`;
663
+ }
664
+ function formatWarning(message) {
665
+ return `${colors.yellow}\u26A0${colors.reset} ${message}`;
666
+ }
667
+ function formatIntent(intent) {
668
+ const lines = [
669
+ "",
670
+ `${colors.bold}Intent: ${colors.cyan}${intent.name}${colors.reset}`,
671
+ colors.gray + "\u2500".repeat(40) + colors.reset
672
+ ];
673
+ if (intent.preconditions.length > 0) {
674
+ lines.push("");
675
+ lines.push(`${colors.bold}Preconditions:${colors.reset}`);
676
+ for (const pre of intent.preconditions) {
677
+ lines.push(` ${colors.magenta}pre:${colors.reset} ${highlightExpression(pre.expression)}`);
678
+ }
679
+ }
680
+ if (intent.postconditions.length > 0) {
681
+ lines.push("");
682
+ lines.push(`${colors.bold}Postconditions:${colors.reset}`);
683
+ for (const post of intent.postconditions) {
684
+ lines.push(` ${colors.magenta}post:${colors.reset} ${highlightExpression(post.expression)}`);
685
+ }
686
+ }
687
+ if (intent.invariants.length > 0) {
688
+ lines.push("");
689
+ lines.push(`${colors.bold}Invariants:${colors.reset}`);
690
+ for (const inv of intent.invariants) {
691
+ lines.push(` ${colors.magenta}invariant:${colors.reset} ${highlightExpression(inv.expression)}`);
692
+ }
693
+ }
694
+ if (intent.scenarios.length > 0) {
695
+ lines.push("");
696
+ lines.push(`${colors.bold}Scenarios:${colors.reset}`);
697
+ for (const scenario of intent.scenarios) {
698
+ lines.push(` ${colors.yellow}${scenario.name}${colors.reset}`);
699
+ }
700
+ }
701
+ lines.push("");
702
+ return lines.join("\n");
703
+ }
704
+ function highlightExpression(expr) {
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}`);
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
+ }
752
+
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
+ }
887
+ var metaCommands = [
888
+ // ─── .help ──────────────────────────────────────────────────────────────
889
+ {
890
+ name: "help",
891
+ aliases: ["h", "?"],
892
+ description: "Show commands",
893
+ usage: ".help [command]",
894
+ handler: (args) => {
895
+ if (args.length > 0) {
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) {
901
+ return {
902
+ output: [
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(", ")}` : ""
906
+ ].filter(Boolean).join("\n")
907
+ };
908
+ }
909
+ return { output: formatError(`Unknown command: ${cmdName}`) };
910
+ }
911
+ const lines = [
912
+ "",
913
+ `${colors.bold}REPL Commands${colors.reset}`,
914
+ "",
915
+ ...metaCommands.map(
916
+ (c) => ` ${colors.cyan}.${c.name.padEnd(12)}${colors.reset} ${c.description}`
917
+ ),
918
+ "",
919
+ `${colors.bold}ISL Input${colors.reset}`,
920
+ "",
921
+ ` Type ISL directly \u2014 multi-line supported (braces auto-detect):`,
922
+ "",
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
+ ` }`,
928
+ ` }`,
929
+ "",
930
+ `Type ${colors.cyan}.help <command>${colors.reset} for details.`,
931
+ ""
932
+ ];
933
+ return { output: lines.join("\n") };
934
+ }
935
+ },
936
+ // ─── .parse ─────────────────────────────────────────────────────────────
937
+ {
938
+ name: "parse",
939
+ aliases: ["p", "ast"],
940
+ description: "Parse ISL and show AST",
941
+ usage: ".parse <isl>",
942
+ handler: (args, session) => {
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
+ }
974
+ }
975
+ },
976
+ // ─── .eval ──────────────────────────────────────────────────────────────
977
+ {
978
+ name: "eval",
979
+ aliases: ["e"],
980
+ description: "Evaluate expression against context",
981
+ usage: ".eval <expression>",
982
+ handler: (args, session) => {
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
+ };
997
+ }
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
+ };
1006
+ }
1007
+ },
1008
+ // ─── .check ─────────────────────────────────────────────────────────────
1009
+ {
1010
+ name: "check",
1011
+ aliases: ["c"],
1012
+ description: "Type check the current session",
1013
+ usage: ".check [intent]",
1014
+ handler: (args, session) => {
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
+ };
1024
+ }
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)}`);
1031
+ }
1032
+ return { output: lines2.join("\n") };
1033
+ }
1034
+ const intents = session.getAllIntents();
1035
+ if (intents.length === 0) {
1036
+ return {
1037
+ output: formatWarning("No intents defined. Write ISL or use .load <file>")
1038
+ };
1039
+ }
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("");
1050
+ }
1051
+ return { output: lines.join("\n") };
1052
+ }
1053
+ },
1054
+ // ─── .gen ───────────────────────────────────────────────────────────────
1055
+ {
1056
+ name: "gen",
1057
+ aliases: ["generate", "g"],
1058
+ description: "Generate TypeScript from intent",
1059
+ usage: ".gen [intent]",
1060
+ handler: (args, session) => {
1061
+ const intents = args.length > 0 ? [session.getIntent(args[0])].filter(Boolean) : session.getAllIntents();
1062
+ if (intents.length === 0) {
1063
+ return {
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>")
1066
+ };
1067
+ }
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("");
1091
+ }
1092
+ return { output: lines.join("\n") };
1093
+ }
1094
+ },
1095
+ // ─── .load ──────────────────────────────────────────────────────────────
1096
+ {
1097
+ name: "load",
1098
+ aliases: ["l"],
1099
+ description: "Load an .isl file",
1100
+ usage: ".load <file.isl>",
1101
+ handler: (args, session) => {
1102
+ if (args.length === 0) {
1103
+ return { output: "Usage: .load <file.isl>" };
1104
+ }
1105
+ const filePath = args[0];
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
+ }
1110
+ try {
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 {
1138
+ }
1139
+ const intentRegex = /(?:intent|behavior)\s+(\w+)\s*\{[^}]*(?:\{[^}]*\}[^}]*)*\}/g;
1140
+ let match;
1141
+ let count = 0;
1142
+ while ((match = intentRegex.exec(content)) !== null) {
1143
+ const intent = session.parseIntent(match[0]);
1144
+ if (intent) {
1145
+ session.defineIntent(intent);
1146
+ count++;
1147
+ }
1148
+ }
1149
+ if (count === 0) {
1150
+ return { output: formatWarning("No intents/behaviors found in file") };
1151
+ }
1152
+ return {
1153
+ output: formatSuccess(`Loaded ${count} intent(s) from ${path3.basename(filePath)}`)
1154
+ };
1155
+ } catch (error) {
1156
+ return {
1157
+ output: formatError(
1158
+ `Failed to load: ${error instanceof Error ? error.message : String(error)}`
1159
+ )
1160
+ };
1161
+ }
1162
+ }
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 ──────────────────────────────────────────────────────────────
1253
+ {
1254
+ name: "list",
1255
+ aliases: ["ls"],
1256
+ description: "List defined intents",
1257
+ usage: ".list",
1258
+ handler: (_args, session) => {
1259
+ const intents = session.getAllIntents();
1260
+ if (intents.length === 0) {
1261
+ return { output: "No intents defined." };
1262
+ }
1263
+ const lines = [""];
1264
+ for (const intent of intents) {
1265
+ const parts = [];
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`);
1269
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1270
+ lines.push(` ${colors.cyan}${intent.name}${colors.reset}${summary}`);
1271
+ }
1272
+ lines.push("");
1273
+ return { output: lines.join("\n") };
1274
+ }
1275
+ },
1276
+ // ─── .inspect ───────────────────────────────────────────────────────────
1277
+ {
1278
+ name: "inspect",
1279
+ aliases: ["i", "show"],
1280
+ description: "Show full details of an intent",
1281
+ usage: ".inspect [intent]",
1282
+ handler: (args, session) => {
1283
+ if (args.length === 0) {
1284
+ const summary = session.getSummary();
1285
+ const ctx = session.getEvalContext();
1286
+ const lines = [
1287
+ "",
1288
+ `${colors.bold}Session Summary${colors.reset}`,
1289
+ "",
1290
+ ` Intents: ${summary.intentCount}`,
1291
+ ` Variables: ${summary.variableCount}`,
1292
+ ` Context: ${Object.keys(ctx).length} keys`,
1293
+ ` History: ${summary.historyCount} entries`,
1294
+ ""
1295
+ ];
1296
+ return { output: lines.join("\n") };
1297
+ }
1298
+ const intentName = args[0];
1299
+ const intent = session.getIntent(intentName);
1300
+ if (!intent) {
1301
+ const available = session.getIntentNames().join(", ") || "(none)";
1302
+ return {
1303
+ output: formatError(`Unknown intent: ${intentName}
1304
+ Available: ${available}`)
1305
+ };
1306
+ }
1307
+ return { output: formatIntent(intent) };
1308
+ }
1309
+ },
1310
+ // ─── .exit ──────────────────────────────────────────────────────────────
1311
+ {
1312
+ name: "exit",
1313
+ aliases: ["quit", "q"],
1314
+ description: "Exit the REPL",
1315
+ usage: ".exit",
1316
+ handler: () => {
1317
+ return { exit: true };
1318
+ }
1319
+ }
1320
+ ];
1321
+ function levenshteinDistance(a, b) {
1322
+ const matrix = [];
1323
+ for (let i = 0; i <= b.length; i++) {
1324
+ matrix[i] = [i];
1325
+ }
1326
+ for (let j = 0; j <= a.length; j++) {
1327
+ matrix[0][j] = j;
1328
+ }
1329
+ for (let i = 1; i <= b.length; i++) {
1330
+ for (let j = 1; j <= a.length; j++) {
1331
+ const cost = a[j - 1] === b[i - 1] ? 0 : 1;
1332
+ matrix[i][j] = Math.min(
1333
+ matrix[i - 1][j] + 1,
1334
+ matrix[i][j - 1] + 1,
1335
+ matrix[i - 1][j - 1] + cost
1336
+ );
1337
+ }
1338
+ }
1339
+ return matrix[b.length][a.length];
1340
+ }
1341
+ function findSimilarCommand(input, _type) {
1342
+ const names = metaCommands.flatMap((c) => [c.name, ...c.aliases]);
1343
+ let bestMatch = null;
1344
+ let bestDistance = Infinity;
1345
+ for (const name of names) {
1346
+ const distance = levenshteinDistance(input.toLowerCase(), name.toLowerCase());
1347
+ if (distance < bestDistance && distance <= 2) {
1348
+ bestDistance = distance;
1349
+ bestMatch = name;
1350
+ }
1351
+ }
1352
+ return bestMatch;
1353
+ }
1354
+
1355
+ // src/completions.ts
1356
+ var KEYWORDS = [
1357
+ // Structure keywords
1358
+ { text: "domain", type: "keyword", description: "Define a domain" },
1359
+ { text: "entity", type: "keyword", description: "Define an entity" },
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" },
1364
+ { text: "pre", type: "keyword", description: "Precondition" },
1365
+ { text: "post", type: "keyword", description: "Postcondition" },
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
1382
+ { text: "true", type: "keyword", description: "Boolean true" },
1383
+ { text: "false", type: "keyword", description: "Boolean false" },
1384
+ { text: "null", type: "keyword", description: "Null value" },
1385
+ { text: "and", type: "keyword", description: "Logical AND" },
1386
+ { text: "or", type: "keyword", description: "Logical OR" },
1387
+ { text: "not", type: "keyword", description: "Logical NOT" },
1388
+ { text: "implies", type: "keyword", description: "Logical implication" },
1389
+ { text: "forall", type: "keyword", description: "Universal quantifier" },
1390
+ { text: "exists", type: "keyword", description: "Existential quantifier" },
1391
+ { text: "in", type: "keyword", description: "Membership test" },
1392
+ { text: "old", type: "keyword", description: "Pre-state value (old(x))" }
1393
+ ];
1394
+ var META_COMMANDS = metaCommands.map((cmd) => ({
1395
+ text: `.${cmd.name}`,
1396
+ type: "command",
1397
+ description: cmd.description
1398
+ }));
1399
+ var ISL_COMMANDS = [];
1400
+ var COMMANDS = [...META_COMMANDS];
1401
+ var GEN_TARGETS = [
1402
+ { text: "typescript", type: "keyword", description: "Generate TypeScript contract" },
1403
+ { text: "rust", type: "keyword", description: "Generate Rust contract" },
1404
+ { text: "go", type: "keyword", description: "Generate Go contract" },
1405
+ { text: "openapi", type: "keyword", description: "Generate OpenAPI schema" }
1406
+ ];
1407
+ var CompletionProvider = class {
1408
+ constructor(session) {
1409
+ this.session = session;
1410
+ }
1411
+ /**
1412
+ * Update the session reference
1413
+ */
1414
+ setSession(session) {
1415
+ this.session = session;
1416
+ }
1417
+ /**
1418
+ * Get completions for a line
1419
+ */
1420
+ complete(line) {
1421
+ const trimmed = line.trimStart();
1422
+ if (trimmed.startsWith(".")) {
1423
+ return this.completeMetaCommand(trimmed);
1424
+ }
1425
+ if (trimmed.startsWith(":")) {
1426
+ return this.completeMetaCommand("." + trimmed.slice(1));
1427
+ }
1428
+ return this.completeExpression(trimmed);
1429
+ }
1430
+ /**
1431
+ * Complete meta commands
1432
+ */
1433
+ completeMetaCommand(line) {
1434
+ const parts = line.slice(1).split(/\s+/);
1435
+ const cmdPart = parts[0] || "";
1436
+ if (parts.length === 1) {
1437
+ const matches = META_COMMANDS.filter(
1438
+ (c) => c.text.toLowerCase().startsWith(`.${cmdPart.toLowerCase()}`)
1439
+ );
1440
+ return [matches.length > 0 ? matches : META_COMMANDS, "." + cmdPart];
1441
+ }
1442
+ return [[], line];
1443
+ }
1444
+ /**
1445
+ * Complete ISL commands
1446
+ */
1447
+ completeISLCommand(line) {
1448
+ const parts = line.slice(1).split(/\s+/);
1449
+ const cmdPart = parts[0] || "";
1450
+ const args = parts.slice(1);
1451
+ if (parts.length === 1) {
1452
+ const matches = ISL_COMMANDS.filter(
1453
+ (c) => c.text.toLowerCase().startsWith(`:${cmdPart.toLowerCase()}`)
1454
+ );
1455
+ return [matches.length > 0 ? matches : ISL_COMMANDS, ":" + cmdPart];
1456
+ }
1457
+ const cmd = cmdPart.toLowerCase();
1458
+ switch (cmd) {
1459
+ case "gen":
1460
+ case "generate":
1461
+ case "g":
1462
+ return this.completeGenCommand(args);
1463
+ case "check":
1464
+ case "c":
1465
+ case "inspect":
1466
+ case "i":
1467
+ case "show":
1468
+ return this.completeIntentName(args[0] || "");
1469
+ case "load":
1470
+ case "l":
1471
+ case "export":
1472
+ case "save":
1473
+ return this.completeFilePath(args[0] || "");
1474
+ default:
1475
+ return [[], line];
1476
+ }
1477
+ }
1478
+ /**
1479
+ * Complete :gen command arguments
1480
+ */
1481
+ completeGenCommand(args) {
1482
+ if (args.length <= 1) {
1483
+ const partial = args[0] || "";
1484
+ const matches = GEN_TARGETS.filter(
1485
+ (t) => t.text.toLowerCase().startsWith(partial.toLowerCase())
1486
+ );
1487
+ return [matches.length > 0 ? matches : GEN_TARGETS, partial];
1488
+ }
1489
+ return this.completeIntentName(args[1] || "");
1490
+ }
1491
+ /**
1492
+ * Complete intent names
1493
+ */
1494
+ completeIntentName(partial) {
1495
+ const intents = this.session.getAllIntents();
1496
+ const items = intents.map((intent) => ({
1497
+ text: intent.name,
1498
+ type: "intent",
1499
+ description: `${intent.preconditions.length} pre, ${intent.postconditions.length} post`
1500
+ }));
1501
+ const matches = items.filter(
1502
+ (i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
1503
+ );
1504
+ return [matches.length > 0 ? matches : items, partial];
1505
+ }
1506
+ /**
1507
+ * Complete file paths
1508
+ */
1509
+ completeFilePath(partial) {
1510
+ try {
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)) {
1515
+ return [[], partial];
1516
+ }
1517
+ const entries = fs4.readdirSync(resolvedDir, { withFileTypes: true });
1518
+ const items = entries.filter((e) => {
1519
+ const name = e.name.toLowerCase();
1520
+ return name.startsWith(base.toLowerCase()) && (e.isDirectory() || name.endsWith(".isl"));
1521
+ }).map((e) => ({
1522
+ text: path4.join(dir, e.name + (e.isDirectory() ? "/" : "")),
1523
+ type: "file",
1524
+ description: e.isDirectory() ? "Directory" : "ISL file"
1525
+ }));
1526
+ return [items, partial];
1527
+ } catch {
1528
+ return [[], partial];
1529
+ }
1530
+ }
1531
+ /**
1532
+ * Complete expressions
1533
+ */
1534
+ completeExpression(line) {
1535
+ const items = [...KEYWORDS];
1536
+ for (const intent of this.session.getAllIntents()) {
1537
+ items.push({
1538
+ text: intent.name,
1539
+ type: "intent",
1540
+ description: "Defined intent"
1541
+ });
1542
+ }
1543
+ for (const [name] of this.session.getAllVariables()) {
1544
+ items.push({
1545
+ text: name,
1546
+ type: "variable",
1547
+ description: "Variable"
1548
+ });
1549
+ }
1550
+ items.push({
1551
+ text: "_",
1552
+ type: "variable",
1553
+ description: "Last result"
1554
+ });
1555
+ const match = line.match(/[\w.]+$/);
1556
+ const partial = match ? match[0] : "";
1557
+ const matches = items.filter(
1558
+ (i) => i.text.toLowerCase().startsWith(partial.toLowerCase())
1559
+ );
1560
+ return [matches.length > 0 ? matches : items, partial];
1561
+ }
1562
+ /**
1563
+ * Get all available completions (for help)
1564
+ */
1565
+ getAllCompletions() {
1566
+ return {
1567
+ metaCommands: META_COMMANDS,
1568
+ islCommands: ISL_COMMANDS,
1569
+ keywords: KEYWORDS,
1570
+ intents: this.session.getAllIntents().map((i) => ({
1571
+ text: i.name,
1572
+ type: "intent",
1573
+ description: `${i.preconditions.length} pre, ${i.postconditions.length} post`
1574
+ }))
1575
+ };
1576
+ }
1577
+ };
1578
+ function createCompleter(provider) {
1579
+ return (line) => {
1580
+ const [items, partial] = provider.complete(line);
1581
+ const completions = items.map((i) => i.text);
1582
+ return [completions, partial];
1583
+ };
1584
+ }
1585
+
1586
+ // src/repl.ts
1587
+ var VERSION = "0.1.0";
1588
+ var PROMPT = `${colors.cyan}isl>${colors.reset} `;
1589
+ var CONTINUATION_PROMPT = `${colors.cyan}...>${colors.reset} `;
1590
+ var ISLREPL = class {
1591
+ session;
1592
+ history;
1593
+ completionProvider;
1594
+ rl = null;
1595
+ buffer = [];
1596
+ braceCount = 0;
1597
+ options;
1598
+ running = false;
1599
+ constructor(options = {}) {
1600
+ this.options = {
1601
+ colors: options.colors !== false,
1602
+ verbose: options.verbose ?? false,
1603
+ historyFile: options.historyFile,
1604
+ load: options.load,
1605
+ context: options.context,
1606
+ parseOnly: options.parseOnly ?? false
1607
+ };
1608
+ this.session = new Session({ colors: this.options.colors });
1609
+ this.history = new History({
1610
+ historyFile: this.options.historyFile
1611
+ });
1612
+ this.completionProvider = new CompletionProvider(this.session);
1613
+ }
1614
+ /**
1615
+ * Start the REPL
1616
+ */
1617
+ start() {
1618
+ if (this.running) return;
1619
+ this.running = true;
1620
+ this.applyStartupOptions();
1621
+ if (this.options.parseOnly || !process.stdin.isTTY) {
1622
+ this.runPipeMode();
1623
+ return;
1624
+ }
1625
+ this.history.load();
1626
+ this.rl = readline.createInterface({
1627
+ input: process.stdin,
1628
+ output: process.stdout,
1629
+ prompt: PROMPT,
1630
+ completer: createCompleter(this.completionProvider),
1631
+ terminal: true
1632
+ });
1633
+ this.printBanner();
1634
+ this.rl.prompt();
1635
+ this.rl.on("line", (line) => {
1636
+ this.handleLine(line);
1637
+ if (this.rl && this.running) {
1638
+ this.rl.setPrompt(this.braceCount > 0 ? CONTINUATION_PROMPT : PROMPT);
1639
+ this.rl.prompt();
1640
+ }
1641
+ });
1642
+ this.rl.on("close", () => {
1643
+ this.exit();
1644
+ });
1645
+ this.rl.on("SIGINT", () => {
1646
+ if (this.buffer.length > 0) {
1647
+ this.buffer = [];
1648
+ this.braceCount = 0;
1649
+ process.stdout.write("\n" + formatWarning("Input cancelled") + "\n");
1650
+ this.rl.setPrompt(PROMPT);
1651
+ this.rl.prompt();
1652
+ } else {
1653
+ process.stdout.write("\n" + formatWarning("Use .exit to quit") + "\n");
1654
+ this.rl.prompt();
1655
+ }
1656
+ });
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
+ }
1714
+ /**
1715
+ * Print the welcome banner
1716
+ */
1717
+ printBanner() {
1718
+ const banner = `
1719
+ ${colors.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1720
+ \u2551 \u2551
1721
+ \u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2551
1722
+ \u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2551
1723
+ \u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2551
1724
+ \u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2551 \u2551
1725
+ \u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2551
1726
+ \u2551 \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u2551
1727
+ \u2551 \u2551
1728
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${colors.reset}
1729
+
1730
+ ${colors.bold}ISL v${VERSION}${colors.reset} \u2014 Intent Specification Language
1731
+ Type ${colors.cyan}.help${colors.reset} for commands, ${colors.cyan}.exit${colors.reset} to quit
1732
+ `;
1733
+ process.stdout.write(banner);
1734
+ }
1735
+ /**
1736
+ * Handle a line of input
1737
+ */
1738
+ handleLine(line) {
1739
+ const trimmed = line.trim();
1740
+ if (!trimmed && this.buffer.length === 0) {
1741
+ return;
1742
+ }
1743
+ if (trimmed.startsWith(".") && this.buffer.length === 0) {
1744
+ this.handleDotCommand(trimmed);
1745
+ return;
1746
+ }
1747
+ if (trimmed.startsWith(":") && this.buffer.length === 0) {
1748
+ this.handleDotCommand("." + trimmed.slice(1));
1749
+ return;
1750
+ }
1751
+ this.braceCount += (line.match(/\{/g) || []).length;
1752
+ this.braceCount -= (line.match(/\}/g) || []).length;
1753
+ this.buffer.push(line);
1754
+ if (this.braceCount <= 0) {
1755
+ const code = this.buffer.join("\n");
1756
+ this.buffer = [];
1757
+ this.braceCount = 0;
1758
+ this.history.add(code);
1759
+ this.session.addToHistory(code);
1760
+ this.evaluate(code);
1761
+ }
1762
+ }
1763
+ /**
1764
+ * Handle a dot command (. prefix)
1765
+ */
1766
+ handleDotCommand(input) {
1767
+ const parts = input.slice(1).split(/\s+/);
1768
+ const cmdName = parts[0]?.toLowerCase() || "";
1769
+ const args = parts.slice(1);
1770
+ const rawArgs = input.slice(1 + (cmdName.length || 0)).trim();
1771
+ const command = metaCommands.find(
1772
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
1773
+ );
1774
+ if (command) {
1775
+ this.history.add(input);
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);
1779
+ if (result.output) {
1780
+ process.stdout.write(result.output + "\n");
1781
+ }
1782
+ if (result.exit) {
1783
+ this.exit();
1784
+ }
1785
+ } else {
1786
+ const suggestion = findSimilarCommand(cmdName);
1787
+ if (suggestion) {
1788
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1789
+ process.stdout.write(formatWarning(`Did you mean: .${suggestion}?`) + "\n");
1790
+ } else {
1791
+ process.stdout.write(formatError(`Unknown command: .${cmdName}`) + "\n");
1792
+ process.stdout.write(`Type ${colors.cyan}.help${colors.reset} for available commands
1793
+ `);
1794
+ }
1795
+ }
1796
+ }
1797
+ /**
1798
+ * Evaluate ISL code (multi-line input or bare expressions)
1799
+ */
1800
+ evaluate(code) {
1801
+ try {
1802
+ const trimmed = code.trim();
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 ")) {
1878
+ this.evaluateIntent(trimmed);
1879
+ return;
1880
+ }
1881
+ if (trimmed.startsWith("domain ")) {
1882
+ process.stdout.write(formatSuccess("Parsed domain block") + "\n");
1883
+ return;
1884
+ }
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
+ );
1892
+ } catch (error) {
1893
+ this.printError(error);
1894
+ }
1895
+ }
1896
+ /**
1897
+ * Evaluate an intent definition
1898
+ */
1899
+ evaluateIntent(code) {
1900
+ const intent = this.session.parseIntent(code);
1901
+ if (intent) {
1902
+ this.session.defineIntent(intent);
1903
+ const preCount = intent.preconditions.length;
1904
+ const postCount = intent.postconditions.length;
1905
+ const invCount = intent.invariants.length;
1906
+ const parts = [];
1907
+ if (preCount > 0) parts.push(`${preCount} pre`);
1908
+ if (postCount > 0) parts.push(`${postCount} post`);
1909
+ if (invCount > 0) parts.push(`${invCount} invariant`);
1910
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1911
+ process.stdout.write(
1912
+ formatSuccess(`Intent '${intent.name}' defined${summary}`) + "\n"
1913
+ );
1914
+ } else {
1915
+ const behaviorMatch = code.match(/^behavior\s+(\w+)\s*\{([\s\S]*)\}$/);
1916
+ if (behaviorMatch) {
1917
+ const name = behaviorMatch[1];
1918
+ const body = behaviorMatch[2];
1919
+ const newIntent = {
1920
+ name,
1921
+ preconditions: [],
1922
+ postconditions: [],
1923
+ invariants: [],
1924
+ scenarios: [],
1925
+ rawSource: code
1926
+ };
1927
+ const preSection = body.match(/pre(?:conditions)?\s*\{([^}]*)\}/s);
1928
+ if (preSection) {
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 });
1932
+ }
1933
+ }
1934
+ const postSection = body.match(/post(?:conditions)?\s*\{([^}]*)\}/s);
1935
+ if (postSection) {
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 });
1939
+ }
1940
+ }
1941
+ this.session.defineIntent(newIntent);
1942
+ const parts = [];
1943
+ if (newIntent.preconditions.length > 0) parts.push(`${newIntent.preconditions.length} pre`);
1944
+ if (newIntent.postconditions.length > 0) parts.push(`${newIntent.postconditions.length} post`);
1945
+ const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
1946
+ process.stdout.write(
1947
+ formatSuccess(`Intent '${name}' defined${summary}`) + "\n"
1948
+ );
1949
+ } else {
1950
+ process.stdout.write(formatError("Failed to parse intent definition") + "\n");
1951
+ }
1952
+ }
1953
+ }
1954
+ /**
1955
+ * Print an error
1956
+ */
1957
+ printError(error) {
1958
+ if (error instanceof Error) {
1959
+ process.stdout.write(formatError(error.message) + "\n");
1960
+ if (this.options.verbose && error.stack) {
1961
+ process.stdout.write(colors.gray + error.stack + colors.reset + "\n");
1962
+ }
1963
+ } else {
1964
+ process.stdout.write(formatError(String(error)) + "\n");
1965
+ }
1966
+ }
1967
+ /**
1968
+ * Exit the REPL
1969
+ */
1970
+ exit() {
1971
+ this.running = false;
1972
+ this.history.save();
1973
+ process.stdout.write(`
1974
+ ${colors.yellow}Goodbye!${colors.reset}
1975
+ `);
1976
+ if (this.rl) {
1977
+ this.rl.close();
1978
+ }
1979
+ process.exit(0);
1980
+ }
1981
+ /**
1982
+ * Get the session
1983
+ */
1984
+ getSession() {
1985
+ return this.session;
1986
+ }
1987
+ /**
1988
+ * Get history
1989
+ */
1990
+ getHistory() {
1991
+ return this.history;
1992
+ }
1993
+ /**
1994
+ * Execute a single command and return result (for testing)
1995
+ */
1996
+ async executeOnce(input) {
1997
+ const trimmed = input.trim();
1998
+ if (trimmed.startsWith(".") || trimmed.startsWith(":")) {
1999
+ const normalized = trimmed.startsWith(":") ? "." + trimmed.slice(1) : trimmed;
2000
+ const parts = normalized.slice(1).split(/\s+/);
2001
+ const cmdName = parts[0]?.toLowerCase() || "";
2002
+ const rawArgs = normalized.slice(1 + (cmdName.length || 0)).trim();
2003
+ const command = metaCommands.find(
2004
+ (c) => c.name === cmdName || c.aliases.includes(cmdName)
2005
+ );
2006
+ if (command) {
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);
2010
+ return { success: true, output: result.output };
2011
+ }
2012
+ return { success: false, error: `Unknown command: ${cmdName}` };
2013
+ }
2014
+ if (trimmed.startsWith("intent ") || trimmed.startsWith("behavior ")) {
2015
+ const intent = this.session.parseIntent(trimmed);
2016
+ if (intent) {
2017
+ this.session.defineIntent(intent);
2018
+ return { success: true, output: `Intent '${intent.name}' defined` };
2019
+ }
2020
+ return { success: false, error: "Failed to parse intent" };
2021
+ }
2022
+ return { success: false, error: "Unknown input" };
2023
+ }
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
+ }
2048
+ function startREPL(options) {
2049
+ const repl = new ISLREPL(options);
2050
+ repl.start();
2051
+ return repl;
2052
+ }
2053
+
2054
+ // src/cli.ts
2055
+ function parseArgs(argv) {
2056
+ const options = {
2057
+ help: false,
2058
+ colors: true,
2059
+ verbose: false,
2060
+ parseOnly: false
2061
+ };
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(`
2098
+ ISL REPL - Intent Specification Language Interactive Shell
2099
+
2100
+ Usage: isl-repl [options]
2101
+
2102
+ Options:
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
2109
+
2110
+ Inside the REPL:
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
2123
+
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
+ ...> }
2131
+ ...> }
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
2138
+ `);
2139
+ }
2140
+ function main() {
2141
+ const args = process.argv.slice(2);
2142
+ const options = parseArgs(args);
2143
+ if (options.help) {
2144
+ printHelp();
2145
+ process.exit(0);
2146
+ }
2147
+ startREPL(options);
2148
+ }
2149
+ var isMainModule = typeof __require !== "undefined" ? __require.main === module : process.argv[1]?.includes("cli");
2150
+ if (isMainModule) {
2151
+ main();
2152
+ }
2153
+ export {
2154
+ main
2155
+ };
2156
+ //# sourceMappingURL=cli.js.map