outcome-cli 1.0.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 (113) hide show
  1. package/README.md +261 -0
  2. package/package.json +95 -0
  3. package/src/agents/README.md +139 -0
  4. package/src/agents/adapters/anthropic.adapter.ts +166 -0
  5. package/src/agents/adapters/dalle.adapter.ts +145 -0
  6. package/src/agents/adapters/gemini.adapter.ts +134 -0
  7. package/src/agents/adapters/imagen.adapter.ts +106 -0
  8. package/src/agents/adapters/nano-banana.adapter.ts +129 -0
  9. package/src/agents/adapters/openai.adapter.ts +165 -0
  10. package/src/agents/adapters/veo.adapter.ts +130 -0
  11. package/src/agents/agent.schema.property.test.ts +379 -0
  12. package/src/agents/agent.schema.test.ts +148 -0
  13. package/src/agents/agent.schema.ts +263 -0
  14. package/src/agents/index.ts +60 -0
  15. package/src/agents/registered-agent.schema.ts +356 -0
  16. package/src/agents/registry.ts +97 -0
  17. package/src/agents/tournament-configs.property.test.ts +266 -0
  18. package/src/cli/README.md +145 -0
  19. package/src/cli/commands/define.ts +79 -0
  20. package/src/cli/commands/list.ts +46 -0
  21. package/src/cli/commands/logs.ts +83 -0
  22. package/src/cli/commands/run.ts +416 -0
  23. package/src/cli/commands/verify.ts +110 -0
  24. package/src/cli/index.ts +81 -0
  25. package/src/config/README.md +128 -0
  26. package/src/config/env.ts +262 -0
  27. package/src/config/index.ts +19 -0
  28. package/src/eval/README.md +318 -0
  29. package/src/eval/ai-judge.test.ts +435 -0
  30. package/src/eval/ai-judge.ts +368 -0
  31. package/src/eval/code-validators.ts +414 -0
  32. package/src/eval/evaluateOutcome.property.test.ts +1174 -0
  33. package/src/eval/evaluateOutcome.ts +591 -0
  34. package/src/eval/immigration-validators.ts +122 -0
  35. package/src/eval/index.ts +90 -0
  36. package/src/eval/judge-cache.ts +402 -0
  37. package/src/eval/tournament-validators.property.test.ts +439 -0
  38. package/src/eval/validators.property.test.ts +1118 -0
  39. package/src/eval/validators.ts +1199 -0
  40. package/src/eval/weighted-scorer.ts +285 -0
  41. package/src/index.ts +17 -0
  42. package/src/league/README.md +188 -0
  43. package/src/league/health-check.ts +353 -0
  44. package/src/league/index.ts +93 -0
  45. package/src/league/killAgent.ts +151 -0
  46. package/src/league/league.test.ts +1151 -0
  47. package/src/league/runLeague.ts +843 -0
  48. package/src/league/scoreAgent.ts +175 -0
  49. package/src/modules/omnibridge/__tests__/.gitkeep +1 -0
  50. package/src/modules/omnibridge/__tests__/auth-tunnel.property.test.ts +524 -0
  51. package/src/modules/omnibridge/__tests__/deterministic-logger.property.test.ts +965 -0
  52. package/src/modules/omnibridge/__tests__/ghost-api.property.test.ts +461 -0
  53. package/src/modules/omnibridge/__tests__/omnibridge-integration.test.ts +542 -0
  54. package/src/modules/omnibridge/__tests__/parallel-executor.property.test.ts +671 -0
  55. package/src/modules/omnibridge/__tests__/semantic-normalizer.property.test.ts +521 -0
  56. package/src/modules/omnibridge/__tests__/semantic-normalizer.test.ts +254 -0
  57. package/src/modules/omnibridge/__tests__/session-vault.property.test.ts +367 -0
  58. package/src/modules/omnibridge/__tests__/shadow-session.property.test.ts +523 -0
  59. package/src/modules/omnibridge/__tests__/triangulation-engine.property.test.ts +292 -0
  60. package/src/modules/omnibridge/__tests__/verification-engine.property.test.ts +769 -0
  61. package/src/modules/omnibridge/api/.gitkeep +1 -0
  62. package/src/modules/omnibridge/api/ghost-api.ts +1087 -0
  63. package/src/modules/omnibridge/auth/.gitkeep +1 -0
  64. package/src/modules/omnibridge/auth/auth-tunnel.ts +843 -0
  65. package/src/modules/omnibridge/auth/session-vault.ts +577 -0
  66. package/src/modules/omnibridge/core/.gitkeep +1 -0
  67. package/src/modules/omnibridge/core/semantic-normalizer.ts +702 -0
  68. package/src/modules/omnibridge/core/triangulation-engine.ts +530 -0
  69. package/src/modules/omnibridge/core/types.ts +610 -0
  70. package/src/modules/omnibridge/execution/.gitkeep +1 -0
  71. package/src/modules/omnibridge/execution/deterministic-logger.ts +629 -0
  72. package/src/modules/omnibridge/execution/parallel-executor.ts +542 -0
  73. package/src/modules/omnibridge/execution/shadow-session.ts +794 -0
  74. package/src/modules/omnibridge/index.ts +212 -0
  75. package/src/modules/omnibridge/omnibridge.ts +510 -0
  76. package/src/modules/omnibridge/verification/.gitkeep +1 -0
  77. package/src/modules/omnibridge/verification/verification-engine.ts +783 -0
  78. package/src/outcomes/README.md +75 -0
  79. package/src/outcomes/acquire-pilot-customer.ts +297 -0
  80. package/src/outcomes/code-delivery-outcomes.ts +89 -0
  81. package/src/outcomes/code-outcomes.ts +256 -0
  82. package/src/outcomes/code_review_battle.test.ts +135 -0
  83. package/src/outcomes/code_review_battle.ts +135 -0
  84. package/src/outcomes/cold_email_battle.ts +97 -0
  85. package/src/outcomes/content_creation_battle.ts +160 -0
  86. package/src/outcomes/f1_stem_opt_compliance.ts +61 -0
  87. package/src/outcomes/index.ts +107 -0
  88. package/src/outcomes/lead_gen_battle.test.ts +113 -0
  89. package/src/outcomes/lead_gen_battle.ts +99 -0
  90. package/src/outcomes/outcome.schema.property.test.ts +229 -0
  91. package/src/outcomes/outcome.schema.ts +187 -0
  92. package/src/outcomes/qualified_sales_interest.ts +118 -0
  93. package/src/outcomes/swarm_planner.property.test.ts +370 -0
  94. package/src/outcomes/swarm_planner.ts +96 -0
  95. package/src/outcomes/web_extraction.ts +234 -0
  96. package/src/runtime/README.md +220 -0
  97. package/src/runtime/agentRunner.test.ts +341 -0
  98. package/src/runtime/agentRunner.ts +746 -0
  99. package/src/runtime/claudeAdapter.ts +232 -0
  100. package/src/runtime/costTracker.ts +123 -0
  101. package/src/runtime/index.ts +34 -0
  102. package/src/runtime/modelAdapter.property.test.ts +305 -0
  103. package/src/runtime/modelAdapter.ts +144 -0
  104. package/src/runtime/openaiAdapter.ts +235 -0
  105. package/src/utils/README.md +122 -0
  106. package/src/utils/command-runner.ts +134 -0
  107. package/src/utils/cost-guard.ts +379 -0
  108. package/src/utils/errors.test.ts +290 -0
  109. package/src/utils/errors.ts +442 -0
  110. package/src/utils/index.ts +37 -0
  111. package/src/utils/logger.test.ts +361 -0
  112. package/src/utils/logger.ts +419 -0
  113. package/src/utils/output-parsers.ts +216 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Content Creation Battle Outcome
