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,461 @@
1
+ /**
2
+ * Property-Based Tests for Ghost-API
3
+ *
4
+ * These tests validate the correctness properties defined in the design document.
5
+ * Each property test runs minimum 100 iterations with randomly generated inputs.
6
+ *
7
+ * **Feature: omnibridge, Property 8: Ghost-API Response Conformance**
8
+ * **Validates: Requirements 3.4, 3.6**
9
+ */
10
+
11
+ import { describe, test, expect, beforeEach } from 'vitest';
12
+ import * as fc from 'fast-check';
13
+ import {
14
+ GhostApi,
15
+ createGhostApi,
16
+ inferSchemaFromDocument,
17
+ generateEndpointPath,
18
+ estimateActions,
19
+ } from '../api/ghost-api.js';
20
+ import { createSemanticNormalizer } from '../core/semantic-normalizer.js';
21
+ import { createTriangulationEngine } from '../core/triangulation-engine.js';
22
+ import { createDeterministicLogger } from '../execution/deterministic-logger.js';
23
+ import type { GoalDefinition } from '../core/types.js';
24
+
25
+ // =============================================================================
26
+ // Test Setup
27
+ // =============================================================================
28
+
29
+ const normalizer = createSemanticNormalizer();
30
+ const triangulationEngine = createTriangulationEngine();
31
+ const logger = createDeterministicLogger();
32
+
33
+ let ghostApi: GhostApi;
34
+
35
+ beforeEach(() => {
36
+ logger.clearAll();
37
+ ghostApi = createGhostApi({
38
+ normalizer,
39
+ triangulationEngine,
40
+ logger,
41
+ basePath: '/omni-bridge',
42
+ });
43
+ });
44
+
45
+ // =============================================================================
46
+ // Arbitraries (Test Data Generators)
47
+ // =============================================================================
48
+
49
+ /**
50
+ * Generate valid goal definitions
51
+ */
52
+ const goalDefinitionArb: fc.Arbitrary<GoalDefinition> = fc.record({
53
+ name: fc.stringMatching(/^[A-Z][a-zA-Z0-9_]{2,30}$/),
54
+ targetUrl: fc.webUrl(),
55
+ description: fc.lorem({ minCount: 3, maxCount: 20 }),
56
+ }).map(({ name, targetUrl, description }) => ({
57
+ name,
58
+ targetUrl,
59
+ description,
60
+ }));
61
+
62
+ /**
63
+ * Generate simple HTML documents for testing
64
+ */
65
+ const simpleHtmlArb = fc.record({
66
+ title: fc.lorem({ maxCount: 5 }),
67
+ buttonText: fc.lorem({ maxCount: 3 }),
68
+ inputLabel: fc.lorem({ maxCount: 3 }),
69
+ paragraphText: fc.lorem({ minCount: 5, maxCount: 30 }),
70
+ }).map(({ title, buttonText, inputLabel, paragraphText }) => `
71
+ <!DOCTYPE html>
72
+ <html>
73
+ <head>
74
+ <title>${title}</title>
75
+ </head>
76
+ <body>
77
+ <header>
78
+ <nav>
79
+ <a href="/">Home</a>
80
+ <a href="/about">About</a>
81
+ </nav>
82
+ </header>
83
+ <main>
84
+ <h1>${title}</h1>
85
+ <p>${paragraphText}</p>
86
+ <form action="/submit" method="POST">
87
+ <label for="input1">${inputLabel}</label>
88
+ <input type="text" id="input1" name="input1" required>
89
+ <button type="submit">${buttonText}</button>
90
+ </form>
91
+ </main>
92
+ <footer>
93
+ <p>Footer content</p>
94
+ </footer>
95
+ </body>
96
+ </html>
97
+ `);
98
+
99
+ /**
100
+ * Generate HTML with forms for testing
101
+ */
102
+ const htmlWithFormArb = fc.record({
103
+ formAction: fc.constantFrom('/login', '/signup', '/submit', '/search'),
104
+ formMethod: fc.constantFrom('GET', 'POST'),
105
+ fields: fc.array(
106
+ fc.record({
107
+ name: fc.stringMatching(/^[a-z][a-zA-Z0-9_]{2,15}$/),
108
+ type: fc.constantFrom('text', 'email', 'password', 'number', 'tel'),
109
+ label: fc.lorem({ maxCount: 3 }),
110
+ required: fc.boolean(),
111
+ }),
112
+ { minLength: 1, maxLength: 5 }
113
+ ),
114
+ submitText: fc.lorem({ maxCount: 2 }),
115
+ }).map(({ formAction, formMethod, fields, submitText }) => {
116
+ const fieldHtml = fields
117
+ .map(
118
+ (f) =>
119
+ `<label for="${f.name}">${f.label}</label>
120
+ <input type="${f.type}" id="${f.name}" name="${f.name}" ${f.required ? 'required' : ''}>`
121
+ )
122
+ .join('\n ');
123
+
124
+ return `
125
+ <!DOCTYPE html>
126
+ <html>
127
+ <head><title>Form Page</title></head>
128
+ <body>
129
+ <main>
130
+ <form action="${formAction}" method="${formMethod}">
131
+ ${fieldHtml}
132
+ <button type="submit">${submitText}</button>
133
+ </form>
134
+ </main>
135
+ </body>
136
+ </html>
137
+ `;
138
+ });
139
+
140
+ // =============================================================================
141
+ // Property Tests
142
+ // =============================================================================
143
+
144
+ describe('Ghost-API Property Tests', () => {
145
+ /**
146
+ * **Feature: omnibridge, Property 8: Ghost-API Response Conformance**
147
+ *
148
+ * *For any* Ghost-API response, the response SHALL include:
149
+ * (1) data conforming to the goal's schema,
150
+ * (2) metadata with confidence, executionTimeMs, and actionsPerformed,
151
+ * (3) a verificationHash.
152
+ *
153
+ * **Validates: Requirements 3.4, 3.6**
154
+ */
155
+ test('Property 8: Ghost-API Response Conformance', async () => {
156
+ await fc.assert(
157
+ fc.asyncProperty(goalDefinitionArb, simpleHtmlArb, async (goal, html) => {
158
+ // Create fresh instance for each test
159
+ const testLogger = createDeterministicLogger();
160
+ const testApi = createGhostApi({
161
+ normalizer,
162
+ triangulationEngine,
163
+ logger: testLogger,
164
+ basePath: '/omni-bridge',
165
+ });
166
+
167
+ // Define the goal and get schema mapping
168
+ const mapping = await testApi.defineGoal(goal, html);
169
+
170
+ // Execute the goal
171
+ const response = await testApi.execute(mapping.endpoint, {}, html);
172
+
173
+ // Verify response structure (Requirement 3.4, 3.6)
174
+ // 1. Response must have data property
175
+ expect(response).toHaveProperty('data');
176
+ expect(response.data).toBeDefined();
177
+
178
+ // 2. Response must have metadata with required fields
179
+ expect(response).toHaveProperty('metadata');
180
+ expect(response.metadata).toHaveProperty('confidence');
181
+ expect(response.metadata).toHaveProperty('executionTimeMs');
182
+ expect(response.metadata).toHaveProperty('actionsPerformed');
183
+ expect(response.metadata).toHaveProperty('triangulationHeals');
184
+
185
+ // 3. Metadata values must be valid
186
+ expect(typeof response.metadata.confidence).toBe('number');
187
+ expect(response.metadata.confidence).toBeGreaterThanOrEqual(0);
188
+ expect(response.metadata.confidence).toBeLessThanOrEqual(1);
189
+
190
+ expect(typeof response.metadata.executionTimeMs).toBe('number');
191
+ expect(response.metadata.executionTimeMs).toBeGreaterThanOrEqual(0);
192
+
193
+ expect(typeof response.metadata.actionsPerformed).toBe('number');
194
+ expect(response.metadata.actionsPerformed).toBeGreaterThanOrEqual(0);
195
+
196
+ expect(typeof response.metadata.triangulationHeals).toBe('number');
197
+ expect(response.metadata.triangulationHeals).toBeGreaterThanOrEqual(0);
198
+
199
+ // 4. Response must have verificationHash
200
+ expect(response).toHaveProperty('verificationHash');
201
+ expect(typeof response.verificationHash).toBe('string');
202
+ expect(response.verificationHash.length).toBeGreaterThan(0);
203
+
204
+ return true;
205
+ }),
206
+ { numRuns: 100 }
207
+ );
208
+ });
209
+
210
+ /**
211
+ * Additional property: Schema mapping generates valid endpoints
212
+ */
213
+ test('Schema mapping generates valid endpoint paths', () => {
214
+ fc.assert(
215
+ fc.property(goalDefinitionArb, (goal) => {
216
+ const endpoint = generateEndpointPath(goal, '/omni-bridge');
217
+
218
+ // Endpoint must start with base path
219
+ expect(endpoint.startsWith('/omni-bridge/')).toBe(true);
220
+
221
+ // Endpoint must be a valid URL path (no special characters except - and /)
222
+ expect(endpoint).toMatch(/^\/[a-z0-9\-\/]+$/);
223
+
224
+ // Endpoint must have at least 2 segments (base/domain/goal)
225
+ const segments = endpoint.split('/').filter(Boolean);
226
+ expect(segments.length).toBeGreaterThanOrEqual(2);
227
+
228
+ return true;
229
+ }),
230
+ { numRuns: 100 }
231
+ );
232
+ });
233
+
234
+ /**
235
+ * Additional property: Schema inference produces valid JSON schema
236
+ */
237
+ test('Schema inference produces valid JSON schema', () => {
238
+ fc.assert(
239
+ fc.property(htmlWithFormArb, goalDefinitionArb, (html, goal) => {
240
+ const document = normalizer.normalize(html, goal.targetUrl);
241
+ const schema = inferSchemaFromDocument(document, goal);
242
+
243
+ // Schema must have type property
244
+ expect(schema).toHaveProperty('type');
245
+ expect(schema.type).toBe('object');
246
+
247
+ // Schema must have properties object
248
+ expect(schema).toHaveProperty('properties');
249
+ expect(typeof schema.properties).toBe('object');
250
+
251
+ return true;
252
+ }),
253
+ { numRuns: 100 }
254
+ );
255
+ });
256
+
257
+ /**
258
+ * Additional property: Action estimation is reasonable
259
+ */
260
+ test('Action estimation is reasonable', () => {
261
+ fc.assert(
262
+ fc.property(htmlWithFormArb, goalDefinitionArb, (html, goal) => {
263
+ const document = normalizer.normalize(html, goal.targetUrl);
264
+ const estimate = estimateActions(document, goal);
265
+
266
+ // Estimate must be positive
267
+ expect(estimate).toBeGreaterThan(0);
268
+
269
+ // Estimate must be capped at 100
270
+ expect(estimate).toBeLessThanOrEqual(100);
271
+
272
+ // Estimate should be at least 1 (navigation)
273
+ expect(estimate).toBeGreaterThanOrEqual(1);
274
+
275
+ return true;
276
+ }),
277
+ { numRuns: 100 }
278
+ );
279
+ });
280
+
281
+ /**
282
+ * Additional property: Response data type consistency
283
+ */
284
+ test('Response data is object or array of objects', async () => {
285
+ await fc.assert(
286
+ fc.asyncProperty(goalDefinitionArb, simpleHtmlArb, async (goal, html) => {
287
+ // Create fresh instance for each test
288
+ const testLogger = createDeterministicLogger();
289
+ const testApi = createGhostApi({
290
+ normalizer,
291
+ triangulationEngine,
292
+ logger: testLogger,
293
+ basePath: '/omni-bridge',
294
+ });
295
+
296
+ const mapping = await testApi.defineGoal(goal, html);
297
+ const response = await testApi.execute(mapping.endpoint, {}, html);
298
+
299
+ // Data must be an object or array
300
+ const dataType = typeof response.data;
301
+ expect(dataType).toBe('object');
302
+
303
+ // If array, all items must be objects
304
+ if (Array.isArray(response.data)) {
305
+ for (const item of response.data) {
306
+ expect(typeof item).toBe('object');
307
+ expect(item).not.toBeNull();
308
+ }
309
+ }
310
+
311
+ return true;
312
+ }),
313
+ { numRuns: 100 }
314
+ );
315
+ });
316
+
317
+ /**
318
+ * Additional property: Verification hash is non-empty hex string
319
+ */
320
+ test('Verification hash is non-empty hex string', async () => {
321
+ await fc.assert(
322
+ fc.asyncProperty(goalDefinitionArb, simpleHtmlArb, async (goal, html) => {
323
+ // Create fresh instance for each test
324
+ const testLogger = createDeterministicLogger();
325
+ const testApi = createGhostApi({
326
+ normalizer,
327
+ triangulationEngine,
328
+ logger: testLogger,
329
+ basePath: '/omni-bridge',
330
+ });
331
+
332
+ const mapping = await testApi.defineGoal(goal, html);
333
+ const response = await testApi.execute(mapping.endpoint, {}, html);
334
+
335
+ // Hash must be a non-empty hex string
336
+ expect(response.verificationHash).toMatch(/^[a-f0-9]+$/);
337
+ expect(response.verificationHash.length).toBeGreaterThanOrEqual(32);
338
+
339
+ return true;
340
+ }),
341
+ { numRuns: 50 }
342
+ );
343
+ });
344
+ });
345
+
346
+ // =============================================================================
347
+ // Unit Tests for Edge Cases
348
+ // =============================================================================
349
+
350
+ describe('Ghost-API Unit Tests', () => {
351
+ test('defineGoal creates valid schema mapping', async () => {
352
+ const goal: GoalDefinition = {
353
+ name: 'Fetch_Invoice_Data',
354
+ targetUrl: 'https://example.com/invoices',
355
+ description: 'Fetch invoice data from the portal',
356
+ };
357
+
358
+ const html = `
359
+ <html>
360
+ <body>
361
+ <h1>Invoices</h1>
362
+ <table>
363
+ <tr><td>Invoice #1</td><td>$100</td></tr>
364
+ </table>
365
+ </body>
366
+ </html>
367
+ `;
368
+
369
+ const mapping = await ghostApi.defineGoal(goal, html);
370
+
371
+ expect(mapping.endpoint).toBe('/omni-bridge/example/fetch-invoice-data');
372
+ expect(mapping.schema).toBeDefined();
373
+ expect(mapping.requiredAuth).toBe(true); // 'invoice' triggers auth detection
374
+ expect(mapping.estimatedActions).toBeGreaterThan(0);
375
+ });
376
+
377
+ test('execute returns valid response structure', async () => {
378
+ const goal: GoalDefinition = {
379
+ name: 'Simple_Test',
380
+ targetUrl: 'https://test.com/page',
381
+ description: 'Simple test goal',
382
+ };
383
+
384
+ const html = '<html><body><p>Test content</p></body></html>';
385
+
386
+ const mapping = await ghostApi.defineGoal(goal, html);
387
+ const response = await ghostApi.execute(mapping.endpoint, {}, html);
388
+
389
+ expect(response.data).toBeDefined();
390
+ expect(response.metadata.confidence).toBeGreaterThanOrEqual(0);
391
+ expect(response.metadata.confidence).toBeLessThanOrEqual(1);
392
+ expect(response.metadata.executionTimeMs).toBeGreaterThanOrEqual(0);
393
+ expect(response.metadata.actionsPerformed).toBeGreaterThanOrEqual(0);
394
+ expect(response.verificationHash).toBeTruthy();
395
+ });
396
+
397
+ test('execute throws for unknown endpoint', async () => {
398
+ await expect(
399
+ ghostApi.execute('/unknown/endpoint', {})
400
+ ).rejects.toThrow('Endpoint not found');
401
+ });
402
+
403
+ test('detectPagination identifies pagination elements', () => {
404
+ const html = `
405
+ <html>
406
+ <body>
407
+ <div class="results">
408
+ <p>Result 1</p>
409
+ <p>Result 2</p>
410
+ </div>
411
+ <nav>
412
+ <a href="/page/1">1</a>
413
+ <a href="/page/2">2</a>
414
+ <a href="/page/2">Next »</a>
415
+ </nav>
416
+ </body>
417
+ </html>
418
+ `;
419
+
420
+ const document = normalizer.normalize(html, 'https://example.com');
421
+ const paginationInfo = ghostApi.detectPagination(document);
422
+
423
+ expect(paginationInfo.hasPagination).toBe(true);
424
+ });
425
+
426
+ test('listEndpoints returns registered endpoints', async () => {
427
+ const goal1: GoalDefinition = {
428
+ name: 'Goal_One',
429
+ targetUrl: 'https://example.com/one',
430
+ description: 'First goal',
431
+ };
432
+
433
+ const goal2: GoalDefinition = {
434
+ name: 'Goal_Two',
435
+ targetUrl: 'https://example.com/two',
436
+ description: 'Second goal',
437
+ };
438
+
439
+ await ghostApi.defineGoal(goal1);
440
+ await ghostApi.defineGoal(goal2);
441
+
442
+ const endpoints = ghostApi.listEndpoints();
443
+ expect(endpoints.length).toBe(2);
444
+ expect(endpoints).toContain('/omni-bridge/example/goal-one');
445
+ expect(endpoints).toContain('/omni-bridge/example/goal-two');
446
+ });
447
+
448
+ test('clearGoals removes all registered goals', async () => {
449
+ const goal: GoalDefinition = {
450
+ name: 'Test_Goal',
451
+ targetUrl: 'https://example.com/test',
452
+ description: 'Test goal',
453
+ };
454
+
455
+ await ghostApi.defineGoal(goal);
456
+ expect(ghostApi.getGoalCount()).toBe(1);
457
+
458
+ ghostApi.clearGoals();
459
+ expect(ghostApi.getGoalCount()).toBe(0);
460
+ });
461
+ });