@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
@@ -0,0 +1,46 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ export function sha256Text(text) {
5
+ return createHash('sha256').update(text).digest('hex');
6
+ }
7
+ export function executionTemplateReference(entry, renderedMarkdown) {
8
+ const declaredVersion = entry.frontmatter?.version;
9
+ return {
10
+ id: entry.id,
11
+ sourceId: entry.sourceId,
12
+ flow: entry.flow,
13
+ ...(entry.runMode ? { runMode: entry.runMode } : {}),
14
+ platforms: entry.platforms,
15
+ labels: entry.labels,
16
+ relativePath: entry.relativePath,
17
+ ...(entry.sourceRevision ? { sourceRevision: entry.sourceRevision } : {}),
18
+ ...(entry.sourceDirty === undefined ? {} : { sourceDirty: entry.sourceDirty }),
19
+ ...(declaredVersion == null || declaredVersion === ''
20
+ ? {}
21
+ : { version: String(declaredVersion) }),
22
+ sha256: entry.sha256,
23
+ ...(renderedMarkdown === undefined ? {} : { renderedSha256: sha256Text(renderedMarkdown) }),
24
+ };
25
+ }
26
+ export function readExecutionTemplateSnapshot(entry) {
27
+ const markdown = readFileSync(entry.path, 'utf8');
28
+ if (sha256Text(markdown) !== entry.sha256) {
29
+ throw new Error(`Execution template "${entry.id}" changed after catalog resolution. Refresh the catalog and select it again.`);
30
+ }
31
+ return { markdown, reference: executionTemplateReference(entry) };
32
+ }
33
+ export function materializeExecutionTemplate(entry, outputPath) {
34
+ const destination = path.resolve(outputPath);
35
+ if (existsSync(destination)) {
36
+ throw new Error(`Execution-template destination already exists: ${destination}`);
37
+ }
38
+ const snapshot = readExecutionTemplateSnapshot(entry);
39
+ mkdirSync(path.dirname(destination), { recursive: true });
40
+ writeFileSync(destination, snapshot.markdown, { encoding: 'utf8', flag: 'wx' });
41
+ return {
42
+ path: destination,
43
+ reference: executionTemplateReference(entry, snapshot.markdown),
44
+ };
45
+ }
46
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/execution-template/snapshot.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { ProjectExecutionTemplatesConfig, UnavailableExecutionTemplateSource } from '@farmslot/protocol';
2
+ import type { ExecutionTemplateSource } from './types.js';
3
+ export type { UnavailableExecutionTemplateSource } from '@farmslot/protocol';
4
+ export interface ResolvedExecutionTemplateSources {
5
+ sources: ExecutionTemplateSource[];
6
+ unavailable: UnavailableExecutionTemplateSource[];
7
+ }
8
+ export interface ResolveConfiguredExecutionTemplateSourcesOptions {
9
+ projectPackRoot: string;
10
+ env?: NodeJS.ProcessEnv;
11
+ }
12
+ export declare function executionTemplateSourceRevision(root: string): string | undefined;
13
+ export declare function executionTemplateSourceDirty(root: string): boolean | undefined;
14
+ /**
15
+ * Resolve optional configured sources only when catalog options are requested.
16
+ * Missing roots are diagnostics, not project-load failures.
17
+ */
18
+ export declare function resolveConfiguredExecutionTemplateSources(config: ProjectExecutionTemplatesConfig | undefined, options: ResolveConfiguredExecutionTemplateSourcesOptions): ResolvedExecutionTemplateSources;
19
+ //# sourceMappingURL=source-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-config.d.ts","sourceRoot":"","sources":["../../src/execution-template/source-config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAEV,+BAA+B,EAC/B,kCAAkC,EACnC,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAE1D,YAAY,EAAE,kCAAkC,EAAE,MAAM,oBAAoB,CAAC;AAE7E,MAAM,WAAW,gCAAgC;IAC/C,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC,WAAW,EAAE,kCAAkC,EAAE,CAAC;CACnD;AAED,MAAM,WAAW,gDAAgD;IAC/D,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB;AAUD,wBAAgB,+BAA+B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAQhF;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAO9E;AAeD;;;GAGG;AACH,wBAAgB,yCAAyC,CACvD,MAAM,EAAE,+BAA+B,GAAG,SAAS,EACnD,OAAO,EAAE,gDAAgD,GACxD,gCAAgC,CAoDlC"}
@@ -0,0 +1,90 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, realpathSync, statSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ function isWithin(base, candidate) {
5
+ const relative = path.relative(base, candidate);
6
+ return (relative === '' ||
7
+ (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative)));
8
+ }
9
+ export function executionTemplateSourceRevision(root) {
10
+ const result = spawnSync('git', ['-C', root, 'rev-parse', 'HEAD'], {
11
+ encoding: 'utf8',
12
+ stdio: ['ignore', 'pipe', 'ignore'],
13
+ });
14
+ if (result.status !== 0)
15
+ return undefined;
16
+ const revision = result.stdout.trim();
17
+ return /^[0-9a-f]{40}$/i.test(revision) ? revision : undefined;
18
+ }
19
+ export function executionTemplateSourceDirty(root) {
20
+ const result = spawnSync('git', ['-C', root, 'status', '--porcelain', '--', '.'], {
21
+ encoding: 'utf8',
22
+ stdio: ['ignore', 'pipe', 'ignore'],
23
+ });
24
+ if (result.status !== 0)
25
+ return undefined;
26
+ return result.stdout.trim().length > 0;
27
+ }
28
+ function configuredRoot(source, options) {
29
+ if (typeof source.root.env === 'string') {
30
+ const value = (options.env ?? process.env)[source.root.env]?.trim();
31
+ if (!value)
32
+ return { reason: 'missing-environment' };
33
+ if (!path.isAbsolute(value))
34
+ return { reason: 'invalid-root' };
35
+ return { path: value };
36
+ }
37
+ return { path: path.resolve(options.projectPackRoot, source.root.projectPath) };
38
+ }
39
+ /**
40
+ * Resolve optional configured sources only when catalog options are requested.
41
+ * Missing roots are diagnostics, not project-load failures.
42
+ */
43
+ export function resolveConfiguredExecutionTemplateSources(config, options) {
44
+ const resolved = [];
45
+ const unavailable = [];
46
+ for (const source of config?.sources ?? []) {
47
+ const rootResult = configuredRoot(source, options);
48
+ if (!rootResult.path) {
49
+ unavailable.push({ id: source.id, reason: rootResult.reason ?? 'invalid-root' });
50
+ continue;
51
+ }
52
+ if (!existsSync(rootResult.path) || !statSync(rootResult.path).isDirectory()) {
53
+ unavailable.push({ id: source.id, reason: 'missing-root' });
54
+ continue;
55
+ }
56
+ const canonicalRoot = realpathSync(rootResult.path);
57
+ if ('projectPath' in source.root &&
58
+ !isWithin(realpathSync(options.projectPackRoot), canonicalRoot)) {
59
+ unavailable.push({ id: source.id, reason: 'invalid-root' });
60
+ continue;
61
+ }
62
+ const scanPath = path.resolve(canonicalRoot, source.subpath ?? '.');
63
+ if (!isWithin(canonicalRoot, scanPath)) {
64
+ unavailable.push({ id: source.id, reason: 'invalid-root' });
65
+ continue;
66
+ }
67
+ if (!existsSync(scanPath) || !statSync(scanPath).isDirectory()) {
68
+ unavailable.push({ id: source.id, reason: 'missing-root' });
69
+ continue;
70
+ }
71
+ const canonicalScanPath = realpathSync(scanPath);
72
+ if (!isWithin(canonicalRoot, canonicalScanPath)) {
73
+ unavailable.push({ id: source.id, reason: 'invalid-root' });
74
+ continue;
75
+ }
76
+ const sourceRevision = executionTemplateSourceRevision(canonicalScanPath);
77
+ const sourceDirty = executionTemplateSourceDirty(canonicalScanPath);
78
+ resolved.push({
79
+ id: source.id,
80
+ kind: source.kind,
81
+ root: canonicalScanPath,
82
+ layout: 'flow-tree',
83
+ ...(source.domains ? { domains: source.domains } : {}),
84
+ ...(sourceRevision ? { sourceRevision } : {}),
85
+ ...(sourceDirty === undefined ? {} : { sourceDirty }),
86
+ });
87
+ }
88
+ return { sources: resolved, unavailable };
89
+ }
90
+ //# sourceMappingURL=source-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-config.js","sourceRoot":"","sources":["../../src/execution-template/source-config.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  /** Shared Markdown execution-template catalog (ADR-049). */
2
+ import type { ExecutionTemplateDefault, ExecutionTemplateReference, ExecutionTemplateRunMode } from '@farmslot/protocol';
2
3
  export type ExecutionTemplateSourceKind = 'custom' | 'project' | 'workspace' | 'user' | 'package' | 'fallback';
