@mainahq/core 0.6.0 → 0.7.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 +1 -1
- package/src/cloud/__tests__/client.test.ts +168 -0
- package/src/cloud/client.ts +54 -0
- package/src/cloud/types.ts +42 -0
- package/src/feedback/__tests__/sync.test.ts +104 -0
- package/src/feedback/sync.ts +54 -0
- package/src/index.ts +5 -0
- package/src/language/__tests__/__fixtures__/detect/composer.lock +1 -0
package/package.json
CHANGED
|
@@ -493,4 +493,172 @@ describe("createCloudClient", () => {
|
|
|
493
493
|
expect(result.error).toBe("Job not found");
|
|
494
494
|
}
|
|
495
495
|
});
|
|
496
|
+
|
|
497
|
+
// ── postFeedbackBatch ─────────────────────────────────────────────────
|
|
498
|
+
|
|
499
|
+
test("postFeedbackBatch sends events in snake_case", async () => {
|
|
500
|
+
mockFetch.mockImplementation(() =>
|
|
501
|
+
Promise.resolve(jsonResponse({ data: { received: 3 } })),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
const client = setupClient();
|
|
505
|
+
const result = await client.postFeedbackBatch([
|
|
506
|
+
{
|
|
507
|
+
promptHash: "hash-1",
|
|
508
|
+
command: "commit",
|
|
509
|
+
accepted: true,
|
|
510
|
+
timestamp: "2026-01-01T00:00:00Z",
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
promptHash: "hash-2",
|
|
514
|
+
command: "review",
|
|
515
|
+
accepted: false,
|
|
516
|
+
context: "user edited",
|
|
517
|
+
diffHash: "diff-abc",
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
promptHash: "hash-3",
|
|
521
|
+
command: "fix",
|
|
522
|
+
accepted: true,
|
|
523
|
+
},
|
|
524
|
+
]);
|
|
525
|
+
|
|
526
|
+
expect(result.ok).toBe(true);
|
|
527
|
+
if (result.ok) {
|
|
528
|
+
expect(result.value.received).toBe(3);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const call = mockFetch.mock.calls[0] as unknown[];
|
|
532
|
+
const url = call[0] as string;
|
|
533
|
+
expect(url).toBe("https://api.test.maina.dev/feedback/batch");
|
|
534
|
+
|
|
535
|
+
const requestInit = call[1] as RequestInit;
|
|
536
|
+
expect(requestInit.method).toBe("POST");
|
|
537
|
+
|
|
538
|
+
const body = JSON.parse(requestInit.body as string);
|
|
539
|
+
expect(body.events).toHaveLength(3);
|
|
540
|
+
// Verify snake_case mapping
|
|
541
|
+
expect(body.events[0].prompt_hash).toBe("hash-1");
|
|
542
|
+
expect(body.events[0].command).toBe("commit");
|
|
543
|
+
expect(body.events[0].accepted).toBe(true);
|
|
544
|
+
expect(body.events[0].timestamp).toBe("2026-01-01T00:00:00Z");
|
|
545
|
+
expect(body.events[1].diff_hash).toBe("diff-abc");
|
|
546
|
+
expect(body.events[1].context).toBe("user edited");
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
test("postFeedbackBatch returns error on failure", async () => {
|
|
550
|
+
mockFetch.mockImplementation(() =>
|
|
551
|
+
Promise.resolve(jsonResponse({ error: "Unauthorized" }, 401)),
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
const client = setupClient();
|
|
555
|
+
const result = await client.postFeedbackBatch([]);
|
|
556
|
+
|
|
557
|
+
expect(result.ok).toBe(false);
|
|
558
|
+
if (!result.ok) {
|
|
559
|
+
expect(result.error).toBe("Unauthorized");
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
// ── getFeedbackImprovements ──────────────────────────────────────────
|
|
564
|
+
|
|
565
|
+
test("getFeedbackImprovements returns improvements with camelCase mapping", async () => {
|
|
566
|
+
mockFetch.mockImplementation(() =>
|
|
567
|
+
Promise.resolve(
|
|
568
|
+
jsonResponse({
|
|
569
|
+
data: {
|
|
570
|
+
improvements: [
|
|
571
|
+
{
|
|
572
|
+
command: "commit",
|
|
573
|
+
prompt_hash: "hash-abc",
|
|
574
|
+
samples: 50,
|
|
575
|
+
accept_rate: 0.85,
|
|
576
|
+
status: "healthy",
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
command: "review",
|
|
580
|
+
prompt_hash: "hash-def",
|
|
581
|
+
samples: 30,
|
|
582
|
+
accept_rate: 0.4,
|
|
583
|
+
status: "needs_improvement",
|
|
584
|
+
},
|
|
585
|
+
],
|
|
586
|
+
team_totals: {
|
|
587
|
+
total_events: 200,
|
|
588
|
+
accept_rate: 0.72,
|
|
589
|
+
},
|
|
590
|
+
},
|
|
591
|
+
}),
|
|
592
|
+
),
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
const client = setupClient();
|
|
596
|
+
const result = await client.getFeedbackImprovements();
|
|
597
|
+
|
|
598
|
+
expect(result.ok).toBe(true);
|
|
599
|
+
if (result.ok) {
|
|
600
|
+
expect(result.value.improvements).toHaveLength(2);
|
|
601
|
+
expect(result.value.improvements[0]?.command).toBe("commit");
|
|
602
|
+
expect(result.value.improvements[0]?.promptHash).toBe("hash-abc");
|
|
603
|
+
expect(result.value.improvements[0]?.samples).toBe(50);
|
|
604
|
+
expect(result.value.improvements[0]?.acceptRate).toBe(0.85);
|
|
605
|
+
expect(result.value.improvements[0]?.status).toBe("healthy");
|
|
606
|
+
expect(result.value.improvements[1]?.status).toBe("needs_improvement");
|
|
607
|
+
expect(result.value.teamTotals.totalEvents).toBe(200);
|
|
608
|
+
expect(result.value.teamTotals.acceptRate).toBe(0.72);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
const call = mockFetch.mock.calls[0] as unknown[];
|
|
612
|
+
const url = call[0] as string;
|
|
613
|
+
expect(url).toBe("https://api.test.maina.dev/feedback/improvements");
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
test("getFeedbackImprovements handles camelCase response", async () => {
|
|
617
|
+
mockFetch.mockImplementation(() =>
|
|
618
|
+
Promise.resolve(
|
|
619
|
+
jsonResponse({
|
|
620
|
+
data: {
|
|
621
|
+
improvements: [
|
|
622
|
+
{
|
|
623
|
+
command: "fix",
|
|
624
|
+
promptHash: "hash-ghi",
|
|
625
|
+
samples: 10,
|
|
626
|
+
acceptRate: 0.95,
|
|
627
|
+
status: "excellent",
|
|
628
|
+
},
|
|
629
|
+
],
|
|
630
|
+
teamTotals: {
|
|
631
|
+
totalEvents: 100,
|
|
632
|
+
acceptRate: 0.9,
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
}),
|
|
636
|
+
),
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
const client = setupClient();
|
|
640
|
+
const result = await client.getFeedbackImprovements();
|
|
641
|
+
|
|
642
|
+
expect(result.ok).toBe(true);
|
|
643
|
+
if (result.ok) {
|
|
644
|
+
expect(result.value.improvements[0]?.promptHash).toBe("hash-ghi");
|
|
645
|
+
expect(result.value.improvements[0]?.acceptRate).toBe(0.95);
|
|
646
|
+
expect(result.value.teamTotals.totalEvents).toBe(100);
|
|
647
|
+
expect(result.value.teamTotals.acceptRate).toBe(0.9);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
test("getFeedbackImprovements returns error on failure", async () => {
|
|
652
|
+
mockFetch.mockImplementation(() =>
|
|
653
|
+
Promise.resolve(jsonResponse({ error: "Forbidden" }, 403)),
|
|
654
|
+
);
|
|
655
|
+
|
|
656
|
+
const client = setupClient();
|
|
657
|
+
const result = await client.getFeedbackImprovements();
|
|
658
|
+
|
|
659
|
+
expect(result.ok).toBe(false);
|
|
660
|
+
if (!result.ok) {
|
|
661
|
+
expect(result.error).toBe("Forbidden");
|
|
662
|
+
}
|
|
663
|
+
});
|
|
496
664
|
});
|
package/src/cloud/client.ts
CHANGED
|
@@ -10,6 +10,9 @@ import type {
|
|
|
10
10
|
ApiResponse,
|
|
11
11
|
CloudConfig,
|
|
12
12
|
CloudFeedbackPayload,
|
|
13
|
+
CloudPromptImprovement,
|
|
14
|
+
FeedbackEvent,
|
|
15
|
+
FeedbackImprovementsResponse,
|
|
13
16
|
PromptRecord,
|
|
14
17
|
SubmitVerifyPayload,
|
|
15
18
|
TeamInfo,
|
|
@@ -74,6 +77,16 @@ export interface CloudClient {
|
|
|
74
77
|
payload: CloudFeedbackPayload,
|
|
75
78
|
): Promise<Result<{ recorded: boolean }, string>>;
|
|
76
79
|
|
|
80
|
+
/** Upload a batch of feedback events to the cloud. */
|
|
81
|
+
postFeedbackBatch(
|
|
82
|
+
events: FeedbackEvent[],
|
|
83
|
+
): Promise<Result<{ received: number }, string>>;
|
|
84
|
+
|
|
85
|
+
/** Fetch feedback-based improvement suggestions from the cloud. */
|
|
86
|
+
getFeedbackImprovements(): Promise<
|
|
87
|
+
Result<FeedbackImprovementsResponse, string>
|
|
88
|
+
>;
|
|
89
|
+
|
|
77
90
|
/** Submit a diff for cloud verification. */
|
|
78
91
|
submitVerify(
|
|
79
92
|
payload: SubmitVerifyPayload,
|
|
@@ -201,6 +214,47 @@ export function createCloudClient(config: CloudConfig): CloudClient {
|
|
|
201
214
|
postFeedback: (payload) =>
|
|
202
215
|
request<{ recorded: boolean }>("POST", "/feedback", payload),
|
|
203
216
|
|
|
217
|
+
postFeedbackBatch: async (events) => {
|
|
218
|
+
// Map camelCase → snake_case for cloud API
|
|
219
|
+
const snakeEvents = events.map((e) => ({
|
|
220
|
+
prompt_hash: e.promptHash,
|
|
221
|
+
command: e.command,
|
|
222
|
+
accepted: e.accepted,
|
|
223
|
+
context: e.context,
|
|
224
|
+
diff_hash: e.diffHash,
|
|
225
|
+
timestamp: e.timestamp,
|
|
226
|
+
}));
|
|
227
|
+
return request<{ received: number }>("POST", "/feedback/batch", {
|
|
228
|
+
events: snakeEvents,
|
|
229
|
+
});
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
getFeedbackImprovements: async () => {
|
|
233
|
+
// biome-ignore lint/suspicious/noExplicitAny: snake_case API mapping
|
|
234
|
+
const result = await request<any>("GET", "/feedback/improvements");
|
|
235
|
+
if (!result.ok) return result;
|
|
236
|
+
const d = result.value;
|
|
237
|
+
const rawImprovements = d.improvements ?? [];
|
|
238
|
+
const improvements: CloudPromptImprovement[] = rawImprovements.map(
|
|
239
|
+
// biome-ignore lint/suspicious/noExplicitAny: snake_case API mapping
|
|
240
|
+
(i: any) => ({
|
|
241
|
+
command: i.command,
|
|
242
|
+
promptHash: i.promptHash ?? i.prompt_hash,
|
|
243
|
+
samples: i.samples,
|
|
244
|
+
acceptRate: i.acceptRate ?? i.accept_rate,
|
|
245
|
+
status: i.status,
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
const totals = d.teamTotals ?? d.team_totals ?? {};
|
|
249
|
+
return ok({
|
|
250
|
+
improvements,
|
|
251
|
+
teamTotals: {
|
|
252
|
+
totalEvents: totals.totalEvents ?? totals.total_events ?? 0,
|
|
253
|
+
acceptRate: totals.acceptRate ?? totals.accept_rate ?? 0,
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
},
|
|
257
|
+
|
|
204
258
|
submitVerify: async (payload) => {
|
|
205
259
|
// biome-ignore lint/suspicious/noExplicitAny: snake_case API mapping
|
|
206
260
|
const result = await request<any>("POST", "/verify", {
|
package/src/cloud/types.ts
CHANGED
|
@@ -156,3 +156,45 @@ export interface CloudFeedbackPayload {
|
|
|
156
156
|
/** Optional context about the feedback. */
|
|
157
157
|
context?: string;
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
// ── Feedback Batch (learn --cloud) ─────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
export interface FeedbackEvent {
|
|
163
|
+
/** Prompt hash the feedback refers to. */
|
|
164
|
+
promptHash: string;
|
|
165
|
+
/** Command that generated the output. */
|
|
166
|
+
command: string;
|
|
167
|
+
/** Whether the user accepted the output. */
|
|
168
|
+
accepted: boolean;
|
|
169
|
+
/** Optional context about the feedback. */
|
|
170
|
+
context?: string;
|
|
171
|
+
/** Optional diff hash for traceability. */
|
|
172
|
+
diffHash?: string;
|
|
173
|
+
/** ISO-8601 timestamp. */
|
|
174
|
+
timestamp?: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface FeedbackBatchPayload {
|
|
178
|
+
/** Array of feedback events to upload. */
|
|
179
|
+
events: FeedbackEvent[];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface CloudPromptImprovement {
|
|
183
|
+
/** Command the improvement applies to. */
|
|
184
|
+
command: string;
|
|
185
|
+
/** Prompt hash that was analysed. */
|
|
186
|
+
promptHash: string;
|
|
187
|
+
/** Number of feedback samples analysed. */
|
|
188
|
+
samples: number;
|
|
189
|
+
/** Accept rate (0–1). */
|
|
190
|
+
acceptRate: number;
|
|
191
|
+
/** Health status based on accept rate. */
|
|
192
|
+
status: "needs_improvement" | "healthy" | "excellent";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface FeedbackImprovementsResponse {
|
|
196
|
+
/** Per-command improvement assessments. */
|
|
197
|
+
improvements: CloudPromptImprovement[];
|
|
198
|
+
/** Aggregated team-wide feedback totals. */
|
|
199
|
+
teamTotals: { totalEvents: number; acceptRate: number };
|
|
200
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdirSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { recordOutcome } from "../../prompts/engine";
|
|
5
|
+
import { exportFeedbackForCloud } from "../sync";
|
|
6
|
+
|
|
7
|
+
let tmpDir: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
tmpDir = join(
|
|
11
|
+
import.meta.dir,
|
|
12
|
+
`tmp-sync-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
13
|
+
);
|
|
14
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
try {
|
|
19
|
+
const { rmSync } = require("node:fs");
|
|
20
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
21
|
+
} catch {
|
|
22
|
+
// ignore
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("exportFeedbackForCloud", () => {
|
|
27
|
+
test("returns empty array when no feedback exists", () => {
|
|
28
|
+
const events = exportFeedbackForCloud(tmpDir);
|
|
29
|
+
expect(events).toEqual([]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("exports accepted feedback events", () => {
|
|
33
|
+
recordOutcome(tmpDir, "hash-abc", {
|
|
34
|
+
accepted: true,
|
|
35
|
+
command: "commit",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const events = exportFeedbackForCloud(tmpDir);
|
|
39
|
+
|
|
40
|
+
expect(events).toHaveLength(1);
|
|
41
|
+
expect(events[0]?.promptHash).toBe("hash-abc");
|
|
42
|
+
expect(events[0]?.command).toBe("commit");
|
|
43
|
+
expect(events[0]?.accepted).toBe(true);
|
|
44
|
+
expect(events[0]?.timestamp).toBeDefined();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("exports rejected feedback events", () => {
|
|
48
|
+
recordOutcome(tmpDir, "hash-def", {
|
|
49
|
+
accepted: false,
|
|
50
|
+
command: "review",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const events = exportFeedbackForCloud(tmpDir);
|
|
54
|
+
|
|
55
|
+
expect(events).toHaveLength(1);
|
|
56
|
+
expect(events[0]?.accepted).toBe(false);
|
|
57
|
+
expect(events[0]?.command).toBe("review");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("exports context when present", () => {
|
|
61
|
+
recordOutcome(tmpDir, "hash-ctx", {
|
|
62
|
+
accepted: true,
|
|
63
|
+
command: "commit",
|
|
64
|
+
context: "user edited the message",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const events = exportFeedbackForCloud(tmpDir);
|
|
68
|
+
|
|
69
|
+
expect(events).toHaveLength(1);
|
|
70
|
+
expect(events[0]?.context).toBe("user edited the message");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("omits context when not present", () => {
|
|
74
|
+
recordOutcome(tmpDir, "hash-no-ctx", {
|
|
75
|
+
accepted: true,
|
|
76
|
+
command: "commit",
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const events = exportFeedbackForCloud(tmpDir);
|
|
80
|
+
|
|
81
|
+
expect(events).toHaveLength(1);
|
|
82
|
+
expect(events[0]?.context).toBeUndefined();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("exports multiple events in chronological order", () => {
|
|
86
|
+
recordOutcome(tmpDir, "hash-1", { accepted: true, command: "commit" });
|
|
87
|
+
recordOutcome(tmpDir, "hash-2", { accepted: false, command: "review" });
|
|
88
|
+
recordOutcome(tmpDir, "hash-3", { accepted: true, command: "fix" });
|
|
89
|
+
|
|
90
|
+
const events = exportFeedbackForCloud(tmpDir);
|
|
91
|
+
|
|
92
|
+
expect(events).toHaveLength(3);
|
|
93
|
+
expect(events[0]?.promptHash).toBe("hash-1");
|
|
94
|
+
expect(events[1]?.promptHash).toBe("hash-2");
|
|
95
|
+
expect(events[2]?.promptHash).toBe("hash-3");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("returns empty array on invalid db path", () => {
|
|
99
|
+
const events = exportFeedbackForCloud(
|
|
100
|
+
"/nonexistent/path/that/does/not/exist",
|
|
101
|
+
);
|
|
102
|
+
expect(events).toEqual([]);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feedback sync — exports local feedback records for cloud upload.
|
|
3
|
+
*
|
|
4
|
+
* Reads from the local SQLite feedback.db and maps records to the
|
|
5
|
+
* cloud-compatible FeedbackEvent format for batch upload.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { FeedbackEvent } from "../cloud/types";
|
|
9
|
+
import { getFeedbackDb } from "../db/index";
|
|
10
|
+
|
|
11
|
+
/** Raw row shape from the feedback table. */
|
|
12
|
+
interface FeedbackRow {
|
|
13
|
+
prompt_hash: string;
|
|
14
|
+
command: string;
|
|
15
|
+
accepted: number;
|
|
16
|
+
context: string | null;
|
|
17
|
+
created_at: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Export all local feedback records in the cloud-compatible format.
|
|
22
|
+
*
|
|
23
|
+
* Reads from the feedback table in the SQLite database at `mainaDir/feedback.db`
|
|
24
|
+
* and maps each row to a `FeedbackEvent` object ready for batch upload.
|
|
25
|
+
*/
|
|
26
|
+
export function exportFeedbackForCloud(mainaDir: string): FeedbackEvent[] {
|
|
27
|
+
const dbResult = getFeedbackDb(mainaDir);
|
|
28
|
+
if (!dbResult.ok) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const { db } = dbResult.value;
|
|
33
|
+
|
|
34
|
+
const rows = db
|
|
35
|
+
.query(
|
|
36
|
+
"SELECT prompt_hash, command, accepted, context, created_at FROM feedback ORDER BY created_at ASC",
|
|
37
|
+
)
|
|
38
|
+
.all() as FeedbackRow[];
|
|
39
|
+
|
|
40
|
+
return rows.map((row) => {
|
|
41
|
+
const event: FeedbackEvent = {
|
|
42
|
+
promptHash: row.prompt_hash,
|
|
43
|
+
command: row.command,
|
|
44
|
+
accepted: row.accepted === 1,
|
|
45
|
+
timestamp: row.created_at,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
if (row.context) {
|
|
49
|
+
event.context = row.context;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return event;
|
|
53
|
+
});
|
|
54
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -64,7 +64,11 @@ export type {
|
|
|
64
64
|
ApiResponse,
|
|
65
65
|
CloudConfig,
|
|
66
66
|
CloudFeedbackPayload,
|
|
67
|
+
CloudPromptImprovement,
|
|
67
68
|
DeviceCodeResponse,
|
|
69
|
+
FeedbackBatchPayload,
|
|
70
|
+
FeedbackEvent,
|
|
71
|
+
FeedbackImprovementsResponse,
|
|
68
72
|
PromptRecord,
|
|
69
73
|
SubmitVerifyPayload,
|
|
70
74
|
TeamInfo,
|
|
@@ -168,6 +172,7 @@ export {
|
|
|
168
172
|
type RulePreference,
|
|
169
173
|
savePreferences,
|
|
170
174
|
} from "./feedback/preferences";
|
|
175
|
+
export { exportFeedbackForCloud } from "./feedback/sync";
|
|
171
176
|
export {
|
|
172
177
|
analyzeWorkflowTrace,
|
|
173
178
|
type PromptImprovement,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"packages": []}
|