@farmslot/agent-runtime 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/bin/farmslot-agent.mjs +1 -1
  3. package/dist/execution-template/create.d.ts.map +1 -1
  4. package/dist/execution-template/create.js +9 -1
  5. package/dist/execution-template/create.js.map +1 -1
  6. package/dist/execution-template/execution-template.test.js +309 -1
  7. package/dist/execution-template/execution-template.test.js.map +1 -1
  8. package/dist/execution-template/frontmatter.d.ts.map +1 -1
  9. package/dist/execution-template/frontmatter.js +19 -4
  10. package/dist/execution-template/frontmatter.js.map +1 -1
  11. package/dist/execution-template/index.d.ts +8 -1
  12. package/dist/execution-template/index.d.ts.map +1 -1
  13. package/dist/execution-template/index.js +5 -0
  14. package/dist/execution-template/index.js.map +1 -1
  15. package/dist/execution-template/infer.d.ts.map +1 -1
  16. package/dist/execution-template/infer.js +14 -1
  17. package/dist/execution-template/infer.js.map +1 -1
  18. package/dist/execution-template/lint.d.ts.map +1 -1
  19. package/dist/execution-template/lint.js +8 -0
  20. package/dist/execution-template/lint.js.map +1 -1
  21. package/dist/execution-template/project-worker.d.ts +19 -0
  22. package/dist/execution-template/project-worker.d.ts.map +1 -0
  23. package/dist/execution-template/project-worker.js +143 -0
  24. package/dist/execution-template/project-worker.js.map +1 -0
  25. package/dist/execution-template/resolve.d.ts +1 -1
  26. package/dist/execution-template/resolve.d.ts.map +1 -1
  27. package/dist/execution-template/resolve.js +12 -2
  28. package/dist/execution-template/resolve.js.map +1 -1
  29. package/dist/execution-template/select.d.ts +8 -0
  30. package/dist/execution-template/select.d.ts.map +1 -0
  31. package/dist/execution-template/select.js +80 -0
  32. package/dist/execution-template/select.js.map +1 -0
  33. package/dist/execution-template/snapshot.d.ts +13 -0
  34. package/dist/execution-template/snapshot.d.ts.map +1 -0
  35. package/dist/execution-template/snapshot.js +46 -0
  36. package/dist/execution-template/snapshot.js.map +1 -0
  37. package/dist/execution-template/source-config.d.ts +19 -0
  38. package/dist/execution-template/source-config.d.ts.map +1 -0
  39. package/dist/execution-template/source-config.js +90 -0
  40. package/dist/execution-template/source-config.js.map +1 -0
  41. package/dist/execution-template/types.d.ts +38 -1
  42. package/dist/execution-template/types.d.ts.map +1 -1
  43. package/dist/execution-template/types.js +7 -2
  44. package/dist/execution-template/types.js.map +1 -1
  45. package/package.json +4 -3
  46. package/scripts/check-task-artifact-contract.mjs +112 -33
  47. package/scripts/checklist-target.cjs +13 -0
  48. package/scripts/execution-template-cli.mjs +150 -19
  49. package/scripts/mark-checklist-step.cjs +16 -6
@@ -3,7 +3,7 @@
3
3
  * Standalone execution-template CLI for ADR-049.
4
4
  * Used by `farmslot-agent execution-template` and thin Consensys wrappers.
5
5
  */
