@jagit/shared 0.0.1

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 (54) hide show
  1. package/dist/approval-bridge.d.ts +12 -0
  2. package/dist/approval-bridge.js +35 -0
  3. package/dist/branch.d.ts +10 -0
  4. package/dist/branch.js +20 -0
  5. package/dist/branch.test.d.ts +1 -0
  6. package/dist/branch.test.js +32 -0
  7. package/dist/config.d.ts +19 -0
  8. package/dist/config.js +36 -0
  9. package/dist/config.test.d.ts +1 -0
  10. package/dist/config.test.js +31 -0
  11. package/dist/credentials.d.ts +82 -0
  12. package/dist/credentials.js +94 -0
  13. package/dist/credentials.test.d.ts +1 -0
  14. package/dist/credentials.test.js +206 -0
  15. package/dist/crypto.d.ts +12 -0
  16. package/dist/crypto.js +29 -0
  17. package/dist/crypto.test.d.ts +1 -0
  18. package/dist/crypto.test.js +20 -0
  19. package/dist/events.d.ts +13 -0
  20. package/dist/events.js +24 -0
  21. package/dist/git-worktree.d.ts +2 -0
  22. package/dist/git-worktree.js +14 -0
  23. package/dist/index.d.ts +15 -0
  24. package/dist/index.js +14 -0
  25. package/dist/mcp-config.d.ts +77 -0
  26. package/dist/mcp-config.js +71 -0
  27. package/dist/mcp-servers.d.ts +54 -0
  28. package/dist/mcp-servers.js +73 -0
  29. package/dist/prisma.d.ts +3 -0
  30. package/dist/prisma.js +20 -0
  31. package/dist/prisma.test.d.ts +1 -0
  32. package/dist/prisma.test.js +16 -0
  33. package/dist/queue.d.ts +10 -0
  34. package/dist/queue.js +7 -0
  35. package/dist/retry.d.ts +5 -0
  36. package/dist/retry.js +16 -0
  37. package/dist/retry.test.d.ts +1 -0
  38. package/dist/retry.test.js +24 -0
  39. package/dist/seed.d.ts +99 -0
  40. package/dist/seed.js +123 -0
  41. package/dist/seed.test.d.ts +1 -0
  42. package/dist/seed.test.js +126 -0
  43. package/dist/types.d.ts +16 -0
  44. package/dist/types.js +5 -0
  45. package/package.json +29 -0
  46. package/prisma/migrations/20260615000000_init/migration.sql +157 -0
  47. package/prisma/migrations/20260615000001_add_updated_at_agent_template/migration.sql +2 -0
  48. package/prisma/migrations/20260616000000_mcp_review/migration.sql +23 -0
  49. package/prisma/migrations/20260616100000_mcp_http_transport/migration.sql +5 -0
  50. package/prisma/migrations/20260616120000_review_default_false/migration.sql +1 -0
  51. package/prisma/migrations/20260620040203_add_usage_models/migration.sql +28 -0
  52. package/prisma/migrations/20260620120000_add_agent_session/migration.sql +35 -0
  53. package/prisma/migrations/migration_lock.toml +3 -0
  54. package/prisma/schema.prisma +257 -0