3
4
  export type ExecutionTemplateLayout = 'flow-tree' | 'worker-flat';
4
- export type ExecutionRunMode = 'autonomous' | 'interactive' | 'validation';
5
+ export type ExecutionRunMode = ExecutionTemplateRunMode;
5
6
  export interface ExecutionTemplateSource {
6
7
  /** Stable source label for catalogs / shadowing (e.g. project:farmslot-farm). */
7
8
  id: string;
@@ -9,10 +10,17 @@ export interface ExecutionTemplateSource {
9
10
  /** Absolute directory to scan. */
10
11
  root: string;
11
12
  layout: ExecutionTemplateLayout;
13
+ /** Exact domains supplied by source configuration. */
14
+ domains?: string[];
15
+ /** Git revision of the source root when available. */
16
+ sourceRevision?: string;
17
+ /** True when the source checkout had uncommitted changes. */
18
+ sourceDirty?: boolean;
12
19
  }
13
20
  export interface ExecutionTemplateFrontmatter {
14
21
  id?: string;
15
22
  title?: string;
23
+ description?: string;
16
24
  flow?: string;
17
25
  version?: string | number;
18
26
  runMode?: ExecutionRunMode;
@@ -23,6 +31,7 @@ export interface ExecutionTemplateFrontmatter {
23
31
  export interface ExecutionTemplateEntry {
24
32
  id: string;
25
33
  title: string;
34
+ description?: string;
26
35
  flow: string;
27
36
  version: string;
28
37
  runMode: ExecutionRunMode | null;
@@ -32,16 +41,43 @@ export interface ExecutionTemplateEntry {
32
41
  relativePath: string;
33
42
  sourceId: string;
34
43
  sourceKind: ExecutionTemplateSourceKind;
44
+ /** SHA-256 of the source Markdown before rendering. */
45
+ sha256: string;
46
+ sourceRevision?: string;
47
+ sourceDirty?: boolean;
35
48
  /** Present when a higher-precedence source already claimed this id. */
36
49
  shadowedBy?: string;
37
50
  frontmatter: ExecutionTemplateFrontmatter | null;
38
51
  heading: string | null;
39
52
  }
53
+ export interface SelectExecutionTemplateOptions {
54
+ sources: ExecutionTemplateSource[];
55
+ flow: string;
56
+ platform: string;
57
+ runMode: ExecutionRunMode;
58
+ domain?: string;
59
+ explicitId?: string;
60
+ defaults?: ExecutionTemplateDefault[];
61
+ }
62
+ export type ListCompatibleExecutionTemplatesOptions = Omit<SelectExecutionTemplateOptions, 'explicitId' | 'defaults'>;
63
+ export type ExecutionTemplateSelectionReason = 'explicit' | 'configured-default' | 'single-domain-candidate' | 'single-general-candidate';
64
+ export interface SelectedExecutionTemplate {
65
+ entry: ExecutionTemplateEntry;
66
+ reason: ExecutionTemplateSelectionReason;
67
+ reference: ExecutionTemplateReference;
68
+ }
69
+ export type ExecutionTemplateSelectionErrorCode = 'ambiguous' | 'missing-or-incompatible' | 'no-compatible-template';
70
+ export declare class ExecutionTemplateSelectionError extends Error {
71
+ readonly code: ExecutionTemplateSelectionErrorCode;
72
+ constructor(code: ExecutionTemplateSelectionErrorCode, message: string);
73
+ }
40
74
  export interface ListExecutionTemplatesOptions {
41
75
  sources: ExecutionTemplateSource[];
42
76
  flow?: string;
43
77
  runMode?: ExecutionRunMode;
44
78
  platform?: string;
79
+ /** Keep general templates plus templates for this exact domain. */
80
+ domain?: string;
45
81
  /** Include shadowed duplicates (default true for list diagnostics). */
46
82
  includeShadowed?: boolean;
47
83
  }
@@ -61,6 +97,7 @@ export interface CreateExecutionTemplateOptions {
61
97
  runMode?: ExecutionRunMode;
62
98
  platforms?: string[];
63
99
  title?: string;
100
+ description?: string;
64
101
  force?: boolean;
65
102
  }
66
103
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/execution-template/types.ts"],"names":[],"mappings":"AAAA,4DAA4D;AAE5D,MAAM,MAAM,2BAA2B,GACnC,QAAQ,GACR,SAAS,GACT,WAAW,GACX,MAAM,GACN,SAAS,GACT,UAAU,CAAC;AAEf,MAAM,MAAM,uBAAuB,GAAG,WAAW,GAAG,aAAa,CAAC;AAElE,MAAM,MAAM,gBAAgB,GAAG,YAAY,GAAG,aAAa,GAAG,YAAY,CAAC;AAE3E,MAAM,WAAW,uBAAuB;IACtC,iFAAiF;IACjF,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,2BAA2B,CAAC;IAClC,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,uBAAuB,CAAC;CACjC;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,2BAA2B,CAAC;IACxC,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,4BAA4B,GAAG,IAAI,CAAC;IACjD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,6BAA6B;IAC5C,OAAO,EAAE,uBAAuB,EAAE,CAAC;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/execution-template/types.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,8 @@
1
- /** Shared Markdown execution-template catalog (ADR-049). */
2
- export {};
1
+ export class ExecutionTemplateSelectionError extends Error {
2
+ constructor(code, message) {
3
+ super(message);
4
+ this.code = code;
5
+ this.name = 'ExecutionTemplateSelectionError';
6
+ }
7
+ }
3
8
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/execution-template/types.ts"],"names":[],"mappings":"AAAA,4DAA4D"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/execution-template/types.ts"],"names":[],"mappings":"AAqGA,MAAM,OAAO,+BAAgC,SAAQ,KAAK;IACxD,YACkB,IAAyC,EACzD,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,SAAI,GAAJ,IAAI,CAAqC;QAIzD,IAAI,CAAC,IAAI,GAAG,iCAAiC,CAAC;IAChD,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farmslot/agent-runtime",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,7 +16,8 @@
16
16
  "./scripts/mark-checklist-step.cjs": "./scripts/mark-checklist-step.cjs",
17
17
  "./scripts/checklist-target.cjs": "./scripts/checklist-target.cjs",
18
18
  "./scripts/worker-terminal-contract.cjs": "./scripts/worker-terminal-contract.cjs",
19
- "./scripts/check-task-artifact-contract.mjs": "./scripts/check-task-artifact-contract.mjs"
19
+ "./scripts/check-task-artifact-contract.mjs": "./scripts/check-task-artifact-contract.mjs",
20
+ "./scripts/execution-template-cli.mjs": "./scripts/execution-template-cli.mjs"
20
21
  },
21
22
  "scripts": {
22
23
  "typecheck": "yarn workspace @farmslot/protocol build && tsc --noEmit --project tsconfig.json",
@@ -31,7 +32,7 @@
31
32
  "prepublishOnly": "node ../../scripts/quality/check-farmslot-package-readiness.mjs --publish --packages @farmslot/agent-runtime"
32
33
  },
33
34
  "dependencies": {
34
- "@farmslot/protocol": "0.12.0"
35
+ "@farmslot/protocol": "0.15.0"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/node": "^22.0.0",
@@ -166,6 +166,12 @@ function isStringArray(value) {
166
166
  return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
167
167
  }
168
168
 
169
+ function isNonEmptyStringArray(value) {
170
+ return (
171
+ isStringArray(value) && value.length > 0 && value.every((entry) => entry.trim().length > 0)
172
+ );
173
+ }
174
+
169
175
  function isRecipeQualityArtifactFallback(value) {
170
176
  if (!isRecord(value)) return false;
171
177
  if (value.version !== 1) return false;
@@ -289,8 +295,9 @@ function addRef(refs, value) {
289
295
  }
290
296
 
291
297
  function validateRecipeQualityArtifact() {
292
- const text = readText('artifacts/recipe-quality.json');
293
- if (!text) return;
298
+ const relPath = 'artifacts/recipe-quality.json';
299
+ if (!fileExists(relPath)) return;
300
+ const text = readText(relPath) ?? '';
294
301
  let artifact;
295
302
  try {
296
303
  artifact = JSON.parse(text);
@@ -325,6 +332,7 @@ function validateRecipeDocumentArtifact() {
325
332
  `${packageRoot}/artifact-manifest.json`,
326
333
  'recipe-run/artifact-manifest.json',
327
334
  );
335
+ const summary = readJsonArtifact(`${packageRoot}/summary.json`, 'recipe-run/summary.json');
328
336
  const trace = readJsonArtifact(`${packageRoot}/trace.json`, 'recipe-run/trace.json');
329
337
  const recipeResolution = readJsonArtifact(
330
338
  `${packageRoot}/recipe-resolution.json`,
@@ -349,6 +357,7 @@ function validateRecipeDocumentArtifact() {
349
357
  const result = sharedRecipeArtifactPackageValidator({
350
358
  recipe,
351
359
  manifest,
360
+ summary,
352
361
  trace,
353
362
  recipeResolution,
354
363
  resolvedRecipes,
@@ -379,8 +388,8 @@ function resolvedRecipeArtifactPath(digest) {
379
388
  }
380
389
 
381
390
  function readJsonArtifact(relPath, label) {
382
- const text = readText(relPath);
383
- if (!text) return undefined;
391
+ if (!fileExists(relPath)) return undefined;
392
+ const text = readText(relPath) ?? '';
384
393
  try {
385
394
  return JSON.parse(text);
386
395
  } catch (error) {
@@ -459,8 +468,9 @@ function listArtifactPaths(root) {
459
468
  }
460
469
 
461
470
  function parseManifest() {
462
- const text = readText('artifacts/evidence-manifest.json');
463
- if (!text) return null;
471
+ const relPath = 'artifacts/evidence-manifest.json';
472
+ if (!fileExists(relPath)) return null;
473
+ const text = readText(relPath) ?? '';
464
474
  let manifest;
465
475
  try {
466
476
  manifest = JSON.parse(text);
@@ -570,32 +580,97 @@ function parseManifest() {
570
580
  return { manifest, refs };
571
581
  }
572
582
 
583
+ let terminalContract;
584
+ if (contractPath && terminalCommand && existsSync(contractPath)) {
585
+ try {
586
+ terminalContract = JSON.parse(readFileSync(contractPath, 'utf8'));
587
+ } catch (error) {
588
+ issues.push(`${contractPath}: invalid worker terminal contract JSON (${error.message})`);
589
+ }
590
+ }
591
+ const isSelfReviewTerminal = terminalContract?.flowType === 'self-review';
573
592
  const hasRecipe = fileExists('artifacts/recipe.json');
574
- validateRecipeDocumentArtifact();
575
- if (
576
- hasRecipe &&
577
- flags.has('--require-recipe-quality-if-recipe') &&
578
- !fileExists('artifacts/recipe-quality.json')
579
- ) {
580
- issues.push('recipe.json exists but artifacts/recipe-quality.json is missing');
581
- }
582
- if (
583
- hasRecipe &&
584
- flags.has('--require-recipe-coverage-if-recipe') &&
585
- !fileExists('artifacts/recipe-coverage.md')
586
- ) {
587
- issues.push('recipe.json exists but artifacts/recipe-coverage.md is missing');
588
- }
589
- validateRecipeQualityArtifact();
590
-
591
- const parsed = parseManifest();
592
- const coverage = readText('artifacts/recipe-coverage.md');
593
- if (coverage && /\|\s*(visual|mixed)\s*\|/i.test(coverage)) {
594
- const refCount = parsed?.refs?.size ?? 0;
595
- if (refCount === 0)
596
- issues.push(
597
- 'recipe-coverage.md declares visual/mixed proof but evidence-manifest has no media references',
598
- );
593
+
594
+ // A reviewer must be able to report ISSUES when worker-owned proof is invalid.
595
+ // Its terminal gate validates only the reviewer-owned feedback artifact below;
596
+ // contribution and fix flows still validate the complete proof package.
597
+ if (!isSelfReviewTerminal) {
598
+ const hasRecipeDecision = fileExists('artifacts/recipe-decision.json');
599
+ const recipeDecisionText = readText('artifacts/recipe-decision.json');
600
+ if (hasRecipeDecision) {
601
+ try {
602
+ const decision = JSON.parse(recipeDecisionText);
603
+ if (!isRecord(decision)) {
604
+ issues.push('artifacts/recipe-decision.json: expected object');
605
+ } else {
606
+ if (decision.version !== 1) {
607
+ issues.push('artifacts/recipe-decision.json.version: expected 1');
608
+ }
609
+ if (typeof decision.required !== 'boolean') {
610
+ issues.push('artifacts/recipe-decision.json.required: expected boolean');
611
+ }
612
+ if (typeof decision.reason !== 'string' || !decision.reason.trim()) {
613
+ issues.push('artifacts/recipe-decision.json.reason: expected non-empty string');
614
+ }
615
+ if (!isNonEmptyStringArray(decision.actionsChecked)) {
616
+ issues.push(
617
+ 'artifacts/recipe-decision.json.actionsChecked: expected non-empty string array',
618
+ );
619
+ }
620
+ if (!isNonEmptyStringArray(decision.claims)) {
621
+ issues.push('artifacts/recipe-decision.json.claims: expected non-empty string array');
622
+ }
623
+ if (decision.recipePath !== undefined && decision.recipePath !== 'artifacts/recipe.json') {
624
+ issues.push(
625
+ 'artifacts/recipe-decision.json.recipePath: expected artifacts/recipe.json when set',
626
+ );
627
+ }
628
+ if (decision.required === true && !hasRecipe) {
629
+ issues.push(
630
+ 'artifacts/recipe-decision.json requires a recipe but artifacts/recipe.json is missing',
631
+ );
632
+ }
633
+ if (decision.required === false && hasRecipe) {
634
+ issues.push(
635
+ 'artifacts/recipe-decision.json says recipe is not required but artifacts/recipe.json exists',
636
+ );
637
+ }
638
+ if (decision.required === true && decision.recipePath !== 'artifacts/recipe.json') {
639
+ issues.push(
640
+ 'artifacts/recipe-decision.json.recipePath: required recipes must declare artifacts/recipe.json',
641
+ );
642
+ }
643
+ }
644
+ } catch (error) {
645
+ issues.push(`artifacts/recipe-decision.json: invalid JSON (${error.message})`);
646
+ }
647
+ }
648
+ validateRecipeDocumentArtifact();
649
+ if (
650
+ hasRecipe &&
651
+ flags.has('--require-recipe-quality-if-recipe') &&
652
+ !fileExists('artifacts/recipe-quality.json')
653
+ ) {
654
+ issues.push('recipe.json exists but artifacts/recipe-quality.json is missing');
655
+ }
656
+ if (
657
+ hasRecipe &&
658
+ flags.has('--require-recipe-coverage-if-recipe') &&
659
+ !fileExists('artifacts/recipe-coverage.md')
660
+ ) {
661
+ issues.push('recipe.json exists but artifacts/recipe-coverage.md is missing');
662
+ }
663
+ validateRecipeQualityArtifact();
664
+
665
+ const parsed = parseManifest();
666
+ const coverage = readText('artifacts/recipe-coverage.md');
667
+ if (coverage && /\|\s*(visual|mixed)\s*\|/i.test(coverage)) {
668
+ const refCount = parsed?.refs?.size ?? 0;
669
+ if (refCount === 0)
670
+ issues.push(
671
+ 'recipe-coverage.md declares visual/mixed proof but evidence-manifest has no media references',
672
+ );
673
+ }
599
674
  }
600
675
 
601
676
  if (flags.has('--require-learnings')) {
@@ -605,8 +680,12 @@ if (flags.has('--require-learnings')) {
605
680
  }
606
681
  }
607
682
 
608
- if (contractPath && existsSync(contractPath) && terminalCommand) {
609
- const contract = JSON.parse(readFileSync(contractPath, 'utf8'));
683
+ if (contractPath && terminalCommand && !existsSync(contractPath)) {
684
+ issues.push(`${contractPath}: explicit worker terminal contract missing`);
685
+ } else if (terminalContract && terminalCommand) {
686
+ const contract = isSelfReviewTerminal
687
+ ? { ...terminalContract, whenPresent: [] }
688
+ : terminalContract;
610
689
  const expanded = expandedArtifactsForCommand(contract, terminalCommand, fileExists);
611
690
  for (const artifactPath of expanded.artifacts) {
612
691
  if (flags.has('--skip-learnings') && artifactPath === 'artifacts/learnings.md') continue;
@@ -12,6 +12,7 @@ const ROLE_SIGNAL_SUFFIX = '-SIGNAL.json';
12
12
  const SELF_REVIEW_CHECKLIST = 'SELF-REVIEW.md';
13
13
  const SELF_REVIEW_FIX_CHECKLIST = 'SELF-REVIEW-FIX.md';
14
14
  const CI_FIX_CHECKLIST = 'CI-FIX.md';
15
+ const WORKER_TERMINAL_CONTRACT_INPUT = path.join('inputs', 'worker-terminal-contract.json');
15
16
 
16
17
  function signalFileForChecklist(checklistBasename) {
17
18
  if (
@@ -36,6 +37,17 @@ function targetForChecklistBasename(checklistBasename) {
36
37
  };
37
38
  }
38
39
 
40
+ function terminalContractInputForChecklist(checklistBasename) {
41
+ if (
42
+ checklistBasename === TASK_PROGRESS_MARKDOWN ||
43
+ checklistBasename === INTERACTIVE_CHECKLIST_MARKDOWN
44
+ ) {
45
+ return WORKER_TERMINAL_CONTRACT_INPUT;
46
+ }
47
+ const base = checklistBasename.replace(/\.md$/i, '');
48
+ return path.join('inputs', `worker-terminal-contract.${base}.json`);
49
+ }
50
+
39
51
  function defaultWorkerTarget(taskDir) {
40
52
  const checklist = fs.existsSync(path.join(taskDir, INTERACTIVE_CHECKLIST_MARKDOWN))
41
53
  ? INTERACTIVE_CHECKLIST_MARKDOWN
@@ -201,6 +213,7 @@ module.exports = {
201
213
  taskDirRelPath,
202
214
  signalFileForChecklist,
203
215
  targetForChecklistBasename,
216
+ terminalContractInputForChecklist,
204
217
  defaultWorkerTarget,
205
218
  manifestModeTeachingError,
206
219
  readManifest,