6
- import { existsSync, statSync } from 'node:fs';
6
+ import { existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs';
7
7
  import { dirname, resolve } from 'node:path';
8
8
  import { fileURLToPath, pathToFileURL } from 'node:url';
9
9
 
@@ -12,11 +12,13 @@ const distEntry = resolve(packageRoot, 'dist', 'index.js');
12
12
 
13
13
  function usage(exitCode = 0) {
14
14
  const text = [
15
- 'Usage: execution-template <list|lint|new> [options]',
15
+ 'Usage: execution-template <list|materialize|lint|new> [options]',
16
16
  '',
17
- 'list --dir <path> --project-worker <path> --package-templates <path>',
17
+ 'list --dir <path> --domain-dir <domain=path> --project-worker <path> --package-templates <path>',
18
18
  ' [--project-name name] [--package-id id] [--flow f] [--run-mode m]',
19
- ' [--platform p] [--no-include-shadowed] [--json]',
19
+ ' [--platform p] [--domain d] [--no-include-shadowed] [--json]',
20
+ 'materialize <output> --flow f --run-mode m --platform p [--domain d] [--id id]',
21
+ ' [the same source options as list] [--provenance path] [--json]',
20
22
  'lint <file-or-dir> [--json]',
21
23
  'new <path> [--flow f] [--run-mode m] [--platform p] [--title t] [--force] [--json]',
22
24
  ].join('\n');
@@ -44,9 +46,22 @@ function parseRunMode(value) {
44
46
  throw new Error(`--run-mode must be autonomous|interactive|validation (got ${value})`);
45
47
  }
46
48
 
47
- async function cmdList(args, runtime) {
49
+ function parseDomainDir(value) {
50
+ const separator = value.indexOf('=');
51
+ if (separator <= 0 || separator === value.length - 1) {
52
+ throw new Error('--domain-dir must use domain=path');
53
+ }
54
+ const domain = value.slice(0, separator);
55
+ if (!/^[a-z][a-z0-9-]*$/.test(domain)) {
56
+ throw new Error(`--domain-dir domain must be a lowercase slug (got ${domain})`);
57
+ }
58
+ return { domain, root: value.slice(separator + 1) };
59
+ }
60
+
61
+ function parseCatalogArgs(args, { materialize = false } = {}) {
48
62
  const opts = {
49
63
  dirs: [],
64
+ domainDirs: [],
50
65
  projectWorker: null,
51
66
  projectName: 'project',
52
67
  packageTemplates: null,
@@ -54,12 +69,18 @@ async function cmdList(args, runtime) {
54
69
  flow: undefined,
55
70
  runMode: undefined,
56
71
  platform: undefined,
72
+ domain: undefined,
73
+ id: undefined,
57
74
  includeShadowed: true,
75
+ output: null,
76
+ provenance: null,
58
77
  json: false,
59
78
  };
60
79
  for (let i = 0; i < args.length; i += 1) {
61
80
  const arg = args[i];
62
81
  if (arg === '--dir') opts.dirs.push(takeValue(args, i++, arg));
82
+ else if (arg === '--domain-dir')
83
+ opts.domainDirs.push(parseDomainDir(takeValue(args, i++, arg)));
63
84
  else if (arg === '--project-worker') opts.projectWorker = takeValue(args, i++, arg);
64
85
  else if (arg === '--project-name') opts.projectName = takeValue(args, i++, arg);
65
86
  else if (arg === '--package-templates') opts.packageTemplates = takeValue(args, i++, arg);
@@ -67,20 +88,51 @@ async function cmdList(args, runtime) {
67
88
  else if (arg === '--flow') opts.flow = takeValue(args, i++, arg);
68
89
  else if (arg === '--run-mode') opts.runMode = parseRunMode(takeValue(args, i++, arg));
69
90
  else if (arg === '--platform') opts.platform = takeValue(args, i++, arg);
91
+ else if (arg === '--domain') opts.domain = takeValue(args, i++, arg);
92
+ else if (arg === '--id') opts.id = takeValue(args, i++, arg);
93
+ else if (arg === '--provenance' && materialize) opts.provenance = takeValue(args, i++, arg);
70
94
  else if (arg === '--include-shadowed') opts.includeShadowed = true;
71
95
  else if (arg === '--no-include-shadowed') opts.includeShadowed = false;
72
96
  else if (arg === '--json') opts.json = true;
73
97
  else if (arg === '-h' || arg === '--help') usage(0);
98
+ else if (materialize && !arg.startsWith('-') && !opts.output) opts.output = arg;
74
99
  else throw new Error(`unknown option ${arg}`);
75
100
  }
101
+ return opts;
102
+ }
76
103
 
104
+ function buildSources(opts, runtime) {
77
105
  const sources = [];
106
+ const domainSourceIds = new Set();
78
107
  for (const [index, dir] of opts.dirs.entries()) {
79
108
  const root = resolve(dir);
80
109
  if (!existsSync(root) || !statSync(root).isDirectory()) {
81
110
  throw new Error(`--dir is not a directory: ${root}`);
82
111
  }
83
- sources.push(runtime.customTemplateSource(`dir-${index + 1}`, root, 'flow-tree'));
112
+ const source = runtime.customTemplateSource(`dir-${index + 1}`, root, 'flow-tree');
113
+ source.sourceRevision = runtime.executionTemplateSourceRevision(root);
114
+ source.sourceDirty = runtime.executionTemplateSourceDirty(root);
115
+ sources.push(source);
116
+ }
117
+ for (const item of opts.domainDirs) {
118
+ const root = resolve(item.root);
119
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
120
+ throw new Error(`--domain-dir is not a directory: ${root}`);
121
+ }
122
+ const sourceId = `team:${item.domain}`;
123
+ if (domainSourceIds.has(sourceId)) {
124
+ throw new Error(`--domain-dir repeats domain ${item.domain}`);
125
+ }
126
+ domainSourceIds.add(sourceId);
127
+ sources.push({
128
+ id: sourceId,
129
+ kind: 'workspace',
130
+ root,
131
+ layout: 'flow-tree',
132
+ domains: [item.domain],
133
+ sourceRevision: runtime.executionTemplateSourceRevision(root),
134
+ sourceDirty: runtime.executionTemplateSourceDirty(root),
135
+ });
84
136
  }
85
137
  if (opts.projectWorker) {
86
138
  const input = resolve(opts.projectWorker);
@@ -92,34 +144,112 @@ async function cmdList(args, runtime) {
92
144
  if (!existsSync(workerRoot) || !statSync(workerRoot).isDirectory()) {
93
145
  throw new Error(`--project-worker is not a directory: ${workerRoot}`);
94
146
  }
95
- sources.push(runtime.projectWorkerTemplateSource(opts.projectName, projectTemplatesDir));
147
+ sources.push(
148
+ runtime.projectWorkerTemplateSource(
149
+ opts.projectName,
150
+ projectTemplatesDir,
151
+ runtime.executionTemplateSourceRevision(projectTemplatesDir),
152
+ runtime.executionTemplateSourceDirty(projectTemplatesDir),
153
+ ),
154
+ );
96
155
  }
97
156
  if (opts.packageTemplates) {
98
157
  const root = resolve(opts.packageTemplates);
99
158
  if (!existsSync(root) || !statSync(root).isDirectory()) {
100
159
  throw new Error(`--package-templates is not a directory: ${root}`);
101
160
  }
102
- sources.push(runtime.packageFlowTreeTemplateSource(opts.packageId, root));
161
+ const source = runtime.packageFlowTreeTemplateSource(opts.packageId, root);
162
+ source.sourceRevision = runtime.executionTemplateSourceRevision(root);
163
+ source.sourceDirty = runtime.executionTemplateSourceDirty(root);
164
+ sources.push(source);
103
165
  }
104
166
  if (sources.length === 0) {
105
- throw new Error('provide --dir, --project-worker, and/or --package-templates');
167
+ throw new Error('provide --dir, --domain-dir, --project-worker, and/or --package-templates');
106
168
  }
169
+ return sources;
170
+ }
107
171
 
108
- const templates = runtime.listExecutionTemplates({
109
- sources,
110
- flow: opts.flow,
111
- runMode: opts.runMode,
112
- platform: opts.platform,
113
- includeShadowed: opts.includeShadowed,
114
- });
172
+ async function cmdList(args, runtime) {
173
+ const opts = parseCatalogArgs(args);
174
+ if (opts.id) throw new Error('--id is only valid with materialize');
175
+ const sources = buildSources(opts, runtime);
176
+ const templates =
177
+ opts.flow && opts.platform && opts.runMode
178
+ ? runtime.listCompatibleExecutionTemplates({
179
+ sources,
180
+ flow: opts.flow,
181
+ platform: opts.platform,
182
+ runMode: opts.runMode,
183
+ ...(opts.domain ? { domain: opts.domain } : {}),
184
+ })
185
+ : runtime.listExecutionTemplates({
186
+ sources,
187
+ flow: opts.flow,
188
+ runMode: opts.runMode,
189
+ platform: opts.platform,
190
+ domain: opts.domain,
191
+ includeShadowed: opts.includeShadowed,
192
+ });
193
+ const catalog = templates.map((entry) => ({
194
+ ...runtime.executionTemplateReference(entry),
195
+ title: entry.title,
196
+ sourceKind: entry.sourceKind,
197
+ ...(entry.shadowedBy ? { shadowedBy: entry.shadowedBy } : {}),
198
+ }));
115
199
  if (opts.json) {
116
- process.stdout.write(`${JSON.stringify({ templates }, null, 2)}\n`);
200
+ process.stdout.write(`${JSON.stringify({ templates: catalog }, null, 2)}\n`);
117
201
  return;
118
202
  }
119
- for (const entry of templates) {
203
+ for (const entry of catalog) {
120
204
  const shadow = entry.shadowedBy ? ` (shadowed by ${entry.shadowedBy})` : '';
121
205
  process.stdout.write(
122
- `${entry.id}\t${entry.flow}\t${entry.runMode ?? '-'}\t${entry.platforms.join(',')}\t${entry.sourceId}\t${entry.path}${shadow}\n`,
206
+ `${entry.id}\t${entry.flow}\t${entry.runMode ?? '-'}\t${entry.platforms.join(',')}\t${entry.sourceId}${shadow}\n`,
207
+ );
208
+ }
209
+ }
210
+
211
+ async function cmdMaterialize(args, runtime) {
212
+ const opts = parseCatalogArgs(args, { materialize: true });
213
+ if (!opts.output) throw new Error('materialize requires <output>');
214
+ if (!opts.flow) throw new Error('materialize requires --flow');
215
+ if (!opts.runMode) throw new Error('materialize requires --run-mode');
216
+ if (!opts.platform) throw new Error('materialize requires --platform');
217
+ const output = resolve(opts.output);
218
+ if (existsSync(output)) {
219
+ throw new Error(`Execution-template destination already exists: ${output}`);
220
+ }
221
+ if (opts.provenance && existsSync(resolve(opts.provenance))) {
222
+ throw new Error(
223
+ `Execution-template provenance destination already exists: ${resolve(opts.provenance)}`,
224
+ );
225
+ }
226
+ const selected = runtime.selectExecutionTemplate({
227
+ sources: buildSources(opts, runtime),
228
+ flow: opts.flow,
229
+ platform: opts.platform,
230
+ runMode: opts.runMode,
231
+ ...(opts.domain ? { domain: opts.domain } : {}),
232
+ ...(opts.id ? { explicitId: opts.id } : {}),
233
+ });
234
+ const materialized = runtime.materializeExecutionTemplate(selected.entry, output);
235
+ const provenance = {
236
+ schemaVersion: 1,
237
+ selectionReason: selected.reason,
238
+ executionTemplate: materialized.reference,
239
+ };
240
+ if (opts.provenance) {
241
+ const destination = resolve(opts.provenance);
242
+ mkdirSync(dirname(destination), { recursive: true });
243
+ writeFileSync(destination, `${JSON.stringify(provenance, null, 2)}\n`, {
244
+ encoding: 'utf8',
245
+ flag: 'wx',
246
+ });
247
+ }
248
+ const result = { ...materialized, provenance };
249
+ if (opts.json) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
250
+ else {
251
+ process.stdout.write(
252
+ `materialized ${selected.entry.id} from ${selected.entry.sourceId} -> ${materialized.path}\n`,
123
253
  );
124
254
  }
125
255
  }
@@ -192,6 +322,7 @@ async function main() {
192
322
  if (!command || command === '-h' || command === '--help') usage(0);
193
323
  const runtime = await loadRuntime();
194
324
  if (command === 'list') await cmdList(rest, runtime);
325
+ else if (command === 'materialize') await cmdMaterialize(rest, runtime);
195
326
  else if (command === 'lint') await cmdLint(rest, runtime);
196
327
  else if (command === 'new') await cmdNew(rest, runtime);
197
328
  else usage(2);
@@ -6,7 +6,10 @@ const {
6
6
  expandedArtifactsForCommand,
7
7
  resolveWorkerTerminalContract,
8
8
  } = require('./worker-terminal-contract.cjs');
9
- const { parseTaskDirMarkArgs } = require('./checklist-target.cjs');
9
+ const {
10
+ parseTaskDirMarkArgs,
11
+ terminalContractInputForChecklist,
12
+ } = require('./checklist-target.cjs');
10
13
 
11
14
  const START_COMMANDS = new Set(['start']);
12
15
  const TERMINAL_COMMANDS = new Set(['complete', 'no-change', 'blocked']);
@@ -161,8 +164,14 @@ function resolveTerminalPreset(command) {
161
164
  }
162
165
  }
163
166
 
167
+ function terminalContractPath(taskDir, taskPath) {
168
+ const scopedPath = path.join(taskDir, terminalContractInputForChecklist(path.basename(taskPath)));
169
+ if (fs.existsSync(scopedPath)) return scopedPath;
170
+ return path.join(taskDir, 'inputs', 'worker-terminal-contract.json');
171
+ }
172
+
164
173
  function loadTerminalContract(taskDir, taskPath) {
165
- const contractPath = path.join(taskDir, 'inputs', 'worker-terminal-contract.json');
174
+ const contractPath = terminalContractPath(taskDir, taskPath);
166
175
  if (fs.existsSync(contractPath)) {
167
176
  return JSON.parse(fs.readFileSync(contractPath, 'utf8'));
168
177
  }
@@ -290,12 +299,12 @@ function assertChecklistComplete(taskPath, { allowOneUnchecked = false } = {}) {
290
299
  process.exit(1);
291
300
  }
292
301
 
293
- function assertArtifactContract(taskDir, contract, terminalCommand) {
302
+ function assertArtifactContract(taskDir, taskPath, contract, terminalCommand) {
294
303
  if (!fs.existsSync(ARTIFACT_CONTRACT_SCRIPT)) {
295
304
  console.error(`missing artifact contract script: ${ARTIFACT_CONTRACT_SCRIPT}`);
296
305
  process.exit(1);
297
306
  }
298
- const contractPath = path.join(taskDir, 'inputs', 'worker-terminal-contract.json');
307
+ const contractPath = terminalContractPath(taskDir, taskPath);
299
308
  const args = [ARTIFACT_CONTRACT_SCRIPT, taskDir];
300
309
  if (fs.existsSync(contractPath)) {
301
310
  args.push('--contract', contractPath);
@@ -329,7 +338,7 @@ function assertTerminalPackagedEvidence(taskPath, taskDir, terminalCommand) {
329
338
  const contract = loadTerminalContract(taskDir, taskPath);
330
339
  if (contract) {
331
340
  assertRequiredArtifacts(taskDir, terminalCommand, contract);
332
- assertArtifactContract(taskDir, contract, terminalCommand);
341
+ assertArtifactContract(taskDir, taskPath, contract, terminalCommand);
333
342
  return;
334
343
  }
335
344
 
@@ -344,7 +353,7 @@ function assertTerminalPackagedEvidence(taskPath, taskDir, terminalCommand) {
344
353
  }
345
354
 
346
355
  if (terminalCommand === 'complete' || terminalCommand === 'no-change') {
347
- assertArtifactContract(taskDir);
356
+ assertArtifactContract(taskDir, taskPath);
348
357
  }
349
358
  }
350
359
 
@@ -374,6 +383,7 @@ function buildSignalUpdate(signal, terminal, target, timing, events, now, taskPa
374
383
  ...(terminal.command === 'complete' && opts['no-self-review']
375
384
  ? { needsSelfReview: false }
376
385
  : {}),
386
+ ...(opts['skip-learnings'] ? { artifactWaivers: { learnings: true } } : {}),
377
387
  };
378
388
 
379
389
  const contract = terminal ? loadTerminalContract(_taskDir, taskPath) : null;