agency-lang 0.0.56 → 0.0.58

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/README.md CHANGED
@@ -25,7 +25,7 @@ agency infile.agency
25
25
  Note that the generated files use several other libraries that you will need to install as well:
26
26
 
27
27
  ```bash
28
- pnpm i nanoid openai piemachine statelog-client zod
28
+ pnpm i nanoid openai piemachine statelog-client zod smoltalk
29
29
  ```
30
30
 
31
31
  ## troubleshooting
@@ -186,14 +186,12 @@ export class AgencyGenerator extends BaseGenerator {
186
186
  .join("\n");
187
187
  result += `${docLines}\n`;
188
188
  }
189
- this.functionScopedVariables = [...parameters.map((p) => p.name)];
190
189
  const lines = [];
191
190
  for (const stmt of body) {
192
191
  lines.push(this.processNode(stmt));
193
192
  }
194
193
  const bodyCode = lines.join("").trimEnd() + "\n";
195
194
  result += bodyCode;
196
- this.functionScopedVariables = [];
197
195
  this.decreaseIndent();
198
196
  // Close function
199
197
  result += this.indentStr(`}`);
@@ -365,14 +363,12 @@ export class AgencyGenerator extends BaseGenerator {
365
363
  : "";
366
364
  let result = this.indentStr(`node ${nodeName}(${params})${returnTypeStr} {\n`);
367
365
  this.increaseIndent();
368
- this.functionScopedVariables = parameters.map((p) => p.name);
369
366
  const lines = [];
370
367
  for (const stmt of body) {
371
368
  lines.push(this.processNode(stmt));
372
369
  }
373
370
  const bodyCode = lines.join("").trimEnd() + "\n";
374
371
  result += bodyCode;
375
- this.functionScopedVariables = [];
376
372
  this.decreaseIndent();
377
373
  result += this.indentStr(`}`);
378
374
  return result;
@@ -1,5 +1,5 @@
1
1
  import { SpecialVar } from "../types/specialVar.js";
2
- import { AgencyComment, AgencyNode, AgencyProgram, Assignment, Literal, NewLine, PromptLiteral, TypeAlias, TypeHint, TypeHintMap, VariableType } from "../types.js";
2
+ import { AgencyComment, AgencyNode, AgencyProgram, Assignment, Literal, NewLine, PromptLiteral, Scope, ScopeType, TypeAlias, TypeHint, TypeHintMap, VariableType } from "../types.js";
3
3
  import { AwaitStatement } from "../types/await.js";
4
4
  import { TimeBlock } from "../types/timeBlock.js";
5
5
  import { AccessExpression, DotFunctionCall, DotProperty, IndexAccess } from "../types/access.js";
@@ -15,26 +15,11 @@ import { WhileLoop } from "../types/whileLoop.js";
15
15
  import { AgencyConfig } from "../config.js";
16
16
  import { MessageThread } from "../types/messageThread.js";
17
17
  import { Skill } from "../types/skill.js";
18
- type Scope = GlobalScope | FunctionScope | NodeScope;
19
- type GlobalScope = {
20
- type: "global";
21
- };
22
- type FunctionScope = {
23
- type: "function";
24
- functionName: string;
25
- };
26
- type NodeScope = {
27
- type: "node";
28
- nodeName: string;
29
- };
30
18
  export declare class BaseGenerator {
31
19
  protected typeHints: TypeHintMap;
32
20
  protected graphNodes: GraphNodeDefinition[];
33
21
  protected generatedStatements: string[];
34
22
  protected generatedTypeAliases: string[];
35
- protected functionScopedVariables: string[];
36
- protected globalScopedVariables: string[];
37
- protected functionParameters: string[];
38
23
  protected typeAliases: Record<string, VariableType>;
39
24
  protected functionsUsed: Set<string>;
40
25
  protected importStatements: string[];
@@ -94,9 +79,7 @@ export declare class BaseGenerator {
94
79
  protected startScope(scope: Scope): void;
95
80
  protected endScope(): void;
96
81
  protected getCurrentScope(): Scope;
97
- protected getScopeVar(): string;
98
- protected generateScopedVariableName(variableName: string): string;
82
+ protected scopetoString(scope: ScopeType): string;
99
83
  protected isImportedTool(functionName: string): boolean;
100
84
  protected isAgencyFunction(functionName: string): boolean;
101
85
  }
102
- export {};
@@ -4,9 +4,6 @@ export class BaseGenerator {
4
4
  graphNodes = [];
5
5
  generatedStatements = [];
6
6
  generatedTypeAliases = [];
7
- functionScopedVariables = [];
8
- globalScopedVariables = [];
9
- functionParameters = [];
10
7
  typeAliases = {};
11
8
  // collect functions used to see what builtin helpers to include
12
9
  functionsUsed = new Set();
@@ -64,12 +61,6 @@ export class BaseGenerator {
64
61
  this.collectFunctionSignature(node);
65
62
  }
66
63
  }
67
- // Pass 6: Collect global scoped variables
68
- for (const node of program.nodes) {
69
- if (node.type === "assignment") {
70
- this.globalScopedVariables.push(node.variableName);
71
- }
72
- }
73
64
  /* For each function, mark whether it is async or not.
74
65
  A function has to be run synchronously if
75
66
  - it or any of its child functions could throw an interrupt
@@ -294,31 +285,19 @@ export class BaseGenerator {
294
285
  getCurrentScope() {
295
286
  return this.currentScope[this.currentScope.length - 1];
296
287
  }
297
- /* This function is used during assignment, so we can figure out whether this variable
298
- should be assigned on local or global scope. There's a related function `generateScopedVariableName`,
299
- which is used when retrieving the value of a variable. */
300
- getScopeVar() {
301
- const currentScope = this.getCurrentScope();
302
- switch (currentScope.type) {
288
+ scopetoString(scope) {
289
+ switch (scope) {
303
290
  case "global":
304
291
  return "__stateStack.globals";
305
292
  case "function":
306
293
  case "node":
307
294
  return "__stack.locals";
295
+ case "args":
296
+ return "__stack.args";
297
+ default:
298
+ throw new Error(`Unknown scope type: ${scope}`);
308
299
  }
309
300
  }
310
- generateScopedVariableName(variableName) {
311
- if (this.functionParameters.includes(variableName)) {
312
- return `__stack.args.${variableName}`;
313
- }
314
- if (this.functionScopedVariables.includes(variableName)) {
315
- return `__stack.locals.${variableName}`;
316
- }
317
- else if (this.globalScopedVariables.includes(variableName)) {
318
- return `__stateStack.globals.${variableName}`;
319
- }
320
- return variableName;
321
- }
322
301
  isImportedTool(functionName) {
323
302
  return this.importedTools
324
303
  .map((node) => node.importedTools)
@@ -22,11 +22,6 @@ export class GraphGenerator extends TypeScriptGenerator {
22
22
  }
23
23
  configDefaults() {
24
24
  return {
25
- log: {
26
- host: "https://statelog.adit.io",
27
- projectId: "agency-lang",
28
- debugMode: false,
29
- },
30
25
  client: {
31
26
  logLevel: "warn",
32
27
  defaultModel: "gpt-4o-mini",
@@ -61,18 +56,13 @@ export class GraphGenerator extends TypeScriptGenerator {
61
56
  } */
62
57
  this.adjacentNodes[nodeName] = [];
63
58
  this.currentAdjacentNodes = [];
64
- this.functionParameters = [];
65
59
  this.isInsideGraphNode = true;
66
- for (const param of parameters) {
67
- this.functionParameters.push(param.name);
68
- }
69
60
  for (const stmt of body) {
70
61
  if (stmt.type === "functionCall" && this.isGraphNode(stmt.functionName)) {
71
62
  throw new Error(`Call to graph node '${stmt.functionName}' inside graph node '${nodeName}' was not returned. All calls to graph nodes must be returned, eg (return ${stmt.functionName}(...)).`);
72
63
  }
73
64
  }
74
65
  const bodyCode = this.processBodyAsParts(body);
75
- this.functionParameters = [];
76
66
  this.adjacentNodes[nodeName] = [...this.currentAdjacentNodes];
77
67
  this.isInsideGraphNode = false;
78
68
  this.endScope();
@@ -132,13 +132,7 @@ export class TypeScriptGenerator extends BaseGenerator {
132
132
  }
133
133
  processAssignment(node) {
134
134
  const { variableName, typeHint, value } = node;
135
- const _currentScope = this.getCurrentScope();
136
- if (_currentScope.type === "global") {
137
- this.globalScopedVariables.push(variableName);
138
- }
139
- else {
140
- this.functionScopedVariables.push(variableName);
141
- }
135
+ const scopeVar = this.scopetoString(node.scope);
142
136
  const typeAnnotation = "";
143
137
  if (value.type === "prompt") {
144
138
  return this.processPromptLiteral(variableName, typeHint, value);
@@ -147,7 +141,7 @@ export class TypeScriptGenerator extends BaseGenerator {
147
141
  // Direct assignment for other literal types
148
142
  const code = this.processNode(value);
149
143
  return renderFunctionCallAssignment.default({
150
- variableName: `${this.getScopeVar()}.${variableName}`,
144
+ variableName: `${scopeVar}.${variableName}`,
151
145
  functionCode: code.trim(),
152
146
  nodeContext: this.getCurrentScope().type === "node",
153
147
  globalScope: this.getCurrentScope().type === "global",
@@ -159,14 +153,13 @@ export class TypeScriptGenerator extends BaseGenerator {
159
153
  return code;
160
154
  }
161
155
  else if (value.type === "messageThread") {
162
- const varName = `${this.getScopeVar()}.${variableName}`;
156
+ const varName = `${scopeVar}.${variableName}`;
163
157
  return this.processMessageThread(value, varName);
164
158
  }
165
159
  else {
166
160
  // Direct assignment for other literal types
167
161
  const code = this.processNode(value);
168
- return (`${this.getScopeVar()}.${variableName}${typeAnnotation} = ${code.trim()};` +
169
- "\n");
162
+ return (`${scopeVar}.${variableName}${typeAnnotation} = ${code.trim()};` + "\n");
170
163
  }
171
164
  }
172
165
  /*
@@ -240,11 +233,7 @@ export class TypeScriptGenerator extends BaseGenerator {
240
233
  this.startScope({ type: "function", functionName: node.functionName });
241
234
  const { functionName, body, parameters } = node;
242
235
  const args = parameters.map((p) => p.name);
243
- this.functionScopedVariables = [...parameters.map((p) => p.name)];
244
- this.functionParameters = args;
245
236
  const bodyCode = this.processBodyAsParts(body);
246
- this.functionScopedVariables = [];
247
- this.functionParameters = [];
248
237
  this.endScope();
249
238
  const argsStr = args.map((arg) => `"${arg}"`).join(", ") || "";
250
239
  return renderFunctionDefinition.default({
@@ -318,7 +307,7 @@ export class TypeScriptGenerator extends BaseGenerator {
318
307
  case "multiLineString":
319
308
  return this.generateStringLiteral(literal.segments);
320
309
  case "variableName":
321
- return this.generateScopedVariableName(literal.value);
310
+ return `${this.scopetoString(literal.scope)}.${literal.value}`;
322
311
  case "prompt":
323
312
  return this.processPromptLiteral(DEFAULT_PROMPT_NAME, this.getScopeReturnType(), literal);
324
313
  }
@@ -393,7 +382,11 @@ export class TypeScriptGenerator extends BaseGenerator {
393
382
  }
394
383
  else {
395
384
  // Interpolation segment
396
- stringParts.push("${" + this.generateScopedVariableName(segment.variableName) + "}");
385
+ stringParts.push("${" +
386
+ this.scopetoString(segment.scope) +
387
+ "." +
388
+ segment.variableName +
389
+ "}");
397
390
  }
398
391
  }
399
392
  return "`" + stringParts.join("") + "`";
@@ -461,7 +454,14 @@ export class TypeScriptGenerator extends BaseGenerator {
461
454
  const metadataObj = `{
462
455
  messages: __self.messages_${this.currentMessageThreadNodeId.at(-1)}.getMessages(),
463
456
  }`;
464
- const scopedFunctionArgs = functionArgs.map((arg) => this.generateScopedVariableName(arg));
457
+ const scopedFunctionArgs = functionArgs.map((arg) => {
458
+ // Find the scope for this interpolated variable from the prompt segments
459
+ const interpSegment = prompt.segments.find((s) => s.type === "interpolation" && s.variableName === arg);
460
+ const scope = interpSegment?.type === "interpolation"
461
+ ? interpSegment.scope
462
+ : undefined;
463
+ return `${this.scopetoString(scope)}.${arg}`;
464
+ });
465
465
  return promptFunction.default({
466
466
  variableName,
467
467
  argsStr,
@@ -6,6 +6,7 @@ import { spawn } from "child_process";
6
6
  import * as fs from "fs";
7
7
  import * as path from "path";
8
8
  import { parseAgency } from "../parser.js";
9
+ import { findRecursively } from "./util.js";
9
10
  // Load configuration from agency.json
10
11
  export function loadConfig(configPath, verbose = false) {
11
12
  let config = {};
@@ -94,31 +95,13 @@ export function getImports(program) {
94
95
  return [...toolAndNodeImports, ...importStatements];
95
96
  }
96
97
  const compiledFiles = new Set();
97
- const dirSearched = new Set();
98
98
  export function compile(config, inputFile, _outputFile) {
99
99
  // Check if the input is a directory
100
100
  const stats = fs.statSync(inputFile);
101
101
  const verbose = config.verbose ?? false;
102
102
  if (stats.isDirectory()) {
103
- dirSearched.add(path.resolve(inputFile));
104
- // Find all .agency files in the directory
105
- const files = fs.readdirSync(inputFile);
106
- const agencyFiles = files.filter((file) => file.endsWith(".agency"));
107
- for (const file of agencyFiles) {
108
- const fullPath = path.join(inputFile, file);
109
- compile(config, fullPath, undefined);
110
- }
111
- // Find all subdirectories and compile their .agency files
112
- const subdirs = files.filter((file) => {
113
- const fullPath = path.join(inputFile, file);
114
- return fs.statSync(fullPath).isDirectory();
115
- });
116
- for (const subdir of subdirs) {
117
- const fullSubdirPath = path.join(inputFile, subdir);
118
- const resolvedSubdirPath = path.resolve(fullSubdirPath);
119
- if (!dirSearched.has(resolvedSubdirPath)) {
120
- compile(config, fullSubdirPath, undefined);
121
- }
103
+ for (const { path } of findRecursively(inputFile)) {
104
+ compile(config, path, undefined);
122
105
  }
123
106
  return null;
124
107
  }
@@ -188,21 +171,8 @@ export async function format(contents, config) {
188
171
  export function formatFile(inputPath, inPlace, config) {
189
172
  const stats = fs.statSync(inputPath);
190
173
  if (stats.isDirectory()) {
191
- // Format all .agency files in directory
192
- const files = fs.readdirSync(inputPath);
193
- const agencyFiles = files.filter((file) => file.endsWith(".agency"));
194
- for (const file of agencyFiles) {
195
- const fullPath = path.join(inputPath, file);
196
- formatFile(fullPath, inPlace, config);
197
- }
198
- // Recursively format subdirectories
199
- const subdirs = files.filter((file) => {
200
- const fullPath = path.join(inputPath, file);
201
- return fs.statSync(fullPath).isDirectory();
202
- });
203
- for (const subdir of subdirs) {
204
- const fullSubdirPath = path.join(inputPath, subdir);
205
- formatFile(fullSubdirPath, inPlace, config);
174
+ for (const { path } of findRecursively(inputPath)) {
175
+ formatFile(path, inPlace, config);
206
176
  }
207
177
  return;
208
178
  }
@@ -1 +1,2 @@
1
- export declare function evaluate(target?: string, argsFilePath?: string, resultsFilePath?: string): Promise<void>;
1
+ import { AgencyConfig } from "../config.js";
2
+ export declare function evaluate(config: AgencyConfig, target?: string, argsFilePath?: string, resultsFilePath?: string): Promise<void>;
@@ -81,7 +81,7 @@ async function createArgsFileInteractively(filename, selectedNode) {
81
81
  console.log(`Args file saved to ${filename}`);
82
82
  return argsFile;
83
83
  }
84
- export async function evaluate(target, argsFilePath, resultsFilePath) {
84
+ export async function evaluate(config, target, argsFilePath, resultsFilePath) {
85
85
  // A. Resolve target
86
86
  let { filename, nodeName } = target
87
87
  ? parseTarget(target)
@@ -162,7 +162,13 @@ export async function evaluate(target, argsFilePath, resultsFilePath) {
162
162
  const argsString = hasArgs
163
163
  ? argsRecordToString(c.args, selectedNode.parameters)
164
164
  : "";
165
- const json = executeNode(filename, nodeName, hasArgs, argsString, undefined);
165
+ const json = executeNode({
166
+ config,
167
+ agencyFile: filename,
168
+ nodeName,
169
+ hasArgs,
170
+ argsString,
171
+ });
166
172
  console.log("\nOutput:");
167
173
  console.log(JSON.stringify(json.data, null, 2));
168
174
  const ratingResponse = await prompts({
@@ -1,2 +1,3 @@
1
- export declare function fixtures(target?: string): Promise<void>;
2
- export declare function test(testFile?: string): Promise<void>;
1
+ import { AgencyConfig } from "../config.js";
2
+ export declare function fixtures(config: AgencyConfig, target?: string): Promise<void>;
3
+ export declare function test(config: AgencyConfig, testFile: string): Promise<void>;
@@ -2,8 +2,9 @@ import { parseAgency } from "../parser.js";
2
2
  import { getNodesOfType } from "../utils/node.js";
3
3
  import fs from "fs";
4
4
  import prompts from "prompts";
5
- import { executeJudge, executeNode, parseTarget, pickANode, promptForArgs, promptForTarget, } from "./util.js";
5
+ import { executeJudge, executeNode, findRecursively, parseTarget, pickANode, promptForArgs, promptForTarget, } from "./util.js";
6
6
  import { color } from "termcolors";
7
+ import path from "path";
7
8
  function readFile(filename) {
8
9
  console.log("Trying to read file", filename, "...");
9
10
  const data = fs.readFileSync(filename);
@@ -17,7 +18,7 @@ function writeTestCase(agencyFilename, nodeName, input, expectedOutput, evaluati
17
18
  tests = JSON.parse(fs.readFileSync(testFilePath, "utf-8"));
18
19
  }
19
20
  else {
20
- tests = { sourceFile: agencyFilename, tests: [] };
21
+ tests = { sourceFile: path.basename(agencyFilename), tests: [] };
21
22
  }
22
23
  const testCase = {
23
24
  nodeName,
@@ -32,7 +33,7 @@ function writeTestCase(agencyFilename, nodeName, input, expectedOutput, evaluati
32
33
  fs.writeFileSync(testFilePath, JSON.stringify(tests, null, 2));
33
34
  return testFilePath;
34
35
  }
35
- export async function fixtures(target) {
36
+ export async function fixtures(config, target) {
36
37
  let { filename, nodeName } = target
37
38
  ? parseTarget(target)
38
39
  : await promptForTarget();
@@ -56,7 +57,13 @@ export async function fixtures(target) {
56
57
  const selectedNode = nodes.find((n) => n.nodeName === nodeName);
57
58
  let { hasArgs, argsString } = await promptForArgs(selectedNode);
58
59
  console.log("Running program from entrypoint", nodeName);
59
- let json = executeNode(filename, nodeName, hasArgs, argsString);
60
+ let json = executeNode({
61
+ config,
62
+ agencyFile: filename,
63
+ nodeName,
64
+ hasArgs,
65
+ argsString,
66
+ });
60
67
  // Handle interrupt discovery
61
68
  const interruptHandlers = [];
62
69
  while (json.data &&
@@ -105,7 +112,14 @@ export async function fixtures(target) {
105
112
  }
106
113
  interruptHandlers.push(handler);
107
114
  // Continue execution with this handler to see if there are more interrupts
108
- json = executeNode(filename, nodeName, hasArgs, argsString, interruptHandlers);
115
+ json = executeNode({
116
+ config,
117
+ agencyFile: filename,
118
+ nodeName,
119
+ hasArgs,
120
+ argsString,
121
+ interruptHandlers,
122
+ });
109
123
  }
110
124
  console.log("\nFinal Output:");
111
125
  console.log(JSON.stringify(json.data, null, 2));
@@ -166,34 +180,16 @@ export async function fixtures(target) {
166
180
  const testFilePath = writeTestCase(filename, nodeName, inputStr, expectedOutput, criteria, interruptHandlers.length > 0 ? interruptHandlers : undefined);
167
181
  console.log(`Test case saved to ${testFilePath}`);
168
182
  }
169
- export async function test(testFile) {
170
- let selectedFile;
171
- if (testFile) {
172
- selectedFile = testFile;
173
- }
174
- else {
175
- const testFiles = fs
176
- .readdirSync(process.cwd())
177
- .filter((file) => file.endsWith(".test.json"))
178
- .map((file) => ({
179
- title: file,
180
- value: file,
181
- }));
182
- if (testFiles.length === 0) {
183
- console.log("No .test.json files found in the current directory.");
184
- return;
183
+ export async function test(config, testFile) {
184
+ const stats = fs.statSync(testFile);
185
+ if (stats.isDirectory()) {
186
+ for (const { path } of findRecursively(testFile, ".test.json")) {
187
+ await test(config, path);
185
188
  }
186
- const response = await prompts({
187
- type: "select",
188
- name: "filename",
189
- message: "Select a test file to run:",
190
- choices: testFiles,
191
- });
192
- if (!response.filename)
193
- return;
194
- selectedFile = response.filename;
189
+ return;
195
190
  }
196
- const tests = JSON.parse(fs.readFileSync(selectedFile, "utf-8"));
191
+ console.log(color.yellow(`Running tests for ${testFile}...`));
192
+ const tests = JSON.parse(fs.readFileSync(testFile, "utf-8"));
197
193
  let passed = 0;
198
194
  const total = tests.tests.length;
199
195
  for (let i = 0; i < total; i++) {
@@ -207,7 +203,15 @@ export async function test(testFile) {
207
203
  if (testCase.description) {
208
204
  console.log(color.cyan("Description:", testCase.description), "\n");
209
205
  }
210
- const result = executeNode(tests.sourceFile, testCase.nodeName, hasArgs, testCase.input, testCase.interruptHandlers);
206
+ const relativeSourceFilePath = path.join(path.dirname(testFile), tests.sourceFile);
207
+ const result = executeNode({
208
+ config,
209
+ agencyFile: relativeSourceFilePath,
210
+ nodeName: testCase.nodeName,
211
+ hasArgs,
212
+ argsString: testCase.input,
213
+ interruptHandlers: testCase.interruptHandlers,
214
+ });
211
215
  let testPassed = true;
212
216
  for (const criterion of testCase.evaluationCriteria) {
213
217
  if (criterion.type === "exact") {
@@ -1,4 +1,5 @@
1
1
  import { GraphNodeDefinition, VariableType } from "../types.js";
2
+ import { AgencyConfig } from "../config.js";
2
3
  export declare function parseTarget(target: string): {
3
4
  filename: string;
4
5
  nodeName: string;
@@ -12,11 +13,18 @@ export declare function promptForArgs(selectedNode: GraphNodeDefinition): Promis
12
13
  hasArgs: boolean;
13
14
  argsString: string;
14
15
  }>;
15
- export declare function executeNode(agencyFile: string, nodeName: string, hasArgs: boolean, argsString: string, interruptHandlers?: Array<{
16
- action: "approve" | "reject" | "modify";
17
- modifiedArgs?: Record<string, any>;
18
- expectedMessage?: string;
19
- }>): {
16
+ export declare function executeNode({ config, agencyFile, nodeName, hasArgs, argsString, interruptHandlers, }: {
17
+ config: AgencyConfig;
18
+ agencyFile: string;
19
+ nodeName: string;
20
+ hasArgs: boolean;
21
+ argsString: string;
22
+ interruptHandlers?: Array<{
23
+ action: "approve" | "reject" | "modify";
24
+ modifiedArgs?: Record<string, any>;
25
+ expectedMessage?: string;
26
+ }>;
27
+ }): {
20
28
  data: any;
21
29
  [key: string]: any;
22
30
  };
@@ -29,3 +37,6 @@ export declare function executeJudge(actualOutput: string, expectedOutput: strin
29
37
  score: number;
30
38
  reasoning: string;
31
39
  };
40
+ export declare function findRecursively(dirName: string, ext?: string, searched?: string[]): Generator<{
41
+ path: string;
42
+ }>;
@@ -89,9 +89,9 @@ export async function promptForArgs(selectedNode) {
89
89
  }
90
90
  return { hasArgs, argsString };
91
91
  }
92
- export function executeNode(agencyFile, nodeName, hasArgs, argsString, interruptHandlers) {
92
+ export function executeNode({ config, agencyFile, nodeName, hasArgs, argsString, interruptHandlers, }) {
93
93
  const outFile = agencyFile.replace(".agency", ".js");
94
- compile({}, agencyFile, outFile);
94
+ compile(config, agencyFile);
95
95
  const evaluateScript = renderEvaluate({
96
96
  filename: outFile,
97
97
  nodeName,
@@ -158,3 +158,24 @@ export function executeJudge(actualOutput, expectedOutput, judgePrompt, interrup
158
158
  const results = readFileSync("__judge_evaluate.json", "utf-8");
159
159
  return JSON.parse(results).data;
160
160
  }
161
+ export function* findRecursively(dirName, ext = ".agency", searched = []) {
162
+ searched.push(path.resolve(dirName));
163
+ // Find all .agency files in the directory
164
+ const files = fs.readdirSync(dirName);
165
+ const filesToProcess = files.filter((file) => {
166
+ return ((file.endsWith(ext) ||
167
+ fs.statSync(path.join(dirName, file)).isDirectory()) &&
168
+ !file.startsWith("."));
169
+ });
170
+ for (const file of filesToProcess) {
171
+ const fullPath = path.join(dirName, file);
172
+ if (fs.statSync(fullPath).isDirectory()) {
173
+ if (!searched.includes(path.resolve(fullPath))) {
174
+ yield* findRecursively(fullPath, ext, searched);
175
+ }
176
+ }
177
+ else {
178
+ yield { path: fullPath };
179
+ }
180
+ }
181
+ }
@@ -90,4 +90,10 @@ export declare class TypescriptPreprocessor {
90
90
  * Extract domain from URL string
91
91
  */
92
92
  protected _extractDomain(url: string): string | null;
93
+ /**
94
+ * Resolve variable scopes by annotating AST nodes with their scope.
95
+ * After this pass, every VariableNameLiteral, InterpolationSegment, and Assignment
96
+ * will have a `scope` property indicating whether the variable is global, local, or args.
97
+ */
98
+ protected resolveVariableScopes(): void;
93
99
  }
@@ -25,6 +25,7 @@ export class TypescriptPreprocessor {
25
25
  this.filterExcludedBuiltinFunctions();
26
26
  this.validateFetchDomains();
27
27
  this.addNodeIDsToMessageThreads();
28
+ this.resolveVariableScopes();
28
29
  return this.program;
29
30
  }
30
31
  addNodeIDsToMessageThreads() {
@@ -903,4 +904,70 @@ export class TypescriptPreprocessor {
903
904
  return null; // Invalid URL
904
905
  }
905
906
  }
907
+ /**
908
+ * Resolve variable scopes by annotating AST nodes with their scope.
909
+ * After this pass, every VariableNameLiteral, InterpolationSegment, and Assignment
910
+ * will have a `scope` property indicating whether the variable is global, local, or args.
911
+ */
912
+ resolveVariableScopes() {
913
+ const varNameToScope = {};
914
+ // First, for each variable name, we try to collect its scope.
915
+ for (const { node, scopes } of walkNodes(this.program.nodes)) {
916
+ if (scopes.length === 0) {
917
+ throw new Error(`Top-level nodes should have at least the global scope in their scopes array. Node: ${JSON.stringify({ node })}, scopes: ${JSON.stringify({ scopes })}`);
918
+ }
919
+ if (node.type === "assignment") {
920
+ varNameToScope[node.variableName] = scopes.at(-1)?.type || "global";
921
+ }
922
+ else if (node.type === "function" || node.type === "graphNode") {
923
+ // Parameters are in the function's scope
924
+ for (const param of node.parameters) {
925
+ varNameToScope[param.name] = "args";
926
+ }
927
+ }
928
+ else if (node.type === "importStatement") {
929
+ // todo imported names need to get parsed better,
930
+ // into an array
931
+ }
932
+ else if (node.type === "importNodeStatement") {
933
+ node.importedNodes.forEach((n) => {
934
+ varNameToScope[n] = "global";
935
+ });
936
+ }
937
+ else if (node.type === "importToolStatement") {
938
+ node.importedTools.forEach((t) => {
939
+ varNameToScope[t] = "global";
940
+ });
941
+ }
942
+ }
943
+ const lookupScope = (varName) => {
944
+ if (varName in varNameToScope) {
945
+ return varNameToScope[varName];
946
+ }
947
+ return "global";
948
+ // TODO enable this
949
+ /* throw new Error(
950
+ `Variable "${varName}" is referenced but not defined in any scope.`,
951
+ ); */
952
+ };
953
+ // Then, whenever we see a variable being referenced,
954
+ // we try to look up its scope and set it on that variable.
955
+ for (const { node, scopes } of walkNodes(this.program.nodes)) {
956
+ if (node.type === "assignment") {
957
+ node.scope = lookupScope(node.variableName);
958
+ }
959
+ else if (node.type === "variableName") {
960
+ node.scope = lookupScope(node.value);
961
+ }
962
+ else if (node.type === "prompt" ||
963
+ node.type === "string" ||
964
+ node.type === "multiLineString") {
965
+ node.segments.forEach((seg) => {
966
+ if (seg.type === "interpolation") {
967
+ seg.scope = lookupScope(seg.variableName);
968
+ }
969
+ });
970
+ }
971
+ }
972
+ }
906
973
  }
@@ -319,22 +319,28 @@ export class TypeChecker {
319
319
  return true;
320
320
  }
321
321
  // Same kind matching
322
- if (resolvedSource.type === "primitiveType" && resolvedTarget.type === "primitiveType") {
322
+ if (resolvedSource.type === "primitiveType" &&
323
+ resolvedTarget.type === "primitiveType") {
323
324
  return resolvedSource.value === resolvedTarget.value;
324
325
  }
325
- if (resolvedSource.type === "stringLiteralType" && resolvedTarget.type === "stringLiteralType") {
326
+ if (resolvedSource.type === "stringLiteralType" &&
327
+ resolvedTarget.type === "stringLiteralType") {
326
328
  return resolvedSource.value === resolvedTarget.value;
327
329
  }
328
- if (resolvedSource.type === "numberLiteralType" && resolvedTarget.type === "numberLiteralType") {
330
+ if (resolvedSource.type === "numberLiteralType" &&
331
+ resolvedTarget.type === "numberLiteralType") {
329
332
  return resolvedSource.value === resolvedTarget.value;
330
333
  }
331
- if (resolvedSource.type === "booleanLiteralType" && resolvedTarget.type === "booleanLiteralType") {
334
+ if (resolvedSource.type === "booleanLiteralType" &&
335
+ resolvedTarget.type === "booleanLiteralType") {
332
336
  return resolvedSource.value === resolvedTarget.value;
333
337
  }
334
- if (resolvedSource.type === "arrayType" && resolvedTarget.type === "arrayType") {
338
+ if (resolvedSource.type === "arrayType" &&
339
+ resolvedTarget.type === "arrayType") {
335
340
  return this.isAssignable(resolvedSource.elementType, resolvedTarget.elementType);
336
341
  }
337
- if (resolvedSource.type === "objectType" && resolvedTarget.type === "objectType") {
342
+ if (resolvedSource.type === "objectType" &&
343
+ resolvedTarget.type === "objectType") {
338
344
  // Structural: source must have all properties of target with compatible types
339
345
  for (const targetProp of resolvedTarget.properties) {
340
346
  const sourceProp = resolvedSource.properties.find((p) => p.key === targetProp.key);
@@ -1,3 +1,4 @@
1
+ import { ScopeType } from "../types.js";
1
2
  import { AgencyObject } from "./dataStructures.js";
2
3
  import { Skill } from "./skill.js";
3
4
  import { UsesTool } from "./tools.js";
@@ -17,6 +18,7 @@ export type MultiLineStringLiteral = {
17
18
  export type VariableNameLiteral = {
18
19
  type: "variableName";
19
20
  value: string;
21
+ scope?: ScopeType;
20
22
  };
21
23
  export type PromptSegment = TextSegment | InterpolationSegment;
22
24
  export type TextSegment = {
@@ -26,6 +28,7 @@ export type TextSegment = {
26
28
  export type InterpolationSegment = {
27
29
  type: "interpolation";
28
30
  variableName: string;
31
+ scope?: ScopeType;
29
32
  };
30
33
  export type PromptLiteral = {
31
34
  type: "prompt";
@@ -2,18 +2,18 @@ import { AccessExpression, DotProperty, IndexAccess } from "./types/access.js";
2
2
  import { AgencyArray, AgencyObject } from "./types/dataStructures.js";
3
3
  import { FunctionCall, FunctionDefinition } from "./types/function.js";
4
4
  import { GraphNodeDefinition } from "./types/graphNode.js";
5
+ import { IfElse } from "./types/ifElse.js";
5
6
  import { ImportNodeStatement, ImportStatement, ImportToolStatement } from "./types/importStatement.js";
6
7
  import { Literal, RawCode } from "./types/literals.js";
7
8
  import { MatchBlock } from "./types/matchBlock.js";
9
+ import { MessageThread } from "./types/messageThread.js";
8
10
  import { ReturnStatement } from "./types/returnStatement.js";
11
+ import { Skill } from "./types/skill.js";
9
12
  import { SpecialVar } from "./types/specialVar.js";
10
13
  import { TimeBlock } from "./types/timeBlock.js";
11
14
  import { UsesTool } from "./types/tools.js";
12
15
  import { TypeAlias, TypeHint, VariableType } from "./types/typeHints.js";
13
16
  import { WhileLoop } from "./types/whileLoop.js";
14
- import { IfElse } from "./types/ifElse.js";
15
- import { MessageThread } from "./types/messageThread.js";
16
- import { Skill } from "./types/skill.js";
17
17
  export * from "./types/access.js";
18
18
  export * from "./types/dataStructures.js";
19
19
  export * from "./types/function.js";
@@ -23,18 +23,36 @@ export * from "./types/importStatement.js";
23
23
  export * from "./types/literals.js";
24
24
  export * from "./types/matchBlock.js";
25
25
  export * from "./types/returnStatement.js";
26
+ export * from "./types/specialVar.js";
27
+ export * from "./types/timeBlock.js";
26
28
  export * from "./types/tools.js";
27
29
  export * from "./types/typeHints.js";
28
30
  export * from "./types/whileLoop.js";
29
- export * from "./types/timeBlock.js";
30
- export * from "./types/specialVar.js";
31
- export * from "./types/ifElse.js";
31
+ export type Scope = GlobalScope | FunctionScope | NodeScope;
32
+ export type ScopeType = Scope["type"] | "args";
33
+ export type GlobalScope = {
34
+ type: "global";
35
+ };
36
+ export type FunctionScope = {
37
+ type: "function";
38
+ functionName: string;
39
+ args?: boolean;
40
+ };
41
+ export type NodeScope = {
42
+ type: "node";
43
+ nodeName: string;
44
+ args?: boolean;
45
+ };
32
46
  export type Assignment = {
33
47
  type: "assignment";
34
48
  variableName: string;
35
49
  typeHint?: VariableType;
50
+ scope?: ScopeType;
36
51
  value: AccessExpression | Literal | FunctionCall | AgencyObject | AgencyArray | IndexAccess | TimeBlock | MessageThread;
37
52
  };
53
+ export declare function globalScope(): Scope;
54
+ export declare function functionScope(functionName: string, args?: boolean): Scope;
55
+ export declare function nodeScope(nodeName: string, args?: boolean): Scope;
38
56
  export type AgencyComment = {
39
57
  type: "comment";
40
58
  content: string;
package/dist/lib/types.js CHANGED
@@ -7,9 +7,17 @@ export * from "./types/importStatement.js";
7
7
  export * from "./types/literals.js";
8
8
  export * from "./types/matchBlock.js";
9
9
  export * from "./types/returnStatement.js";
10
+ export * from "./types/specialVar.js";
11
+ export * from "./types/timeBlock.js";
10
12
  export * from "./types/tools.js";
11
13
  export * from "./types/typeHints.js";
12
14
  export * from "./types/whileLoop.js";
13
- export * from "./types/timeBlock.js";
14
- export * from "./types/specialVar.js";
15
- export * from "./types/ifElse.js";
15
+ export function globalScope() {
16
+ return { type: "global" };
17
+ }
18
+ export function functionScope(functionName, args = false) {
19
+ return { type: "function", functionName, args };
20
+ }
21
+ export function nodeScope(nodeName, args = false) {
22
+ return { type: "node", nodeName, args };
23
+ }
@@ -1,10 +1,11 @@
1
- import { AgencyNode } from "../types.js";
1
+ import { AgencyNode, Scope } from "../types.js";
2
2
  export declare function getAllVariablesInBody(body: AgencyNode[]): Generator<{
3
3
  name: string;
4
4
  node: AgencyNode;
5
5
  }>;
6
6
  export declare function getNodesOfType<T extends AgencyNode["type"]>(nodes: AgencyNode[], type: T): AgencyNode[];
7
- export declare function walkNodes(nodes: AgencyNode[], ancestors?: AgencyNode[]): Generator<{
7
+ export declare function walkNodes(nodes: AgencyNode[], ancestors?: AgencyNode[], scopes?: Scope[]): Generator<{
8
8
  node: AgencyNode;
9
9
  ancestors: AgencyNode[];
10
+ scopes: Scope[];
10
11
  }>;
@@ -1,3 +1,4 @@
1
+ import { functionScope, globalScope, nodeScope, } from "../types.js";
1
2
  export function* getAllVariablesInBody(body) {
2
3
  for (const { node } of walkNodes(body)) {
3
4
  if (node.type === "assignment") {
@@ -19,6 +20,7 @@ export function* getAllVariablesInBody(body) {
19
20
  yield* getAllVariablesInBody(node.body);
20
21
  }
21
22
  else if (node.type === "ifElse") {
23
+ yield* getAllVariablesInBody([node.condition]);
22
24
  yield* getAllVariablesInBody(node.thenBody);
23
25
  if (node.elseBody) {
24
26
  yield* getAllVariablesInBody(node.elseBody);
@@ -131,78 +133,84 @@ export function getNodesOfType(nodes, type) {
131
133
  }
132
134
  return result;
133
135
  }
134
- export function* walkNodes(nodes, ancestors = []) {
136
+ export function* walkNodes(nodes, ancestors = [], scopes = []) {
137
+ if (scopes.length === 0) {
138
+ scopes.push(globalScope());
139
+ }
135
140
  for (const node of nodes) {
136
- yield { node, ancestors };
141
+ yield { node, ancestors, scopes };
137
142
  if (node.type === "function") {
138
- yield* walkNodes(node.body, [...ancestors, node]);
143
+ yield* walkNodes(node.body, [...ancestors, node], [...scopes, functionScope(node.functionName)]);
139
144
  }
140
145
  else if (node.type === "graphNode") {
141
- yield* walkNodes(node.body, [...ancestors, node]);
146
+ yield* walkNodes(node.body, [...ancestors, node], [...scopes, nodeScope(node.nodeName)]);
142
147
  }
143
148
  else if (node.type === "ifElse") {
144
- yield* walkNodes(node.thenBody, [...ancestors, node]);
149
+ yield* walkNodes([node.condition], [...ancestors, node], scopes);
150
+ yield* walkNodes(node.thenBody, [...ancestors, node], scopes);
145
151
  if (node.elseBody) {
146
- yield* walkNodes(node.elseBody, [...ancestors, node]);
152
+ yield* walkNodes(node.elseBody, [...ancestors, node], scopes);
147
153
  }
148
154
  }
149
155
  else if (node.type === "whileLoop") {
150
- yield* walkNodes(node.body, [...ancestors, node]);
156
+ yield* walkNodes([node.condition], [...ancestors, node], scopes);
157
+ yield* walkNodes(node.body, [...ancestors, node], scopes);
151
158
  }
152
159
  else if (node.type === "timeBlock") {
153
- yield* walkNodes(node.body, [...ancestors, node]);
160
+ yield* walkNodes(node.body, [...ancestors, node], scopes);
154
161
  }
155
162
  else if (node.type === "messageThread") {
156
- yield* walkNodes(node.body, [...ancestors, node]);
163
+ yield* walkNodes(node.body, [...ancestors, node], scopes);
157
164
  }
158
165
  else if (node.type === "returnStatement") {
159
- yield* walkNodes([node.value], [...ancestors, node]);
166
+ yield* walkNodes([node.value], [...ancestors, node], scopes);
160
167
  }
161
168
  else if (node.type === "assignment") {
162
- yield* walkNodes([node.value], [...ancestors, node]);
169
+ yield* walkNodes([node.value], [...ancestors, node], scopes);
163
170
  }
164
171
  else if (node.type === "functionCall") {
165
- yield* walkNodes(node.arguments, [...ancestors, node]);
172
+ yield* walkNodes(node.arguments, [...ancestors, node], scopes);
166
173
  }
167
174
  else if (node.type === "matchBlock") {
175
+ yield* walkNodes([node.expression], [...ancestors, node], scopes);
168
176
  for (const caseItem of node.cases) {
169
177
  if (caseItem.type === "comment")
170
178
  continue;
171
179
  if (caseItem.caseValue !== "_") {
172
- yield* walkNodes([caseItem.caseValue], [...ancestors, node]);
180
+ yield* walkNodes([caseItem.caseValue], [...ancestors, node], scopes);
173
181
  }
174
- yield* walkNodes([caseItem.body], [...ancestors, node]);
182
+ yield* walkNodes([caseItem.body], [...ancestors, node], scopes);
175
183
  }
176
184
  }
177
185
  else if (node.type === "accessExpression") {
178
186
  const expr = node.expression;
179
187
  if (expr.type === "dotProperty") {
180
- yield* walkNodes([expr.object], [...ancestors, node]);
188
+ yield* walkNodes([expr.object], [...ancestors, node], scopes);
181
189
  }
182
190
  else if (expr.type === "indexAccess") {
183
- yield* walkNodes([expr.array], [...ancestors, node]);
184
- yield* walkNodes([expr.index], [...ancestors, node]);
191
+ yield* walkNodes([expr.array], [...ancestors, node], scopes);
192
+ yield* walkNodes([expr.index], [...ancestors, node], scopes);
185
193
  }
186
194
  else if (expr.type === "dotFunctionCall") {
187
- yield* walkNodes([expr.object], [...ancestors, node]);
188
- yield* walkNodes([expr.functionCall], [...ancestors, node]);
195
+ yield* walkNodes([expr.object], [...ancestors, node], scopes);
196
+ yield* walkNodes([expr.functionCall], [...ancestors, node], scopes);
189
197
  }
190
198
  }
191
199
  else if (node.type === "dotProperty") {
192
- yield* walkNodes([node.object], [...ancestors, node]);
200
+ yield* walkNodes([node.object], [...ancestors, node], scopes);
193
201
  }
194
202
  else if (node.type === "indexAccess") {
195
- yield* walkNodes([node.array], [...ancestors, node]);
196
- yield* walkNodes([node.index], [...ancestors, node]);
203
+ yield* walkNodes([node.array], [...ancestors, node], scopes);
204
+ yield* walkNodes([node.index], [...ancestors, node], scopes);
197
205
  }
198
206
  else if (node.type === "agencyArray") {
199
- yield* walkNodes(node.items, [...ancestors, node]);
207
+ yield* walkNodes(node.items, [...ancestors, node], scopes);
200
208
  }
201
209
  else if (node.type === "agencyObject") {
202
- yield* walkNodes(node.entries.map((e) => e.value), [...ancestors, node]);
210
+ yield* walkNodes(node.entries.map((e) => e.value), [...ancestors, node], scopes);
203
211
  }
204
212
  else if (node.type === "specialVar") {
205
- yield* walkNodes([node.value], [...ancestors, node]);
213
+ yield* walkNodes([node.value], [...ancestors, node], scopes);
206
214
  }
207
215
  }
208
216
  }
@@ -4,10 +4,11 @@ import { evaluate } from "../lib/cli/evaluate.js";
4
4
  import { fixtures, test } from "../lib/cli/test.js";
5
5
  import { _parseAgency } from "../lib/parser.js";
6
6
  import { TypescriptPreprocessor } from "../lib/preprocessors/typescriptPreprocessor.js";
7
- import { typeCheck, formatErrors } from "../lib/typeChecker.js";
7
+ import { formatErrors, typeCheck } from "../lib/typeChecker.js";
8
8
  import { Command } from "commander";
9
9
  import * as fs from "fs";
10
10
  import { TarsecError } from "tarsec";
11
+ import process from "process";
11
12
  const program = new Command();
12
13
  program
13
14
  .name("agency")
@@ -23,93 +24,104 @@ function getConfig() {
23
24
  }
24
25
  return config;
25
26
  }
26
- function sleep(ms) {
27
- return new Promise((resolve) => setTimeout(resolve, ms * 1000));
28
- }
29
27
  program
30
28
  .command("compile")
31
29
  .alias("build")
32
- .description("Compile .agency file or directory to JavaScript")
33
- .argument("<input>", "Path to .agency input file or directory")
34
- .argument("[output]", "Path to output .js file (optional)")
35
- .action(async (input, output) => {
36
- compile(getConfig(), input, output);
30
+ .description("Compile .agency file(s) or directory(s) to JavaScript")
31
+ .argument("<inputs...>", "Paths to .agency input files or directories")
32
+ .action(async (inputs) => {
33
+ const config = getConfig();
34
+ for (const input of inputs) {
35
+ compile(config, input);
36
+ }
37
37
  });
38
38
  program
39
39
  .command("run")
40
- .description("Compile and run .agency file")
41
- .argument("<input>", "Path to .agency input file")
42
- .argument("[output]", "Path to output .js file (optional)")
43
- .action((input, output) => {
44
- run(getConfig(), input, output);
40
+ .description("Compile and run .agency file(s)")
41
+ .argument("[input]", "Paths to .agency input file")
42
+ .action((input) => {
43
+ const config = getConfig();
44
+ run(config, input);
45
45
  });
46
46
  program
47
47
  .command("format")
48
48
  .alias("fmt")
49
- .description("Format .agency file or directory (reads from stdin if no input)")
50
- .argument("[input]", "Path to .agency input file or directory")
49
+ .description("Format .agency file(s) or directory(s) (reads from stdin if no input)")
50
+ .argument("[inputs...]", "Paths to .agency input files or directories")
51
51
  .option("-i, --in-place", "Format file(s) in-place")
52
- .action(async (input, opts) => {
52
+ .action(async (inputs, opts) => {
53
53
  const config = getConfig();
54
- if (!input) {
54
+ if (inputs.length === 0) {
55
55
  const contents = await readStdin();
56
56
  const formatted = await format(contents, config);
57
57
  console.log(formatted);
58
58
  }
59
59
  else {
60
- formatFile(input, opts.inPlace ?? false, config);
60
+ for (const input of inputs) {
61
+ formatFile(input, opts.inPlace ?? false, config);
62
+ }
61
63
  }
62
64
  });
63
65
  program
64
66
  .command("ast")
65
67
  .alias("parse")
66
- .description("Parse .agency file and show AST (reads from stdin if no input)")
67
- .argument("[input]", "Path to .agency input file")
68
- .action(async (input) => {
68
+ .description("Parse .agency file(s) and show AST (reads from stdin if no input)")
69
+ .argument("[inputs...]", "Paths to .agency input files")
70
+ .action(async (inputs) => {
69
71
  const config = getConfig();
70
- let contents;
71
- if (!input) {
72
- contents = await readStdin();
72
+ if (inputs.length === 0) {
73
+ const contents = await readStdin();
74
+ const result = parse(contents, config);
75
+ console.log(JSON.stringify(result, null, 2));
73
76
  }
74
77
  else {
75
- contents = readFile(input);
78
+ for (const input of inputs) {
79
+ const contents = readFile(input);
80
+ const result = parse(contents, config);
81
+ console.log(JSON.stringify(result, null, 2));
82
+ }
76
83
  }
77
- const result = parse(contents, config);
78
- console.log(JSON.stringify(result, null, 2));
79
84
  });
80
85
  program
81
86
  .command("graph")
82
87
  .alias("mermaid")
83
- .description("Render Mermaid graph from .agency file (reads from stdin if no input)")
84
- .argument("[input]", "Path to .agency input file")
85
- .action(async (input) => {
88
+ .description("Render Mermaid graph from .agency file(s) (reads from stdin if no input)")
89
+ .argument("[inputs...]", "Paths to .agency input files")
90
+ .action(async (inputs) => {
86
91
  const config = getConfig();
87
- let contents;
88
- if (!input) {
89
- contents = await readStdin();
92
+ if (inputs.length === 0) {
93
+ const contents = await readStdin();
94
+ renderGraph(contents, config);
90
95
  }
91
96
  else {
92
- contents = readFile(input);
97
+ for (const input of inputs) {
98
+ const contents = readFile(input);
99
+ renderGraph(contents, config);
100
+ }
93
101
  }
94
- renderGraph(contents, config);
95
102
  });
96
103
  program
97
104
  .command("preprocess")
98
- .description("Parse .agency file and show AST after preprocessing (reads from stdin if no input)")
99
- .argument("[input]", "Path to .agency input file")
100
- .action(async (input) => {
105
+ .description("Parse .agency file(s) and show AST after preprocessing (reads from stdin if no input)")
106
+ .argument("[inputs...]", "Paths to .agency input files")
107
+ .action(async (inputs) => {
101
108
  const config = getConfig();
102
- let contents;
103
- if (!input) {
104
- contents = await readStdin();
109
+ const process = (contents) => {
110
+ const parsedProgram = parse(contents, config);
111
+ const preprocessor = new TypescriptPreprocessor(parsedProgram, config);
112
+ preprocessor.preprocess();
113
+ console.log(JSON.stringify(preprocessor.program, null, 2));
114
+ };
115
+ if (inputs.length === 0) {
116
+ const contents = await readStdin();
117
+ process(contents);
105
118
  }
106
119
  else {
107
- contents = readFile(input);
120
+ for (const input of inputs) {
121
+ const contents = readFile(input);
122
+ process(contents);
123
+ }
108
124
  }
109
- const parsedProgram = parse(contents, config);
110
- const preprocessor = new TypescriptPreprocessor(parsedProgram, config);
111
- preprocessor.preprocess();
112
- console.log(JSON.stringify(preprocessor.program, null, 2));
113
125
  });
114
126
  program
115
127
  .command("evaluate")
@@ -119,7 +131,7 @@ program
119
131
  .option("--args <path>", "Path to eval args JSON file")
120
132
  .option("--results <path>", "Path to existing results file (to resume)")
121
133
  .action(async (target, opts) => {
122
- await evaluate(target, opts.args, opts.results);
134
+ await evaluate(getConfig(), target, opts.args, opts.results);
123
135
  });
124
136
  program
125
137
  .command("gen-fixtures")
@@ -127,59 +139,87 @@ program
127
139
  .description("Generate test fixtures")
128
140
  .argument("[target]", "Target in file.agency:nodeName format")
129
141
  .action(async (target) => {
130
- await fixtures(target);
142
+ await fixtures(getConfig(), target);
131
143
  });
132
144
  program
133
145
  .command("test")
134
146
  .description("Run tests")
135
- .argument("[testFile]", "Path to .test.json file")
147
+ .argument("[inputs...]", "Paths to .test.json files or directories")
136
148
  .action(async (testFile) => {
137
- await test(testFile);
149
+ for (const file of testFile) {
150
+ await test(getConfig(), file);
151
+ }
138
152
  });
139
153
  program
140
154
  .command("diagnostics")
141
155
  .description("Run diagnostics for VSCode")
142
- .argument("[testFile]", "Path to .test.json file")
143
- .action(async (testFile) => {
144
- const contents = testFile ? readFile(testFile) : await readStdin();
145
- try {
146
- _parseAgency(contents);
147
- }
148
- catch (error) {
149
- if (error instanceof TarsecError) {
150
- console.log(JSON.stringify(error.data, null, 2));
156
+ .argument("[inputs...]", "Paths to .agency input files")
157
+ .action(async (inputs) => {
158
+ if (inputs.length === 0) {
159
+ const contents = await readStdin();
160
+ try {
161
+ _parseAgency(contents);
151
162
  }
152
- else {
153
- throw error;
163
+ catch (error) {
164
+ if (error instanceof TarsecError) {
165
+ console.log(JSON.stringify(error.data, null, 2));
166
+ }
167
+ else {
168
+ throw error;
169
+ }
170
+ }
171
+ }
172
+ else {
173
+ for (const input of inputs) {
174
+ const contents = readFile(input);
175
+ try {
176
+ _parseAgency(contents);
177
+ }
178
+ catch (error) {
179
+ if (error instanceof TarsecError) {
180
+ console.log(JSON.stringify(error.data, null, 2));
181
+ }
182
+ else {
183
+ throw error;
184
+ }
185
+ }
154
186
  }
155
187
  }
156
188
  });
157
189
  program
158
190
  .command("typecheck")
159
191
  .alias("tc")
160
- .description("Type check .agency file (reads from stdin if no input)")
161
- .argument("[input]", "Path to .agency input file")
192
+ .description("Type check .agency file(s) (reads from stdin if no input)")
193
+ .argument("[inputs...]", "Paths to .agency input files")
162
194
  .option("--strict", "Enable strict types (untyped variables are errors)")
163
- .action(async (input, opts) => {
195
+ .action(async (inputs, opts) => {
164
196
  const config = getConfig();
197
+ let hasErrors = false;
198
+ const runTypeCheck = (contents) => {
199
+ const parsedProgram = parse(contents, config);
200
+ const { errors } = typeCheck(parsedProgram, config);
201
+ if (errors.length > 0) {
202
+ console.error(formatErrors(errors));
203
+ hasErrors = true;
204
+ }
205
+ else {
206
+ console.log("No type errors found.");
207
+ }
208
+ };
165
209
  if (opts.strict)
166
210
  config.strictTypes = true;
167
- let contents;
168
- if (!input) {
169
- contents = await readStdin();
211
+ if (inputs.length === 0) {
212
+ const contents = await readStdin();
213
+ runTypeCheck(contents);
170
214
  }
171
215
  else {
172
- contents = readFile(input);
216
+ for (const input of inputs) {
217
+ const contents = readFile(input);
218
+ runTypeCheck(contents);
219
+ }
173
220
  }
174
- const parsedProgram = parse(contents, config);
175
- const { errors } = typeCheck(parsedProgram, config);
176
- if (errors.length > 0) {
177
- console.error(formatErrors(errors));
221
+ if (hasErrors)
178
222
  process.exit(1);
179
- }
180
- else {
181
- console.log("No type errors found.");
182
- }
183
223
  });
184
224
  // Default: treat unknown args as a file to run
185
225
  program.arguments("[file]").action((file) => {
package/package.json CHANGED
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "agency-lang",
3
- "version": "0.0.56",
3
+ "version": "0.0.58",
4
4
  "description": "The Agency language",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
7
7
  "test": "vitest",
8
8
  "test:run": "vitest run",
9
+ "test:agency": "node ./dist/scripts/agency.js test -v tests/agency",
9
10
  "build": "rm -rf dist/ && tsc && tsc-alias && cp -r lib/agents dist/lib/agents",
10
11
  "start": "node dist/index.js",
11
12
  "templates": "typestache ./lib/templates",
@@ -54,7 +55,7 @@
54
55
  "piemachine": "^0.0.8",
55
56
  "prompts": "^2.4.2",
56
57
  "smoltalk": "^0.0.18",
57
- "statelog-client": "^0.0.36",
58
+ "statelog-client": "^0.0.38",
58
59
  "tarsec": "^0.1.8",
59
60
  "typestache": "^0.4.4",
60
61
  "zod": "^4.3.5"