3
+ *
4
+ * Visual content generation competition where AI agents create
5
+ * images, graphics, and visual assets for marketing campaigns.
6
+ *
7
+ * @module outcomes/content_creation_battle
8
+ */
9
+
10
+ import type { Outcome } from './outcome.schema.js';
11
+
12
+ /**
13
+ * Payout amount for successfully completing a content creation battle.
14
+ */
15
+ export const CONTENT_CREATION_PAYOUT = 50;
16
+
17
+ /**
18
+ * Maximum number of attempts an agent can make.
19
+ */
20
+ export const CONTENT_CREATION_MAX_ATTEMPTS = 3;
21
+
22
+ /**
23
+ * Time limit for creating content (5 minutes).
24
+ */
25
+ export const CONTENT_CREATION_TIME_LIMIT_MS = 300000;
26
+
27
+ /**
28
+ * Content creation brief for the battle
29
+ */
30
+ export interface ContentBrief {
31
+ /** Type of content to create */
32
+ contentType: 'social_media' | 'hero_banner' | 'product_showcase' | 'brand_illustration';
33
+ /** Target audience description */
34
+ audience: string;
35
+ /** Brand or company information */
36
+ brand: {
37
+ name: string;
38
+ industry: string;
39
+ tone: 'professional' | 'casual' | 'playful' | 'luxury' | 'tech';
40
+ colors?: string[];
41
+ };
42
+ /** Specific requirements */
43
+ requirements: {
44
+ dimensions: string; // e.g., "1080x1080", "1920x1080"
45
+ style?: string; // e.g., "minimalist", "vibrant", "corporate"
46
+ mustInclude?: string[]; // Elements that must be present
47
+ mustAvoid?: string[]; // Elements to avoid
48
+ };
49
+ /** Campaign context */
50
+ campaign: {
51
+ objective: string; // e.g., "increase brand awareness", "drive sales"
52
+ message: string; // Key message to communicate
53
+ callToAction?: string; // CTA if applicable
54
+ };
55
+ }
56
+
57
+ /**
58
+ * Content creation artifact produced by an agent
59
+ */
60
+ export interface ContentCreationArtifact {
61
+ /** Generated images */
62
+ images: Array<{
63
+ imageBytes: string; // Base64 encoded
64
+ mimeType: string;
65
+ description: string;
66
+ }>;
67
+ /** Creative brief interpretation */
68
+ concept: {
69
+ title: string;
70
+ description: string;
71
+ rationale: string; // Why this approach works for the brief
72
+ };
73
+ /** Technical specifications */
74
+ specs: {
75
+ dimensions: string;
76
+ style: string;
77
+ colorPalette: string[];
78
+ };
79
+ /** Prompt used for generation */
80
+ prompt: string;
81
+ }
82
+
83
+ /**
84
+ * Default content brief for battles
85
+ */
86
+ export const DEFAULT_CONTENT_BRIEF: ContentBrief = {
87
+ contentType: 'social_media',
88
+ audience: 'Tech-savvy entrepreneurs and startup founders',
89
+ brand: {
90
+ name: 'WAI Championship',
91
+ industry: 'AI/Technology',
92
+ tone: 'tech',
93
+ colors: ['#8B5CF6', '#10B981', '#3B82F6']
94
+ },
95
+ requirements: {
96
+ dimensions: '1080x1080',
97
+ style: 'modern tech aesthetic',
98
+ mustInclude: ['AI agents', 'competition theme'],
99
+ mustAvoid: ['cluttered design', 'outdated graphics']
100
+ },
101
+ campaign: {
102
+ objective: 'increase platform awareness',
103
+ message: 'AI agents compete for real bounties',
104
+ callToAction: 'Join the Championship'
105
+ }
106
+ };
107
+
108
+ /**
109
+ * Content Creation Battle Outcome Definition
110
+ *
111
+ * Success criteria:
112
+ * 1. Images match specified dimensions
113
+ * 2. Content aligns with brand guidelines
114
+ * 3. Includes required elements
115
+ * 4. Avoids prohibited elements
116
+ * 5. Demonstrates creative interpretation of brief
117
+ */
118
+ export const contentCreationBattle: Outcome = {
119
+ name: 'content_creation_battle',
120
+ description: 'Visual Content Creation Battle - Generate compelling marketing visuals that align with brand guidelines and campaign objectives.',
121
+ payoutAmount: CONTENT_CREATION_PAYOUT,
122
+ maxAttempts: CONTENT_CREATION_MAX_ATTEMPTS,
123
+ timeLimitMs: CONTENT_CREATION_TIME_LIMIT_MS,
124
+ successCriteria: [
125
+ {
126
+ name: 'correct_dimensions',
127
+ validator: 'validateImageDimensions',
128
+ params: { expectedDimensions: '1080x1080' }
129
+ },
130
+ {
131
+ name: 'brand_alignment',
132
+ validator: 'validateBrandAlignment',
133
+ params: { brandGuidelines: DEFAULT_CONTENT_BRIEF.brand }
134
+ },
135
+ {
136
+ name: 'required_elements',
137
+ validator: 'validateRequiredElements',
138
+ params: { requiredElements: DEFAULT_CONTENT_BRIEF.requirements.mustInclude }
139
+ },
140
+ {
141
+ name: 'creative_quality',
142
+ validator: 'validateCreativeQuality',
143
+ params: { minQualityScore: 7 }
144
+ },
145
+ {
146
+ name: 'technical_specs',
147
+ validator: 'validateTechnicalSpecs',
148
+ params: { imageFormat: 'png', minResolution: 1080 }
149
+ }
150
+ ],
151
+ failureReasons: [
152
+ 'Images do not match required dimensions',
153
+ 'Content does not align with brand guidelines',
154
+ 'Missing required elements from brief',
155
+ 'Creative quality below acceptable threshold',
156
+ 'Technical specifications not met'
157
+ ]
158
+ };
159
+
160
+ export default contentCreationBattle;
@@ -0,0 +1,61 @@
1
+ import type { Outcome } from './outcome.schema.js';
2
+
3
+ export const f1StemOPTCompliance: Outcome = {
4
+ name: 'f1_stem_opt_compliance',
5
+ description: 'Verify Form I-983 Training Plan meets all USCIS requirements for F1 STEM OPT extension',
6
+ payoutAmount: 199,
7
+ maxAttempts: 3,
8
+ timeLimitMs: 120000,
9
+ successCriteria: [
10
+ {
11
+ name: 'required_student_fields',
12
+ validator: 'validateI983RequiredFields',
13
+ params: {
14
+ requiredFields: ['student_name', 'student_email', 'sevis_id', 'school_name', 'degree_level', 'cip_code']
15
+ }
16
+ },
17
+ {
18
+ name: 'required_employer_fields',
19
+ validator: 'validateI983RequiredFields',
20
+ params: {
21
+ requiredFields: ['employer_name', 'employer_ein', 'employer_everify', 'employer_address']
22
+ }
23
+ },
24
+ {
25
+ name: 'required_training_fields',
26
+ validator: 'validateI983RequiredFields',
27
+ params: {
28
+ requiredFields: ['position_title', 'training_start_date', 'training_end_date', 'training_description']
29
+ }
30
+ },
31
+ {
32
+ name: 'training_start_date_range',
33
+ validator: 'validateOPTDateRange',
34
+ params: { field: 'training_start_date', minDaysFromNow: 0, maxDaysFromNow: 540 }
35
+ },
36
+ {
37
+ name: 'training_end_date_range',
38
+ validator: 'validateOPTDateRange',
39
+ params: { field: 'training_end_date', minDaysFromNow: 30, maxDaysFromNow: 540 }
40
+ },
41
+ {
42
+ name: 'everify_format',
43
+ validator: 'validateEVerifyFormat',
44
+ params: { field: 'employer_everify' }
45
+ },
46
+ {
47
+ name: 'training_description_length',
48
+ validator: 'validateTrainingDescriptionLength',
49
+ params: { field: 'training_description', minWords: 50 }
50
+ }
51
+ ],
52
+ failureReasons: [
53
+ 'Missing required student information',
54
+ 'Missing required employer information',
55
+ 'Missing required training plan details',
56
+ 'OPT dates are outside valid range (must be within 540 days)',
57
+ 'E-Verify number format is invalid (must be 8 digits)',
58
+ 'Training description is too short (minimum 50 words)',
59
+ 'Form fields are incomplete or incorrectly formatted'
60
+ ]
61
+ };
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Outcomes Module Exports
3
+ *
4
+ * Includes both sales outcomes and SWE-bench style code outcomes.
5
+ *
6
+ * @module outcomes
7
+ */
8
+
9
+ // Schema and validation
10
+ export {
11
+ type Outcome,
12
+ type SuccessCriterion,
13
+ type ValidationResult,
14
+ validateOutcome,
15
+ isOutcome,
16
+ } from './outcome.schema.js';
17
+
18
+ // Sales outcomes (original)
19
+ export {
20
+ qualifiedSalesInterest,
21
+ PAYOUT_AMOUNT,
22
+ MAX_ATTEMPTS,
23
+ TIME_LIMIT_MS,
24
+ BUYING_INTENT_KEYWORDS,
25
+ MIN_COMPANY_SIZE,
26
+ EXCLUDED_ROLES,
27
+ MIN_MESSAGE_WORDS,
28
+ } from './qualified_sales_interest.js';
29
+
30
+ // SWE-bench style code outcomes
31
+ export {
32
+ type CodeOutcome,
33
+ CODE_OUTCOMES,
34
+ getCodeOutcomesByLevel,
35
+ getCodeOutcomesByCategory,
36
+ getCodeOutcomesByLanguage,
37
+ } from './code-outcomes.js';
38
+
39
+ // Outcome-based code delivery (feature/refactor/test generation)
40
+ export {
41
+ featureImplementationOutcome,
42
+ refactorTaskOutcome,
43
+ testGenerationOutcome,
44
+ CODE_DELIVERY_OUTCOMES,
45
+ } from './code-delivery-outcomes.js';
46
+
47
+ // Snowdevil Hunter Squad outcome
48
+ export {
49
+ acquirePilotCustomerOutcome,
50
+ PILOT_PAYOUT_AMOUNT,
51
+ PILOT_MAX_ATTEMPTS,
52
+ PILOT_TIME_LIMIT_MS,
53
+ POSITIVE_REPLY_KEYWORDS,
54
+ PAIN_SIGNALS,
55
+ MIN_OUTREACH_LENGTH,
56
+ MAX_OUTREACH_LENGTH,
57
+ validateContactInfo,
58
+ validatePainSignal,
59
+ validateOutreachSent,
60
+ validatePositiveReply,
61
+ } from './acquire-pilot-customer.js';
62
+
63
+ // Tournament Seed Bounties - Code Review Battle
64
+ export {
65
+ codeReviewBattle,
66
+ CODE_REVIEW_PAYOUT,
67
+ CODE_REVIEW_MAX_ATTEMPTS,
68
+ CODE_REVIEW_TIME_LIMIT_MS,
69
+ type CodeReviewIssue,
70
+ type CodeReviewComment,
71
+ type RefactorSuggestion,
72
+ type CodeReviewArtifact,
73
+ } from './code_review_battle.js';
74
+
75
+ // Tournament Seed Bounties - Lead Gen Battle
76
+ export {
77
+ leadGenBattle,
78
+ LEAD_GEN_PAYOUT,
79
+ LEAD_GEN_MAX_ATTEMPTS,
80
+ LEAD_GEN_TIME_LIMIT_MS,
81
+ type LeadGenArtifact,
82
+ } from './lead_gen_battle.js';
83
+
84
+ // Web Extraction Outcomes (OmniBridge integration)
85
+ export {
86
+ type WebExtractionOutcome,
87
+ isWebExtractionOutcome,
88
+ createWebExtractionOutcome,
89
+ WEB_EXTRACTION_OUTCOMES,
90
+ } from './web_extraction.js';
91
+
92
+ // Swarm Planner Outcome (MVP Unified Engine)
93
+ export {
94
+ swarmPlannerOutcome,
95
+ PLANNER_MAX_ATTEMPTS,
96
+ PLANNER_TIME_LIMIT_MS,
97
+ PLANNER_COST_CEILING,
98
+ PLANNER_MIN_TASKS,
99
+ PLANNER_MAX_TASKS,
100
+ PLANNER_PAYOUT_AMOUNT,
101
+ REQUIRED_TASK_FIELDS,
102
+ type PlannerTask,
103
+ type PlannerOutput,
104
+ } from './swarm_planner.js';
105
+
106
+ // F1 STEM OPT Compliance Checker
107
+ export { f1StemOPTCompliance } from './f1_stem_opt_compliance.js';
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Unit tests for Lead Gen Battle Outcome Configuration
3
+ *
4
+ * Validates that the lead gen battle outcome is properly configured
5
+ * with correct payout, attempts, time limits, and schema compliance.
6
+ *
7
+ * @module outcomes/lead_gen_battle.test
8
+ */
9
+
10
+ import { describe, test, expect } from 'vitest';
11
+ import {
12
+ leadGenBattle,
13
+ LEAD_GEN_PAYOUT,
14
+ LEAD_GEN_MAX_ATTEMPTS,
15
+ LEAD_GEN_TIME_LIMIT_MS,
16
+ type LeadGenArtifact,
17
+ } from './lead_gen_battle.js';
18
+ import { validateOutcome } from './outcome.schema.js';
19
+
20
+ describe('Lead Gen Battle Outcome Configuration', () => {
21
+ test('has correct payout amount', () => {
22
+ expect(leadGenBattle.payoutAmount).toBe(1);
23
+ expect(leadGenBattle.payoutAmount).toBe(LEAD_GEN_PAYOUT);
24
+ });
25
+
26
+ test('has correct maximum attempts', () => {
27
+ expect(leadGenBattle.maxAttempts).toBe(10);
28
+ expect(leadGenBattle.maxAttempts).toBe(LEAD_GEN_MAX_ATTEMPTS);
29
+ });
30
+
31
+ test('has correct time limit', () => {
32
+ expect(leadGenBattle.timeLimitMs).toBe(60000); // 1 minute
33
+ expect(leadGenBattle.timeLimitMs).toBe(LEAD_GEN_TIME_LIMIT_MS);
34
+ });
35
+
36
+ test('validates against OutcomeSchema', () => {
37
+ const result = validateOutcome(leadGenBattle);
38
+ expect(result.valid).toBe(true);
39
+ expect(result.errors).toHaveLength(0);
40
+ });
41
+
42
+ test('has correct outcome name', () => {
43
+ expect(leadGenBattle.name).toBe('lead_gen_battle');
44
+ });
45
+
46
+ test('has descriptive description', () => {
47
+ expect(leadGenBattle.description).toContain('Lead Gen Precision Battle');
48
+ expect(leadGenBattle.description).toContain('qualified leads');
49
+ expect(leadGenBattle.description).toContain('valid email');
50
+ expect(leadGenBattle.description).toContain('LinkedIn URL');
51
+ });
52
+
53
+ test('has all required success criteria', () => {
54
+ expect(leadGenBattle.successCriteria).toHaveLength(4);
55
+
56
+ const criteriaNames = leadGenBattle.successCriteria.map(c => c.name);
57
+ expect(criteriaNames).toContain('valid_email');
58
+ expect(criteriaNames).toContain('company_size');
59
+ expect(criteriaNames).toContain('valid_role');
60
+ expect(criteriaNames).toContain('valid_linkedin');
61
+ });
62
+
63
+ test('has correct validator configurations', () => {
64
+ const emailCriterion = leadGenBattle.successCriteria.find(c => c.name === 'valid_email');
65
+ expect(emailCriterion).toBeDefined();
66
+ expect(emailCriterion?.validator).toBe('validateEmail');
67
+ expect(emailCriterion?.params).toEqual({});
68
+
69
+ const companySizeCriterion = leadGenBattle.successCriteria.find(c => c.name === 'company_size');
70
+ expect(companySizeCriterion).toBeDefined();
71
+ expect(companySizeCriterion?.validator).toBe('validateCompanySize');
72
+ expect(companySizeCriterion?.params).toEqual({ minimum: 50 });
73
+
74
+ const roleCriterion = leadGenBattle.successCriteria.find(c => c.name === 'valid_role');
75
+ expect(roleCriterion).toBeDefined();
76
+ expect(roleCriterion?.validator).toBe('validateRole');
77
+ expect(roleCriterion?.params).toEqual({ excludedRoles: ['intern', 'student'] });
78
+
79
+ const linkedInCriterion = leadGenBattle.successCriteria.find(c => c.name === 'valid_linkedin');
80
+ expect(linkedInCriterion).toBeDefined();
81
+ expect(linkedInCriterion?.validator).toBe('validateLinkedIn');
82
+ expect(linkedInCriterion?.params).toEqual({});
83
+ });
84
+
85
+ test('has appropriate failure reasons', () => {
86
+ expect(leadGenBattle.failureReasons).toHaveLength(4);
87
+ expect(leadGenBattle.failureReasons).toContain('Invalid email format');
88
+ expect(leadGenBattle.failureReasons).toContain('Company too small - must have at least 50 employees');
89
+ expect(leadGenBattle.failureReasons).toContain('Invalid role - interns and students are not qualified leads');
90
+ expect(leadGenBattle.failureReasons).toContain('Invalid LinkedIn URL - must start with https://www.linkedin.com/in/');
91
+ });
92
+
93
+ test('LeadGenArtifact interface is properly typed', () => {
94
+ // This is a compile-time test - if it compiles, the interface is correct
95
+ const validArtifact: LeadGenArtifact = {
96
+ email: 'user@example.com',
97
+ companySize: 100,
98
+ role: 'Engineering Manager',
99
+ linkedIn: 'https://www.linkedin.com/in/user'
100
+ };
101
+
102
+ expect(typeof validArtifact.email).toBe('string');
103
+ expect(typeof validArtifact.companySize).toBe('number');
104
+ expect(typeof validArtifact.role).toBe('string');
105
+ expect(typeof validArtifact.linkedIn).toBe('string');
106
+ });
107
+
108
+ test('constants are properly exported', () => {
109
+ expect(LEAD_GEN_PAYOUT).toBe(1);
110
+ expect(LEAD_GEN_MAX_ATTEMPTS).toBe(10);
111
+ expect(LEAD_GEN_TIME_LIMIT_MS).toBe(60000);
112
+ });
113
+ });
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Lead Gen Battle Outcome
3
+ *
4
+ * Tournament Seed bounty for the WAI Championship Q1 Tournament.
5
+ * Agents compete to generate qualified leads with precision validation
6
+ * including valid email, company size, role, and LinkedIn URL.
7
+ *
8
+ * @module outcomes/lead_gen_battle
9
+ * @see Requirements 2.1, 2.2, 2.3, 8.2
10
+ */
11
+
12
+ import type { Outcome } from './outcome.schema.js';
13
+
14
+ /**
15
+ * Payout amount for successfully completing a lead gen battle.
16
+ * Set to $1 as per Requirements 2.1.
17
+ */
18
+ export const LEAD_GEN_PAYOUT = 1;
19
+
20
+ /**
21
+ * Maximum number of attempts an agent can make to achieve this outcome.
22
+ * Set to 10 as per Requirements 2.2.
23
+ */
24
+ export const LEAD_GEN_MAX_ATTEMPTS = 10;
25
+
26
+ /**
27
+ * Time limit for achieving the outcome (1 minute in milliseconds).
28
+ * Set to 60000ms as per Requirements 2.3.
29
+ */
30
+ export const LEAD_GEN_TIME_LIMIT_MS = 60000;
31
+
32
+ /**
33
+ * Represents a lead artifact produced by an agent.
34
+ * Contains all required fields for lead qualification validation.
35
+ */
36
+ export interface LeadGenArtifact {
37
+ /** Email address of the lead (must match standard email regex) */
38
+ email: string;
39
+ /** Company size in number of employees (must be >= 50) */
40
+ companySize: number;
41
+ /** Role/title of the lead (must not be "intern" or "student") */
42
+ role: string;
43
+ /** LinkedIn profile URL (must start with "https://www.linkedin.com/in/") */
44
+ linkedIn: string;
45
+ }
46
+
47
+ /**
48
+ * Lead Gen Battle Outcome Definition
49
+ *
50
+ * Success requires meeting ALL 4 criteria:
51
+ * 1. Valid email format matching /^[^\s@]+@[^\s@]+\.[^\s@]+$/
52
+ * 2. Company size of at least 50 employees
53
+ * 3. Role is not "intern" or "student" (case-insensitive)
54
+ * 4. LinkedIn URL starts with "https://www.linkedin.com/in/"
55
+ *
56
+ * @see Requirements 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8, 8.2
57
+ */
58
+ export const leadGenBattle: Outcome = {
59
+ name: 'lead_gen_battle',
60
+ description:
61
+ 'Lead Gen Precision Battle - Generate qualified leads with valid email, company size >= 50, non-student/intern role, and valid LinkedIn URL.',
62
+ payoutAmount: LEAD_GEN_PAYOUT,
63
+ maxAttempts: LEAD_GEN_MAX_ATTEMPTS,
64
+ timeLimitMs: LEAD_GEN_TIME_LIMIT_MS,
65
+ successCriteria: [
66
+ {
67
+ // Requirement 2.4: Verify email has valid syntax
68
+ name: 'valid_email',
69
+ validator: 'validateEmail',
70
+ params: {},
71
+ },
72
+ {
73
+ // Requirement 2.5: Verify company size is at least 50 employees
74
+ name: 'company_size',
75
+ validator: 'validateCompanySize',
76
+ params: { minimum: 50 },
77
+ },
78
+ {
79
+ // Requirement 2.6: Verify role is not "Student" or "Intern"
80
+ name: 'valid_role',
81
+ validator: 'validateRole',
82
+ params: { excludedRoles: ['intern', 'student'] },
83
+ },
84
+ {
85
+ // Requirement 2.7: Verify LinkedIn URL starts with correct prefix
86
+ name: 'valid_linkedin',
87
+ validator: 'validateLinkedIn',
88
+ params: {},
89
+ },
90
+ ],
91
+ failureReasons: [
92
+ 'Invalid email format',
93
+ 'Company too small - must have at least 50 employees',
94
+ 'Invalid role - interns and students are not qualified leads',
95
+ 'Invalid LinkedIn URL - must start with https://www.linkedin.com/in/',
96
+ ],
97
+ };
98
+
99
+ export default leadGenBattle;