@mainahq/core 0.5.0 → 0.6.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mainahq/core",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Maina core engines — Context, Prompt, and Verify for verification-first development",
@@ -250,4 +250,247 @@ describe("createCloudClient", () => {
250
250
  expect(result.error).toContain("Network unreachable");
251
251
  }
252
252
  });
253
+
254
+ // ── submitVerify ─────────────────────────────────────────────────────────
255
+
256
+ test("submitVerify sends diff, repo, and base_branch", async () => {
257
+ mockFetch.mockImplementation(() =>
258
+ Promise.resolve(jsonResponse({ data: { jobId: "job-abc-123" } })),
259
+ );
260
+
261
+ const client = setupClient();
262
+ const result = await client.submitVerify({
263
+ diff: "--- a/file.ts\n+++ b/file.ts\n@@ -1 +1 @@\n-old\n+new",
264
+ repo: "acme/app",
265
+ baseBranch: "main",
266
+ });
267
+
268
+ expect(result.ok).toBe(true);
269
+ if (result.ok) {
270
+ expect(result.value.jobId).toBe("job-abc-123");
271
+ }
272
+
273
+ const call = mockFetch.mock.calls[0] as unknown[];
274
+ const url = call[0] as string;
275
+ const requestInit = call[1] as RequestInit;
276
+ expect(url).toBe("https://api.test.maina.dev/verify");
277
+ expect(requestInit.method).toBe("POST");
278
+ const body = JSON.parse(requestInit.body as string);
279
+ expect(body.diff).toContain("+new");
280
+ expect(body.repo).toBe("acme/app");
281
+ expect(body.base_branch).toBe("main");
282
+ });
283
+
284
+ test("submitVerify omits base_branch when not provided", async () => {
285
+ mockFetch.mockImplementation(() =>
286
+ Promise.resolve(jsonResponse({ data: { jobId: "job-def-456" } })),
287
+ );
288
+
289
+ const client = setupClient();
290
+ await client.submitVerify({
291
+ diff: "some diff",
292
+ repo: "acme/app",
293
+ });
294
+
295
+ const call = mockFetch.mock.calls[0] as unknown[];
296
+ const requestInit = call[1] as RequestInit;
297
+ const body = JSON.parse(requestInit.body as string);
298
+ expect(body.base_branch).toBeUndefined();
299
+ });
300
+
301
+ test("submitVerify returns error on 401", async () => {
302
+ mockFetch.mockImplementation(() =>
303
+ Promise.resolve(jsonResponse({ error: "Unauthorized" }, 401)),
304
+ );
305
+
306
+ const client = setupClient();
307
+ const result = await client.submitVerify({
308
+ diff: "diff",
309
+ repo: "acme/app",
310
+ });
311
+
312
+ expect(result.ok).toBe(false);
313
+ if (!result.ok) {
314
+ expect(result.error).toBe("Unauthorized");
315
+ }
316
+ });
317
+
318
+ // ── getVerifyStatus ──────────────────────────────────────────────────────
319
+
320
+ test("getVerifyStatus returns queued status", async () => {
321
+ mockFetch.mockImplementation(() =>
322
+ Promise.resolve(
323
+ jsonResponse({
324
+ data: { status: "queued", currentStep: "Waiting in queue" },
325
+ }),
326
+ ),
327
+ );
328
+
329
+ const client = setupClient();
330
+ const result = await client.getVerifyStatus("job-abc-123");
331
+
332
+ expect(result.ok).toBe(true);
333
+ if (result.ok) {
334
+ expect(result.value.status).toBe("queued");
335
+ expect(result.value.currentStep).toBe("Waiting in queue");
336
+ }
337
+
338
+ const call = mockFetch.mock.calls[0] as unknown[];
339
+ const url = call[0] as string;
340
+ expect(url).toBe("https://api.test.maina.dev/verify/job-abc-123/status");
341
+ });
342
+
343
+ test("getVerifyStatus returns running status", async () => {
344
+ mockFetch.mockImplementation(() =>
345
+ Promise.resolve(
346
+ jsonResponse({
347
+ data: {
348
+ status: "running",
349
+ currentStep: "Running Biome lint",
350
+ },
351
+ }),
352
+ ),
353
+ );
354
+
355
+ const client = setupClient();
356
+ const result = await client.getVerifyStatus("job-abc-123");
357
+
358
+ expect(result.ok).toBe(true);
359
+ if (result.ok) {
360
+ expect(result.value.status).toBe("running");
361
+ expect(result.value.currentStep).toBe("Running Biome lint");
362
+ }
363
+ });
364
+
365
+ test("getVerifyStatus returns error on 404", async () => {
366
+ mockFetch.mockImplementation(() =>
367
+ Promise.resolve(jsonResponse({ error: "Job not found" }, 404)),
368
+ );
369
+
370
+ const client = setupClient();
371
+ const result = await client.getVerifyStatus("nonexistent");
372
+
373
+ expect(result.ok).toBe(false);
374
+ if (!result.ok) {
375
+ expect(result.error).toBe("Job not found");
376
+ }
377
+ });
378
+
379
+ test("getVerifyStatus retries on 500", async () => {
380
+ let attempts = 0;
381
+ mockFetch.mockImplementation(() => {
382
+ attempts++;
383
+ if (attempts === 1) {
384
+ return Promise.resolve(jsonResponse({ error: "Internal error" }, 500));
385
+ }
386
+ return Promise.resolve(
387
+ jsonResponse({
388
+ data: { status: "done", currentStep: "Complete" },
389
+ }),
390
+ );
391
+ });
392
+
393
+ const client = setupClient({ maxRetries: 2 });
394
+ const result = await client.getVerifyStatus("job-abc-123");
395
+
396
+ expect(result.ok).toBe(true);
397
+ expect(attempts).toBe(2);
398
+ });
399
+
400
+ // ── getVerifyResult ──────────────────────────────────────────────────────
401
+
402
+ test("getVerifyResult returns passing result", async () => {
403
+ const verifyResult = {
404
+ id: "job-abc-123",
405
+ status: "done",
406
+ passed: true,
407
+ findings: [],
408
+ findingsErrors: 0,
409
+ findingsWarnings: 0,
410
+ proofKey: "proof-xyz-789",
411
+ durationMs: 4523,
412
+ };
413
+ mockFetch.mockImplementation(() =>
414
+ Promise.resolve(jsonResponse({ data: verifyResult })),
415
+ );
416
+
417
+ const client = setupClient();
418
+ const result = await client.getVerifyResult("job-abc-123");
419
+
420
+ expect(result.ok).toBe(true);
421
+ if (result.ok) {
422
+ expect(result.value.id).toBe("job-abc-123");
423
+ expect(result.value.passed).toBe(true);
424
+ expect(result.value.findings).toHaveLength(0);
425
+ expect(result.value.proofKey).toBe("proof-xyz-789");
426
+ expect(result.value.durationMs).toBe(4523);
427
+ }
428
+
429
+ const call = mockFetch.mock.calls[0] as unknown[];
430
+ const url = call[0] as string;
431
+ expect(url).toBe("https://api.test.maina.dev/verify/job-abc-123");
432
+ });
433
+
434
+ test("getVerifyResult returns failing result with findings", async () => {
435
+ const verifyResult = {
436
+ id: "job-fail-456",
437
+ status: "failed",
438
+ passed: false,
439
+ findings: [
440
+ {
441
+ tool: "biome",
442
+ file: "src/index.ts",
443
+ line: 42,
444
+ message: "Unexpected console.log",
445
+ severity: "error",
446
+ ruleId: "no-console",
447
+ },
448
+ {
449
+ tool: "semgrep",
450
+ file: "src/utils.ts",
451
+ line: 10,
452
+ message: "Potential SQL injection",
453
+ severity: "warning",
454
+ },
455
+ ],
456
+ findingsErrors: 1,
457
+ findingsWarnings: 1,
458
+ proofKey: null,
459
+ durationMs: 3200,
460
+ };
461
+ mockFetch.mockImplementation(() =>
462
+ Promise.resolve(jsonResponse({ data: verifyResult })),
463
+ );
464
+
465
+ const client = setupClient();
466
+ const result = await client.getVerifyResult("job-fail-456");
467
+
468
+ expect(result.ok).toBe(true);
469
+ if (result.ok) {
470
+ expect(result.value.passed).toBe(false);
471
+ expect(result.value.findings).toHaveLength(2);
472
+ expect(result.value.findings[0]?.tool).toBe("biome");
473
+ expect(result.value.findings[0]?.severity).toBe("error");
474
+ expect(result.value.findings[0]?.ruleId).toBe("no-console");
475
+ expect(result.value.findings[1]?.tool).toBe("semgrep");
476
+ expect(result.value.findings[1]?.ruleId).toBeUndefined();
477
+ expect(result.value.findingsErrors).toBe(1);
478
+ expect(result.value.findingsWarnings).toBe(1);
479
+ expect(result.value.proofKey).toBeNull();
480
+ }
481
+ });
482
+
483
+ test("getVerifyResult returns error on 404", async () => {
484
+ mockFetch.mockImplementation(() =>
485
+ Promise.resolve(jsonResponse({ error: "Job not found" }, 404)),
486
+ );
487
+
488
+ const client = setupClient();
489
+ const result = await client.getVerifyResult("nonexistent");
490
+
491
+ expect(result.ok).toBe(false);
492
+ if (!result.ok) {
493
+ expect(result.error).toBe("Job not found");
494
+ }
495
+ });
253
496
  });