@@ -0,0 +1,13 @@
1
+ import { Redis } from "ioredis";
2
+ import type { ControlSignal } from "./types.js";
3
+ /** Channel name for live job event streaming (dashboard SSE) */
4
+ export declare const jobChannel: (jobId: string) => string;
5
+ /** Channel name for worker control signals (stop/pause/resume/approval) */
6
+ export declare const controlChannel: (jobId: string) => string;
7
+ /** Channel name for global approval events (SSE + worker graph) */
8
+ export declare const approvalsChannel = "approvals";
9
+ export declare function makeRedis(url: string): Redis;
10
+ /** Publish any JSON-serialisable payload to a channel */
11
+ export declare function publishEvent(url: string, channel: string, data: unknown): Promise<void>;
12
+ /** Publish a control signal to the worker monitoring the given job */
13
+ export declare function publishControl(url: string, signal: ControlSignal): Promise<void>;
package/dist/events.js ADDED
@@ -0,0 +1,24 @@
1
+ import { Redis } from "ioredis";
2
+ /** Channel name for live job event streaming (dashboard SSE) */
3
+ export const jobChannel = (jobId) => `job:${jobId}`;
4
+ /** Channel name for worker control signals (stop/pause/resume/approval) */
5
+ export const controlChannel = (jobId) => `control:${jobId}`;
6
+ /** Channel name for global approval events (SSE + worker graph) */
7
+ export const approvalsChannel = "approvals";
8
+ export function makeRedis(url) {
9
+ return new Redis(url, { maxRetriesPerRequest: null, lazyConnect: false });
10
+ }
11
+ /** Publish any JSON-serialisable payload to a channel */
12
+ export async function publishEvent(url, channel, data) {
13
+ const client = makeRedis(url);
14
+ try {
15
+ await client.publish(channel, JSON.stringify(data));
16
+ }
17
+ finally {
18
+ await client.quit();
19
+ }
20
+ }
21
+ /** Publish a control signal to the worker monitoring the given job */
22
+ export async function publishControl(url, signal) {
23
+ return publishEvent(url, controlChannel(signal.jobId), signal);
24
+ }
@@ -0,0 +1,2 @@
1
+ /** Best-effort removal of a git worktree directory. */
2
+ export declare function removeWorktree(worktreePath: string): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ /** Best-effort removal of a git worktree directory. */
5
+ export async function removeWorktree(worktreePath) {
6
+ if (!worktreePath)
7
+ return;
8
+ try {
9
+ await execFileAsync("git", ["worktree", "remove", "--force", worktreePath]);
10
+ }
11
+ catch {
12
+ // already removed or path invalid
13
+ }
14
+ }
@@ -0,0 +1,15 @@
1
+ export * from "./config.js";
2
+ export * from "./crypto.js";
3
+ export { CredentialKindSchema, JiraCredentialSchema, GitLabCredentialSchema, AnthropicCredentialSchema, TelegramCredentialSchema, credentialSecretKeys, validateCredential, mergeSecrets, } from "./credentials.js";
4
+ export type { CredentialKind } from "./credentials.js";
5
+ export * from "./prisma.js";
6
+ export * from "./branch.js";
7
+ export * from "./retry.js";
8
+ export * from "./queue.js";
9
+ export * from "./events.js";
10
+ export * from "./types.js";
11
+ export * from "./git-worktree.js";
12
+ export * from "./seed.js";
13
+ export * from "./mcp-config.js";
14
+ export * from "./mcp-servers.js";
15
+ export * from "./approval-bridge.js";
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export * from "./config.js";
2
+ export * from "./crypto.js";
3
+ export { CredentialKindSchema, JiraCredentialSchema, GitLabCredentialSchema, AnthropicCredentialSchema, TelegramCredentialSchema, credentialSecretKeys, validateCredential, mergeSecrets, } from "./credentials.js";
4
+ export * from "./prisma.js";
5
+ export * from "./branch.js";
6
+ export * from "./retry.js";
7
+ export * from "./queue.js";
8
+ export * from "./events.js";
9
+ export * from "./types.js";
10
+ export * from "./git-worktree.js";
11
+ export * from "./seed.js";
12
+ export * from "./mcp-config.js";
13
+ export * from "./mcp-servers.js";
14
+ export * from "./approval-bridge.js";
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ import { CredentialKindSchema } from "./credentials.js";
3
+ export declare const McpTransportSchema: z.ZodEnum<{
4
+ stdio: "stdio";
5
+ http: "http";
6
+ }>;
7
+ export declare const McpEnvCredentialRefSchema: z.ZodObject<{
8
+ type: z.ZodLiteral<"credential">;
9
+ kind: z.ZodEnum<{
10
+ jira: "jira";
11
+ gitlab: "gitlab";
12
+ telegram: "telegram";
13
+ anthropic: "anthropic";
14
+ }>;
15
+ name: z.ZodString;
16
+ secretKey: z.ZodString;
17
+ }, z.core.$strip>;
18
+ export declare const McpEnvValueSchema: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
19
+ type: z.ZodLiteral<"credential">;
20
+ kind: z.ZodEnum<{
21
+ jira: "jira";
22
+ gitlab: "gitlab";
23
+ telegram: "telegram";
24
+ anthropic: "anthropic";
25
+ }>;
26
+ name: z.ZodString;
27
+ secretKey: z.ZodString;
28
+ }, z.core.$strip>]>;
29
+ export declare const McpServerConfigBodySchema: z.ZodObject<{
30
+ name: z.ZodString;
31
+ transport: z.ZodDefault<z.ZodEnum<{
32
+ stdio: "stdio";
33
+ http: "http";
34
+ }>>;
35
+ enabled: z.ZodDefault<z.ZodBoolean>;
36
+ command: z.ZodOptional<z.ZodString>;
37
+ args: z.ZodDefault<z.ZodArray<z.ZodString>>;
38
+ env: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
39
+ type: z.ZodLiteral<"credential">;
40
+ kind: z.ZodEnum<{
41
+ jira: "jira";
42
+ gitlab: "gitlab";
43
+ telegram: "telegram";
44
+ anthropic: "anthropic";
45
+ }>;
46
+ name: z.ZodString;
47
+ secretKey: z.ZodString;
48
+ }, z.core.$strip>]>>>;
49
+ url: z.ZodOptional<z.ZodString>;
50
+ headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
51
+ type: z.ZodLiteral<"credential">;
52
+ kind: z.ZodEnum<{
53
+ jira: "jira";
54
+ gitlab: "gitlab";
55
+ telegram: "telegram";
56
+ anthropic: "anthropic";
57
+ }>;
58
+ name: z.ZodString;
59
+ secretKey: z.ZodString;
60
+ }, z.core.$strip>]>>>;
61
+ }, z.core.$strip>;
62
+ export type McpServerConfigBody = z.infer<typeof McpServerConfigBodySchema>;
63
+ export type McpTransport = z.infer<typeof McpTransportSchema>;
64
+ export type McpEnvValue = z.infer<typeof McpEnvValueSchema>;
65
+ export type McpEnvCredentialRef = z.infer<typeof McpEnvCredentialRefSchema>;
66
+ export type CredentialResolver = (kind: z.infer<typeof CredentialKindSchema>, name: string) => Promise<Record<string, string>>;
67
+ /** Resolve MCP env/header map to plain string values. */
68
+ export declare function resolveMcpEnv(env: Record<string, McpEnvValue>, resolveCredential: CredentialResolver): Promise<Record<string, string>>;
69
+ /** Whether a chosen approval option counts as human approval for commit guard. */
70
+ export declare function isApproveOptionId(optionId: string): boolean;
71
+ export declare const DEFAULT_REVIEW_OPTIONS: readonly [{
72
+ readonly optionId: "approve";
73
+ readonly name: "Approve";
74
+ }, {
75
+ readonly optionId: "reject";
76
+ readonly name: "Request changes";
77
+ }];
@@ -0,0 +1,71 @@
1
+ import { z } from "zod";
2
+ import { CredentialKindSchema } from "./credentials.js";
3
+ export const McpTransportSchema = z.enum(["stdio", "http"]);
4
+ export const McpEnvCredentialRefSchema = z.object({
5
+ type: z.literal("credential"),
6
+ kind: CredentialKindSchema,
7
+ name: z.string().min(1),
8
+ secretKey: z.string().min(1),
9
+ });
10
+ export const McpEnvValueSchema = z.union([z.string(), McpEnvCredentialRefSchema]);
11
+ const mcpKeyValues = z.record(z.string(), McpEnvValueSchema).default({});
12
+ export const McpServerConfigBodySchema = z
13
+ .object({
14
+ name: z.string().min(1),
15
+ transport: McpTransportSchema.default("stdio"),
16
+ enabled: z.boolean().default(true),
17
+ // stdio
18
+ command: z.string().optional(),
19
+ args: z.array(z.string()).default([]),
20
+ env: mcpKeyValues,
21
+ // http
22
+ url: z.string().url().optional(),
23
+ headers: mcpKeyValues,
24
+ })
25
+ .superRefine((data, ctx) => {
26
+ if (data.transport === "stdio") {
27
+ if (!data.command?.trim()) {
28
+ ctx.addIssue({
29
+ code: z.ZodIssueCode.custom,
30
+ message: "command is required for stdio transport",
31
+ path: ["command"],
32
+ });
33
+ }
34
+ }
35
+ if (data.transport === "http") {
36
+ if (!data.url?.trim()) {
37
+ ctx.addIssue({
38
+ code: z.ZodIssueCode.custom,
39
+ message: "url is required for http transport",
40
+ path: ["url"],
41
+ });
42
+ }
43
+ }
44
+ });
45
+ /** Resolve MCP env/header map to plain string values. */
46
+ export async function resolveMcpEnv(env, resolveCredential) {
47
+ const out = {};
48
+ for (const [key, value] of Object.entries(env)) {
49
+ if (typeof value === "string") {
50
+ out[key] = value;
51
+ continue;
52
+ }
53
+ const secrets = await resolveCredential(value.kind, value.name);
54
+ const secret = secrets[value.secretKey];
55
+ if (!secret) {
56
+ throw new Error(`Credential ${value.kind}/${value.name} missing secret key "${value.secretKey}"`);
57
+ }
58
+ out[key] = secret;
59
+ }
60
+ return out;
61
+ }
62
+ /** Whether a chosen approval option counts as human approval for commit guard. */
63
+ export function isApproveOptionId(optionId) {
64
+ if (optionId.startsWith("deny") || optionId === "reject")
65
+ return false;
66
+ return optionId === "approve" || optionId === "allow" || optionId.startsWith("allow");
67
+ }
68
+ export const DEFAULT_REVIEW_OPTIONS = [
69
+ { optionId: "approve", name: "Approve" },
70
+ { optionId: "reject", name: "Request changes" },
71
+ ];
@@ -0,0 +1,54 @@
1
+ import { type CredentialResolver } from "./mcp-config.js";
2
+ export interface AcpMcpServerStdio {
3
+ name: string;
4
+ command: string;
5
+ args: string[];
6
+ env: {
7
+ name: string;
8
+ value: string;
9
+ }[];
10
+ }
11
+ export interface AcpMcpServerHttp {
12
+ type: "http";
13
+ name: string;
14
+ url: string;
15
+ headers: {
16
+ name: string;
17
+ value: string;
18
+ }[];
19
+ }
20
+ export type AcpMcpServer = AcpMcpServerStdio | AcpMcpServerHttp;
21
+ export declare function isAcpMcpServerHttp(server: AcpMcpServer): server is AcpMcpServerHttp;
22
+ export interface McpServerConfigRow {
23
+ id: string;
24
+ name: string;
25
+ transport: string;
26
+ command: string;
27
+ args: unknown;
28
+ env: unknown;
29
+ url: string | null;
30
+ headers: unknown;
31
+ enabled: boolean;
32
+ }
33
+ export interface BuildAcpMcpServersOpts {
34
+ template: {
35
+ mcpServerIds: string[];
36
+ requireReviewBeforeCommit: boolean;
37
+ };
38
+ dbConfigs: McpServerConfigRow[];
39
+ jobContext: {
40
+ jobId: string;
41
+ redisUrl: string;
42
+ publicBaseUrl: string;
43
+ dashboardApiToken: string;
44
+ jagitServerPath: string;
45
+ approvalTimeoutMs: number;
46
+ };
47
+ resolveCredential: CredentialResolver;
48
+ }
49
+ /** Build ACP session/new mcpServers: built-in jagit + template-linked configs. */
50
+ export declare function buildAcpMcpServers(opts: BuildAcpMcpServersOpts): Promise<AcpMcpServer[]>;
51
+ /** Instruction appended to agent prompt when review before commit is required. */
52
+ export declare function buildReviewInstruction(): string;
53
+ /** Instruction appended to every agent prompt so its final message can be relayed as the job's status report. */
54
+ export declare function buildReportInstruction(): string;
@@ -0,0 +1,73 @@
1
+ import { resolveMcpEnv } from "./mcp-config.js";
2
+ export function isAcpMcpServerHttp(server) {
3
+ return "type" in server && server.type === "http";
4
+ }
5
+ function toKeyValueArray(map) {
6
+ return Object.entries(map).map(([name, value]) => ({ name, value }));
7
+ }
8
+ function parseKeyValues(raw) {
9
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
10
+ return raw;
11
+ }
12
+ return {};
13
+ }
14
+ function buildJagitServer(opts) {
15
+ const { jobContext } = opts;
16
+ return {
17
+ name: "jagit",
18
+ command: "node",
19
+ args: [jobContext.jagitServerPath],
20
+ env: toKeyValueArray({
21
+ JAGIT_JOB_ID: jobContext.jobId,
22
+ REDIS_URL: jobContext.redisUrl,
23
+ PUBLIC_BASE_URL: jobContext.publicBaseUrl,
24
+ DASHBOARD_API_TOKEN: jobContext.dashboardApiToken,
25
+ APPROVAL_TIMEOUT_MS: String(jobContext.approvalTimeoutMs),
26
+ }),
27
+ };
28
+ }
29
+ /** Build ACP session/new mcpServers: built-in jagit + template-linked configs. */
30
+ export async function buildAcpMcpServers(opts) {
31
+ const servers = [buildJagitServer(opts)];
32
+ const idSet = new Set(opts.template.mcpServerIds);
33
+ const selected = opts.dbConfigs.filter((c) => idSet.has(c.id) && c.enabled);
34
+ for (const config of selected) {
35
+ const transport = config.transport === "http" ? "http" : "stdio";
36
+ if (transport === "http") {
37
+ if (!config.url)
38
+ continue;
39
+ const resolvedHeaders = await resolveMcpEnv(parseKeyValues(config.headers), opts.resolveCredential);
40
+ servers.push({
41
+ type: "http",
42
+ name: config.name,
43
+ url: config.url,
44
+ headers: toKeyValueArray(resolvedHeaders),
45
+ });
46
+ continue;
47
+ }
48
+ const args = Array.isArray(config.args) ? config.args : [];
49
+ const resolvedEnv = await resolveMcpEnv(parseKeyValues(config.env), opts.resolveCredential);
50
+ servers.push({
51
+ name: config.name,
52
+ command: config.command,
53
+ args,
54
+ env: toKeyValueArray(resolvedEnv),
55
+ });
56
+ }
57
+ return servers;
58
+ }
59
+ /** Instruction appended to agent prompt when review before commit is required. */
60
+ export function buildReviewInstruction() {
61
+ return [
62
+ "Before you finish your work, you MUST call the MCP tool `jagit_request_review`",
63
+ "with a summary of your changes and wait for human approval.",
64
+ "Do not consider the task complete until review is approved.",
65
+ ].join(" ");
66
+ }
67
+ /** Instruction appended to every agent prompt so its final message can be relayed as the job's status report. */
68
+ export function buildReportInstruction() {
69
+ return [
70
+ "End your final message with a concise 2-4 sentence summary of what you changed",
71
+ "and why, written for a non-technical reader — it will be relayed to them directly.",
72
+ ].join(" ");
73
+ }
@@ -0,0 +1,3 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+ export declare const prisma: PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/client").DefaultArgs>;
3
+ export * from "@prisma/client";
package/dist/prisma.js ADDED
@@ -0,0 +1,20 @@
1
+ import { PrismaClient } from "@prisma/client";
2
+ import { PrismaPg } from "@prisma/adapter-pg";
3
+ function createPrismaClient() {
4
+ // Allow empty string so the module loads without DATABASE_URL set.
5
+ // Actual queries will fail at connect time, letting test skipIf guards act.
6
+ const connectionString = process.env.DATABASE_URL ?? "";
7
+ const adapter = new PrismaPg({ connectionString });
8
+ return new PrismaClient({
9
+ adapter,
10
+ log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
11
+ });
12
+ }
13
+ // Singleton: one client per process.
14
+ // In tests that set DATABASE_URL, this connects to the real DB.
15
+ const globalForPrisma = global;
16
+ export const prisma = globalForPrisma.prisma ?? createPrismaClient();
17
+ if (process.env.NODE_ENV !== "production") {
18
+ globalForPrisma.prisma = prisma;
19
+ }
20
+ export * from "@prisma/client";
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { describe, it, expect, afterAll } from "vitest";
2
+ import { prisma } from "./prisma.js";
3
+ describe.skipIf(!process.env.DATABASE_URL)("prisma smoke", () => {
4
+ afterAll(async () => {
5
+ await prisma.$disconnect();
6
+ });
7
+ it("connects and counts jobs", async () => {
8
+ const count = await prisma.job.count();
9
+ expect(typeof count).toBe("number");
10
+ });
11
+ it("can read all enum values for JobStatus", async () => {
12
+ // If the enum is wrong the Prisma client won't even compile.
13
+ const jobs = await prisma.job.findMany({ take: 1 });
14
+ expect(Array.isArray(jobs)).toBe(true);
15
+ });
16
+ });
@@ -0,0 +1,10 @@
1
+ import { Queue, Worker, type Processor } from "bullmq";
2
+ /** Creates the BullMQ queue used by the API to enqueue jobs */
3
+ export declare const createQueue: (redisUrl: string) => Queue<{
4
+ jobId: string;
5
+ }, any, string, {
6
+ jobId: string;
7
+ }, any, string>;
8
+ /** Creates the BullMQ worker consumed by the worker service */
9
+ export declare const createWorker: <T = unknown>(redisUrl: string, processor: Processor<T>, concurrency: number) => Worker<T, any, string>;
10
+ export type { JagitJobData } from "./types.js";
package/dist/queue.js ADDED
@@ -0,0 +1,7 @@
1
+ import { Queue, Worker } from "bullmq";
2
+ import { JOB_QUEUE } from "./types.js";
3
+ const connection = (url) => ({ connection: { url } });
4
+ /** Creates the BullMQ queue used by the API to enqueue jobs */
5
+ export const createQueue = (redisUrl) => new Queue(JOB_QUEUE, connection(redisUrl));
6
+ /** Creates the BullMQ worker consumed by the worker service */
7
+ export const createWorker = (redisUrl, processor, concurrency) => new Worker(JOB_QUEUE, processor, { ...connection(redisUrl), concurrency });
@@ -0,0 +1,5 @@
1
+ export interface RetryOpts {
2
+ maxRetries: number;
3
+ baseDelayMs: number;
4
+ }
5
+ export declare function withRetry<T>(fn: () => Promise<T>, opts: RetryOpts): Promise<T>;
package/dist/retry.js ADDED
@@ -0,0 +1,16 @@
1
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
2
+ export async function withRetry(fn, opts) {
3
+ let lastError;
4
+ for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
5
+ try {
6
+ return await fn();
7
+ }
8
+ catch (err) {
9
+ lastError = err;
10
+ if (attempt < opts.maxRetries) {
11
+ await sleep(opts.baseDelayMs * 2 ** attempt);
12
+ }
13
+ }
14
+ }
15
+ throw lastError;
16
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { withRetry } from "./retry.js";
3
+ describe("withRetry", () => {
4
+ it("returns immediately on first success", async () => {
5
+ const fn = vi.fn(async () => "ok");
6
+ expect(await withRetry(fn, { maxRetries: 3, baseDelayMs: 0 })).toBe("ok");
7
+ expect(fn).toHaveBeenCalledTimes(1);
8
+ });
9
+ it("retries until success", async () => {
10
+ let n = 0;
11
+ const fn = vi.fn(async () => {
12
+ if (++n < 3)
13
+ throw new Error("not yet");
14
+ return "ok";
15
+ });
16
+ expect(await withRetry(fn, { maxRetries: 3, baseDelayMs: 0 })).toBe("ok");
17
+ expect(fn).toHaveBeenCalledTimes(3);
18
+ });
19
+ it("throws after maxRetries + 1 attempts", async () => {
20
+ const fn = vi.fn(async () => { throw new Error("nope"); });
21
+ await expect(withRetry(fn, { maxRetries: 2, baseDelayMs: 0 })).rejects.toThrow("nope");
22
+ expect(fn).toHaveBeenCalledTimes(3); // 1 initial + 2 retries
23
+ });
24
+ });
package/dist/seed.d.ts ADDED
@@ -0,0 +1,99 @@
1
+ import { z } from "zod";
2
+ declare const CredentialKindSchema: z.ZodEnum<{
3
+ jira: "jira";
4
+ gitlab: "gitlab";
5
+ telegram: "telegram";
6
+ anthropic: "anthropic";
7
+ }>;
8
+ export declare const SeedDataSchema: z.ZodObject<{
9
+ agentTemplate: z.ZodObject<{
10
+ name: z.ZodString;
11
+ model: z.ZodString;
12
+ systemPrompt: z.ZodString;
13
+ maxConcurrent: z.ZodNumber;
14
+ allowedTools: z.ZodArray<z.ZodString>;
15
+ skills: z.ZodArray<z.ZodString>;
16
+ }, z.core.$strip>;
17
+ credentials: z.ZodArray<z.ZodObject<{
18
+ kind: z.ZodEnum<{
19
+ jira: "jira";
20
+ gitlab: "gitlab";
21
+ telegram: "telegram";
22
+ anthropic: "anthropic";
23
+ }>;
24
+ name: z.ZodString;
25
+ secrets: z.ZodRecord<z.ZodString, z.ZodString>;
26
+ meta: z.ZodRecord<z.ZodString, z.ZodString>;
27
+ }, z.core.$strip>>;
28
+ repoMapping: z.ZodObject<{
29
+ jiraProjectKey: z.ZodString;
30
+ gitlabProjectId: z.ZodString;
31
+ defaultBaseBranch: z.ZodString;
32
+ branchPrefixRules: z.ZodRecord<z.ZodString, z.ZodString>;
33
+ agentTemplateName: z.ZodString;
34
+ }, z.core.$strip>;
35
+ }, z.core.$strip>;
36
+ export type SeedData = z.infer<typeof SeedDataSchema>;
37
+ export type SeedCredentialKind = z.infer<typeof CredentialKindSchema>;
38
+ export interface SeedPrismaClient {
39
+ agentTemplate: {
40
+ upsert(args: {
41
+ where: {
42
+ name: string;
43
+ };
44
+ create: SeedData["agentTemplate"];
45
+ update: Omit<SeedData["agentTemplate"], "name">;
46
+ }): Promise<{
47
+ id: string;
48
+ }>;
49
+ };
50
+ credential: {
51
+ upsert(args: {
52
+ where: {
53
+ kind_name: {
54
+ kind: SeedCredentialKind;
55
+ name: string;
56
+ };
57
+ };
58
+ create: {
59
+ kind: SeedCredentialKind;
60
+ name: string;
61
+ secrets: {
62
+ encrypted: string;
63
+ };
64
+ meta: Record<string, string>;
65
+ };
66
+ update: {
67
+ secrets: {
68
+ encrypted: string;
69
+ };
70
+ meta: Record<string, string>;
71
+ };
72
+ }): Promise<unknown>;
73
+ };
74
+ repoMapping: {
75
+ upsert(args: {
76
+ where: {
77
+ jiraProjectKey: string;
78
+ };
79
+ create: {
80
+ jiraProjectKey: string;
81
+ gitlabProjectId: string;
82
+ defaultBaseBranch: string;
83
+ branchPrefixRules: Record<string, string>;
84
+ agentTemplateId: string;
85
+ };
86
+ update: {
87
+ gitlabProjectId: string;
88
+ defaultBaseBranch: string;
89
+ branchPrefixRules: Record<string, string>;
90
+ agentTemplateId: string;
91
+ };
92
+ }): Promise<unknown>;
93
+ };
94
+ }
95
+ export declare function buildSeedData(input: {
96
+ anthropicApiKey: string;
97
+ }): SeedData;
98
+ export declare function seedDatabase(client: SeedPrismaClient, seedData: SeedData, encryptionKey: string): Promise<void>;
99
+ export {};