package/src/cloud/auth.ts CHANGED
@@ -144,7 +144,7 @@ export async function startDeviceFlow(
144
144
  }
145
145
 
146
146
  const body = (await response.json()) as {
147
- data?: DeviceCodeResponse;
147
+ data?: Record<string, unknown>;
148
148
  error?: string;
149
149
  };
150
150
  if (body.error) {
@@ -153,7 +153,14 @@ export async function startDeviceFlow(
153
153
  if (!body.data) {
154
154
  return err("Invalid response: missing data");
155
155
  }
156
- return ok(body.data);
156
+ const d = body.data;
157
+ return ok({
158
+ userCode: (d.userCode ?? d.user_code) as string,
159
+ deviceCode: (d.deviceCode ?? d.device_code) as string,
160
+ verificationUri: (d.verificationUri ?? d.verification_uri) as string,
161
+ interval: (d.interval ?? 5) as number,
162
+ expiresIn: (d.expiresIn ?? d.expires_in ?? 900) as number,
163
+ });
157
164
  } catch (e) {
158
165
  return err(
159
166
  `Device flow request failed: ${e instanceof Error ? e.message : String(e)}`,
@@ -188,8 +195,8 @@ export async function pollForToken(
188
195
  Accept: "application/json",
189
196
  },
190
197
  body: JSON.stringify({
191
- deviceCode,
192
- grantType: "urn:ietf:params:oauth:grant-type:device_code",
198
+ device_code: deviceCode,
199
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
193
200
  }),
194
201
  });
195
202
 
@@ -204,7 +211,7 @@ export async function pollForToken(
204
211
  }
205
212
 
206
213
  const body = (await response.json()) as {
207
- data?: TokenResponse;
214
+ data?: Record<string, unknown>;
208
215
  error?: string;
209
216
  };
210
217
  if (body.error) {
@@ -217,7 +224,12 @@ export async function pollForToken(
217
224
  if (!body.data) {
218
225
  return err("Invalid token response: missing data");
219
226
  }
220
- return ok(body.data);
227
+ const d = body.data;
228
+ return ok({
229
+ accessToken: (d.accessToken ?? d.access_token) as string,
230
+ refreshToken: (d.refreshToken ?? d.refresh_token) as string | undefined,
231
+ expiresIn: (d.expiresIn ?? d.expires_in ?? 0) as number,
232
+ });
221
233
  } catch (e) {
222
234
  // Network errors during polling are transient — keep trying
223
235
  if (Date.now() >= deadline) {
@@ -11,8 +11,11 @@ import type {
11
11
  CloudConfig,
12
12
  CloudFeedbackPayload,
13
13
  PromptRecord,
14
+ SubmitVerifyPayload,
14
15
  TeamInfo,
15
16
  TeamMember,
17
+ VerifyResultResponse,
18
+ VerifyStatusResponse,
16
19
  } from "./types";
17
20
 
18
21
  // ── Helpers ─────────────────────────────────────────────────────────────────
@@ -70,6 +73,17 @@ export interface CloudClient {
70
73
  postFeedback(
71
74
  payload: CloudFeedbackPayload,
72
75
  ): Promise<Result<{ recorded: boolean }, string>>;
76
+
77
+ /** Submit a diff for cloud verification. */
78
+ submitVerify(
79
+ payload: SubmitVerifyPayload,
80
+ ): Promise<Result<{ jobId: string }, string>>;
81
+
82
+ /** Poll the status of a verification job. */
83
+ getVerifyStatus(jobId: string): Promise<Result<VerifyStatusResponse, string>>;
84
+
85
+ /** Retrieve the full result of a completed verification job. */
86
+ getVerifyResult(jobId: string): Promise<Result<VerifyResultResponse, string>>;
73
87
  }
74
88
 
75
89
  /**
@@ -186,5 +200,51 @@ export function createCloudClient(config: CloudConfig): CloudClient {
186
200
 
187
201
  postFeedback: (payload) =>
188
202
  request<{ recorded: boolean }>("POST", "/feedback", payload),
203
+
204
+ submitVerify: async (payload) => {
205
+ // biome-ignore lint/suspicious/noExplicitAny: snake_case API mapping
206
+ const result = await request<any>("POST", "/verify", {
207
+ diff: payload.diff,
208
+ repo: payload.repo,
209
+ base_branch: payload.baseBranch,
210
+ });
211
+ if (!result.ok) return result;
212
+ const d = result.value;
213
+ return ok({ jobId: d.jobId ?? d.job_id });
214
+ },
215
+
216
+ getVerifyStatus: async (jobId) => {
217
+ // biome-ignore lint/suspicious/noExplicitAny: snake_case API mapping
218
+ const result = await request<any>("GET", `/verify/${jobId}/status`);
219
+ if (!result.ok) return result;
220
+ const d = result.value;
221
+ return ok({
222
+ status: d.status,
223
+ currentStep: d.currentStep ?? d.current_step ?? d.step ?? "",
224
+ });
225
+ },
226
+
227
+ getVerifyResult: async (jobId) => {
228
+ // biome-ignore lint/suspicious/noExplicitAny: snake_case API mapping
229
+ const result = await request<any>("GET", `/verify/${jobId}`);
230
+ if (!result.ok) return result;
231
+ const d = result.value;
232
+ const items = d.findings?.items ?? d.findings ?? [];
233
+ return ok({
234
+ id: d.id,
235
+ status: d.status,
236
+ passed: d.passed,
237
+ findings: items,
238
+ findingsErrors:
239
+ d.findingsErrors ?? d.findings_errors ?? d.findings?.errors ?? 0,
240
+ findingsWarnings:
241
+ d.findingsWarnings ??
242
+ d.findings_warnings ??
243
+ d.findings?.warnings ??
244
+ 0,
245
+ proofKey: d.proofKey ?? d.proof_key ?? d.proof_url ?? null,
246
+ durationMs: d.durationMs ?? d.duration_ms ?? 0,
247
+ });
248
+ },
189
249
  };
190
250
  }
@@ -8,7 +8,7 @@
8
8
  // ── Configuration ───────────────────────────────────────────────────────────
9
9
 
10
10
  export interface CloudConfig {
11
- /** Base URL of the maina cloud API (e.g. "https://api.maina.dev"). */
11
+ /** Base URL of the maina cloud API (e.g. "https://api.mainahq.com"). */
12
12
  baseUrl: string;
13
13
  /** Bearer token for authenticated requests. */
14
14
  token?: string;
@@ -90,6 +90,58 @@ export interface ApiResponse<T> {
90
90
  meta?: Record<string, unknown>;
91
91
  }
92
92
 
93
+ // ── Verify ─────────────────────────────────────────────────────────────────
94
+
95
+ export interface SubmitVerifyPayload {
96
+ /** Diff content to verify. */
97
+ diff: string;
98
+ /** Repository identifier (e.g. "owner/repo"). */
99
+ repo: string;
100
+ /** Base branch to compare against. */
101
+ baseBranch?: string;
102
+ }
103
+
104
+ export interface VerifyStatusResponse {
105
+ /** Current job status. */
106
+ status: "queued" | "running" | "done" | "failed";
107
+ /** Human-readable description of the current step. */
108
+ currentStep: string;
109
+ }
110
+
111
+ export interface VerifyFinding {
112
+ /** Tool that produced the finding (e.g. "biome", "semgrep"). */
113
+ tool: string;
114
+ /** File path relative to repository root. */
115
+ file: string;
116
+ /** Line number in the file. */
117
+ line: number;
118
+ /** Finding description. */
119
+ message: string;
120
+ /** Severity level. */
121
+ severity: "error" | "warning" | "info";
122
+ /** Optional rule identifier. */
123
+ ruleId?: string;
124
+ }
125
+
126
+ export interface VerifyResultResponse {
127
+ /** Job identifier. */
128
+ id: string;
129
+ /** Final job status. */
130
+ status: "done" | "failed";
131
+ /** Whether all checks passed. */
132
+ passed: boolean;
133
+ /** Array of individual findings from verification tools. */
134
+ findings: VerifyFinding[];
135
+ /** Count of error-level findings. */
136
+ findingsErrors: number;
137
+ /** Count of warning-level findings. */
138
+ findingsWarnings: number;
139
+ /** Proof key for passing verification (null when failed). */
140
+ proofKey: string | null;
141
+ /** Total verification duration in milliseconds. */
142
+ durationMs: number;
143
+ }
144
+
93
145
  // ── Feedback ────────────────────────────────────────────────────────────────
94
146
 
95
147
  export interface CloudFeedbackPayload {
package/src/index.ts CHANGED
@@ -66,9 +66,13 @@ export type {
66
66
  CloudFeedbackPayload,
67
67
  DeviceCodeResponse,
68
68
  PromptRecord,
69
+ SubmitVerifyPayload,
69
70
  TeamInfo,
70
71
  TeamMember,
71
72
  TokenResponse,
73
+ VerifyFinding,
74
+ VerifyResultResponse,
75
+ VerifyStatusResponse,
72
76
  } from "./cloud/types";
73
77
  // Config
74
78
  export { getApiKey, isHostMode, shouldDelegateToHost } from "./config/index";
@@ -306,16 +310,12 @@ export {
306
310
  runCoverage,
307
311
  } from "./verify/coverage";
308
312
  export {
309
- type DetectedTool,
310
313
  detectTool,
311
314
  detectTools,
312
315
  isToolAvailable,
313
316
  TOOL_REGISTRY,
314
- type ToolName,
315
317
  } from "./verify/detect";
316
318
  export {
317
- type DiffFilterResult,
318
- type Finding,
319
319
  filterByDiff,
320
320
  filterByDiffWithMap,
321
321
  parseChangedLines,
@@ -343,12 +343,7 @@ export {
343
343
  runMutation,
344
344
  } from "./verify/mutation";
345
345
  // Verify — Pipeline
346
- export {
347
- type PipelineOptions,
348
- type PipelineResult,
349
- runPipeline,
350
- type ToolReport,
351
- } from "./verify/pipeline";
346
+ export { runPipeline } from "./verify/pipeline";
352
347
  // Verify — Proof
353
348
  export {
354
349
  formatVerificationProof,
@@ -376,12 +371,22 @@ export {
376
371
  } from "./verify/sonar";
377
372
  export {
378
373
  parseBiomeOutput,
379
- type SyntaxDiagnostic,
380
- type SyntaxGuardResult,
381
374
  syntaxGuard,
382
375
  } from "./verify/syntax-guard";
383
376
  // Verify — Typecheck + Consistency (built-in checks)
384
377
  export { runTypecheck, type TypecheckResult } from "./verify/typecheck";
378
+ // Verify — Public Type Surface (consolidated for external consumers like maina-cloud)
379
+ export type {
380
+ DetectedTool,
381
+ DiffFilterResult,
382
+ Finding,
383
+ PipelineOptions,
384
+ PipelineResult,
385
+ SyntaxDiagnostic,
386
+ SyntaxGuardResult,
387
+ ToolName,
388
+ ToolReport,
389
+ } from "./verify/types";
385
390
  // Verify — Visual
386
391
  export {
387
392
  captureScreenshot,
@@ -0,0 +1,129 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import type {
3
+ DetectedTool,
4
+ Finding,
5
+ PipelineOptions,
6
+ PipelineResult,
7
+ ToolReport,
8
+ } from "../types";
9
+
10
+ // ─── Verify Type Exports ────────────────────────────────────────────────────
11
+
12
+ describe("verify/types re-exports", () => {
13
+ test("Finding type has expected shape", () => {
14
+ const finding: Finding = {
15
+ tool: "semgrep",
16
+ file: "src/index.ts",
17
+ line: 42,
18
+ message: "unused variable",
19
+ severity: "warning",
20
+ };
21
+ expect(finding.tool).toBe("semgrep");
22
+ expect(finding.file).toBe("src/index.ts");
23
+ expect(finding.line).toBe(42);
24
+ expect(finding.message).toBe("unused variable");
25
+ expect(finding.severity).toBe("warning");
26
+ });
27
+
28
+ test("Finding type supports optional fields", () => {
29
+ const finding: Finding = {
30
+ tool: "eslint",
31
+ file: "app.ts",
32
+ line: 10,
33
+ column: 5,
34
+ message: "no-unused-vars",
35
+ severity: "error",
36
+ ruleId: "no-unused-vars",
37
+ };
38
+ expect(finding.column).toBe(5);
39
+ expect(finding.ruleId).toBe("no-unused-vars");
40
+ });
41
+
42
+ test("ToolReport type has expected shape", () => {
43
+ const report: ToolReport = {
44
+ tool: "trivy",
45
+ findings: [],
46
+ skipped: false,
47
+ duration: 123,
48
+ };
49
+ expect(report.tool).toBe("trivy");
50
+ expect(report.findings).toEqual([]);
51
+ expect(report.skipped).toBe(false);
52
+ expect(report.duration).toBe(123);
53
+ });
54
+
55
+ test("PipelineResult type has expected shape", () => {
56
+ const result: PipelineResult = {
57
+ passed: true,
58
+ syntaxPassed: true,
59
+ tools: [],
60
+ findings: [],
61
+ hiddenCount: 0,
62
+ detectedTools: [],
63
+ duration: 500,
64
+ cacheHits: 2,
65
+ cacheMisses: 1,
66
+ };
67
+ expect(result.passed).toBe(true);
68
+ expect(result.syntaxPassed).toBe(true);
69
+ expect(result.tools).toEqual([]);
70
+ expect(result.findings).toEqual([]);
71
+ expect(result.hiddenCount).toBe(0);
72
+ expect(result.detectedTools).toEqual([]);
73
+ expect(result.duration).toBe(500);
74
+ expect(result.cacheHits).toBe(2);
75
+ expect(result.cacheMisses).toBe(1);
76
+ });
77
+
78
+ test("PipelineOptions type has expected shape", () => {
79
+ const opts: PipelineOptions = {
80
+ files: ["src/app.ts"],
81
+ baseBranch: "main",
82
+ diffOnly: true,
83
+ deep: false,
84
+ cwd: "/tmp",
85
+ mainaDir: ".maina",
86
+ languages: ["typescript"],
87
+ };
88
+ expect(opts.files).toEqual(["src/app.ts"]);
89
+ expect(opts.baseBranch).toBe("main");
90
+ expect(opts.diffOnly).toBe(true);
91
+ expect(opts.deep).toBe(false);
92
+ });
93
+
94
+ test("DetectedTool type has expected shape", () => {
95
+ const tool: DetectedTool = {
96
+ name: "biome",
97
+ command: "biome",
98
+ version: "1.5.0",
99
+ available: true,
100
+ };
101
+ expect(tool.name).toBe("biome");
102
+ expect(tool.command).toBe("biome");
103
+ expect(tool.version).toBe("1.5.0");
104
+ expect(tool.available).toBe(true);
105
+ });
106
+
107
+ test("DetectedTool supports null version", () => {
108
+ const tool: DetectedTool = {
109
+ name: "semgrep",
110
+ command: "semgrep",
111
+ version: null,
112
+ available: false,
113
+ };
114
+ expect(tool.version).toBeNull();
115
+ expect(tool.available).toBe(false);
116
+ });
117
+ });
118
+
119
+ // ─── Verify types re-exported from @mainahq/core index ──────────────────────
120
+
121
+ describe("verify types from core index", () => {
122
+ test("types are importable from core index", async () => {
123
+ const coreIndex = await import("../../index");
124
+ // The module should export these — we verify by checking the module loaded
125
+ // Type-only exports don't appear at runtime, but the module must resolve
126
+ expect(coreIndex).toBeDefined();
127
+ expect(typeof coreIndex.VERSION).toBe("string");
128
+ });
129
+ });
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Verify Engine — Public Type Exports.
3
+ *
4
+ * Consolidated re-exports of all verify types for the public API surface.
5
+ * Consumers (e.g. maina-cloud) import from here or from @mainahq/core.
6
+ * Types are NOT duplicated — each re-exports from its source module.
7
+ */
8
+
9
+ // DetectedTool, ToolName from detect
10
+ export type { DetectedTool, ToolName } from "./detect";
11
+ // Finding + DiffFilterResult from diff-filter
12
+ export type { DiffFilterResult, Finding } from "./diff-filter";
13
+ // PipelineResult, PipelineOptions, ToolReport from pipeline
14
+ export type { PipelineOptions, PipelineResult, ToolReport } from "./pipeline";
15
+
16
+ // SyntaxDiagnostic, SyntaxGuardResult from syntax-guard (used in PipelineResult)
17
+ export type { SyntaxDiagnostic, SyntaxGuardResult } from "./syntax-guard";