@vercel/factory 0.0.4 → 0.0.5

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.
@@ -0,0 +1,43 @@
1
+ import { a as NormalizedSignal } from "../index-WNGZJRRH.mjs";
2
+ //#region src/connectors/slack.d.ts
3
+ interface SlackMessageEvent {
4
+ teamId: string;
5
+ channelId: string;
6
+ messageTs: string;
7
+ threadTs: string;
8
+ actorId: string;
9
+ text: string;
10
+ replyAddress: string;
11
+ repositorySlug: string;
12
+ payload: unknown;
13
+ }
14
+ interface SlackMessageSignal extends NormalizedSignal<"slack"> {
15
+ repositorySlug: string;
16
+ message: {
17
+ teamId: string;
18
+ channelId: string;
19
+ messageTs: string;
20
+ threadTs: string;
21
+ actorId: string;
22
+ text: string;
23
+ };
24
+ }
25
+ /** Normalize an Eve-verified Slack message after the caller resolves its repository. */
26
+ declare function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal;
27
+ type SlackRepositorySelection = {
28
+ status: "selected";
29
+ repositorySlug: string;
30
+ } | {
31
+ status: "required";
32
+ } | {
33
+ status: "ambiguous";
34
+ repositorySlugs: readonly string[];
35
+ };
36
+ /**
37
+ * Select the only configured repository, or require an explicit owner/name
38
+ * when a Slack surface fronts more than one repository.
39
+ */
40
+ declare function selectSlackRepository(text: string, repositories: readonly string[]): SlackRepositorySelection;
41
+ //#endregion
42
+ export { SlackMessageEvent, SlackMessageSignal, SlackRepositorySelection, normalizeSlackMessage, selectSlackRepository };
43
+ //# sourceMappingURL=slack.d.mts.map
@@ -0,0 +1,50 @@
1
+ //#region src/connectors/slack.ts
2
+ /** Normalize an Eve-verified Slack message after the caller resolves its repository. */
3
+ function normalizeSlackMessage(event) {
4
+ return {
5
+ source: "slack",
6
+ deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,
7
+ repositorySlug: event.repositorySlug.toLowerCase(),
8
+ replyTo: {
9
+ channel: "slack",
10
+ address: event.replyAddress
11
+ },
12
+ message: {
13
+ teamId: event.teamId,
14
+ channelId: event.channelId,
15
+ messageTs: event.messageTs,
16
+ threadTs: event.threadTs,
17
+ actorId: event.actorId,
18
+ text: event.text
19
+ },
20
+ payload: event.payload
21
+ };
22
+ }
23
+ /**
24
+ * Select the only configured repository, or require an explicit owner/name
25
+ * when a Slack surface fronts more than one repository.
26
+ */
27
+ function selectSlackRepository(text, repositories) {
28
+ const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];
29
+ if (normalized.length === 1) return {
30
+ status: "selected",
31
+ repositorySlug: normalized[0]
32
+ };
33
+ const matches = normalized.filter((slug) => {
34
+ const escaped = slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
35
+ return new RegExp(`(^|[^a-z0-9._/-])${escaped}($|[^a-z0-9._/-])`, "i").test(text);
36
+ });
37
+ if (matches.length === 1) return {
38
+ status: "selected",
39
+ repositorySlug: matches[0]
40
+ };
41
+ if (matches.length > 1) return {
42
+ status: "ambiguous",
43
+ repositorySlugs: matches
44
+ };
45
+ return { status: "required" };
46
+ }
47
+ //#endregion
48
+ export { normalizeSlackMessage, selectSlackRepository };
49
+
50
+ //# sourceMappingURL=slack.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slack.mjs","names":[],"sources":["../../src/connectors/slack.ts"],"sourcesContent":["import type { NormalizedSignal } from \"./index\";\n\nexport interface SlackMessageEvent {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n replyAddress: string;\n repositorySlug: string;\n payload: unknown;\n}\n\nexport interface SlackMessageSignal extends NormalizedSignal<\"slack\"> {\n repositorySlug: string;\n message: {\n teamId: string;\n channelId: string;\n messageTs: string;\n threadTs: string;\n actorId: string;\n text: string;\n };\n}\n\n/** Normalize an Eve-verified Slack message after the caller resolves its repository. */\nexport function normalizeSlackMessage(event: SlackMessageEvent): SlackMessageSignal {\n return {\n source: \"slack\",\n deliveryId: `${event.teamId}:${event.channelId}:${event.messageTs}`,\n repositorySlug: event.repositorySlug.toLowerCase(),\n replyTo: { channel: \"slack\", address: event.replyAddress },\n message: {\n teamId: event.teamId,\n channelId: event.channelId,\n messageTs: event.messageTs,\n threadTs: event.threadTs,\n actorId: event.actorId,\n text: event.text,\n },\n payload: event.payload,\n };\n}\n\nexport type SlackRepositorySelection =\n | { status: \"selected\"; repositorySlug: string }\n | { status: \"required\" }\n | { status: \"ambiguous\"; repositorySlugs: readonly string[] };\n\n/**\n * Select the only configured repository, or require an explicit owner/name\n * when a Slack surface fronts more than one repository.\n */\nexport function selectSlackRepository(\n text: string,\n repositories: readonly string[],\n): SlackRepositorySelection {\n const normalized = [...new Set(repositories.map((slug) => slug.toLowerCase()))];\n if (normalized.length === 1) {\n return { status: \"selected\", repositorySlug: normalized[0]! };\n }\n\n const matches = normalized.filter((slug) => {\n const escaped = slug.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n return new RegExp(`(^|[^a-z0-9._/-])${escaped}($|[^a-z0-9._/-])`, \"i\").test(text);\n });\n if (matches.length === 1) {\n return { status: \"selected\", repositorySlug: matches[0]! };\n }\n if (matches.length > 1) {\n return { status: \"ambiguous\", repositorySlugs: matches };\n }\n return { status: \"required\" };\n}\n"],"mappings":";;AA2BA,SAAgB,sBAAsB,OAA8C;CAClF,OAAO;EACL,QAAQ;EACR,YAAY,GAAG,MAAM,OAAO,GAAG,MAAM,UAAU,GAAG,MAAM;EACxD,gBAAgB,MAAM,eAAe,YAAY;EACjD,SAAS;GAAE,SAAS;GAAS,SAAS,MAAM;EAAa;EACzD,SAAS;GACP,QAAQ,MAAM;GACd,WAAW,MAAM;GACjB,WAAW,MAAM;GACjB,UAAU,MAAM;GAChB,SAAS,MAAM;GACf,MAAM,MAAM;EACd;EACA,SAAS,MAAM;CACjB;AACF;;;;;AAWA,SAAgB,sBACd,MACA,cAC0B;CAC1B,MAAM,aAAa,CAAC,GAAG,IAAI,IAAI,aAAa,KAAK,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC;CAC9E,IAAI,WAAW,WAAW,GACxB,OAAO;EAAE,QAAQ;EAAY,gBAAgB,WAAW;CAAI;CAG9D,MAAM,UAAU,WAAW,QAAQ,SAAS;EAC1C,MAAM,UAAU,KAAK,QAAQ,uBAAuB,MAAM;EAC1D,OAAO,IAAI,OAAO,oBAAoB,QAAQ,oBAAoB,GAAG,CAAC,CAAC,KAAK,IAAI;CAClF,CAAC;CACD,IAAI,QAAQ,WAAW,GACrB,OAAO;EAAE,QAAQ;EAAY,gBAAgB,QAAQ;CAAI;CAE3D,IAAI,QAAQ,SAAS,GACnB,OAAO;EAAE,QAAQ;EAAa,iBAAiB;CAAQ;CAEzD,OAAO,EAAE,QAAQ,WAAW;AAC9B"}
@@ -1,5 +1,5 @@
1
1
  import { c as Finding, k as FactoryStores, t as ApprovalPolicy } from "../approval-h5aj8HYN.mjs";
2
- import { l as GitHubReviewComment } from "../github-5qQPCaXj.mjs";
2
+ import { d as GitHubReviewComment } from "../github-3sbHe-fo.mjs";
3
3
  import { z } from "zod";
4
4
  //#region src/eve/fetch.d.ts
5
5
  /** The subset of the eve sandbox handle a credentialed git operation needs. */
@@ -379,11 +379,17 @@ declare function invokeAgentTool(stores: FactoryStores, options: InvokeAgentOpti
379
379
  };
380
380
  //#endregion
381
381
  //#region src/eve/review.d.ts
382
- interface ReviewCommentToolOptions {
382
+ interface ReviewCommentToolOptions<S extends CommandSandbox = CommandSandbox> {
383
383
  tokenForRepository: (repository: string) => Promise<string>;
384
+ withCredential?: (sandbox: S, repository: string, command: string) => Promise<{
385
+ stdout: string;
386
+ stderr?: string;
387
+ exitCode?: number;
388
+ }>;
384
389
  fetch?: typeof globalThis.fetch;
390
+ repoPath?: string;
385
391
  }
386
- declare function reviewCommentTools(stores: FactoryStores, options: ReviewCommentToolOptions): {
392
+ declare function reviewCommentTools<S extends CommandSandbox>(stores: FactoryStores, options: ReviewCommentToolOptions<S>): {
387
393
  read_review_comment: import("eve/tools").ToolDefinition<{
388
394
  changeId: string;
389
395
  commentId: number;
@@ -404,6 +410,44 @@ declare function reviewCommentTools(stores: FactoryStores, options: ReviewCommen
404
410
  body: string;
405
411
  }, ctx: import("eve/tools").ToolContext): Promise<GitHubReviewComment>;
406
412
  };
413
+ suggest_review_fix: import("eve/tools").ToolDefinition<{
414
+ changeId: string;
415
+ commentId: number;
416
+ confidence: "medium";
417
+ replacement: string;
418
+ summary: string;
419
+ }, GitHubReviewComment> & {
420
+ execute(input: {
421
+ changeId: string;
422
+ commentId: number;
423
+ confidence: "medium";
424
+ replacement: string;
425
+ summary: string;
426
+ }, ctx: import("eve/tools").ToolContext): Promise<GitHubReviewComment>;
427
+ };
428
+ apply_review_fix: import("eve/tools").ToolDefinition<{
429
+ changeId: string;
430
+ commentId: number;
431
+ confidence: "high";
432
+ summary: string;
433
+ }, {
434
+ updated: true;
435
+ prNumber: number;
436
+ prUrl: string;
437
+ reply: GitHubReviewComment;
438
+ }> & {
439
+ execute(input: {
440
+ changeId: string;
441
+ commentId: number;
442
+ confidence: "high";
443
+ summary: string;
444
+ }, ctx: import("eve/tools").ToolContext): Promise<{
445
+ updated: true;
446
+ prNumber: number;
447
+ prUrl: string;
448
+ reply: GitHubReviewComment;
449
+ }>;
450
+ };
407
451
  };
408
452
  //#endregion
409
453
  //#region src/eve/verification.d.ts
@@ -1,6 +1,6 @@
1
1
  import { i as repositoryIdSchema, o as signalIdSchema, p as vocabularySchema, s as taskIdSchema, t as changeIdSchema } from "../id-DwCihQr4.mjs";
2
2
  import { S as findingFingerprint, b as requiresApproval, g as dispatchTask, p as levelSchema, s as taskQuestionSchema, u as changeEvidenceSchema, w as findingSchema, y as defaultApprovalPolicy } from "../task-Cj7Ei9Nh.mjs";
3
- import { answerReviewComment, readReviewComment } from "../github.mjs";
3
+ import { answerReviewComment, readPullRequestHead, readReviewComment } from "../github.mjs";
4
4
  import { z } from "zod";
5
5
  import { defineHook } from "eve/hooks";
6
6
  import { defineTool } from "eve/tools";
@@ -476,44 +476,120 @@ function invokeAgentTool(stores, options) {
476
476
  //#endregion
477
477
  //#region src/eve/review.ts
478
478
  function reviewCommentTools(stores, options) {
479
+ const repoPath = options.repoPath ?? "repo";
479
480
  const commentInputSchema = z.object({
480
481
  changeId: changeIdSchema.describe("The change under review, from your starting message."),
481
482
  commentId: z.number().int().positive().describe("The GitHub review comment id.")
482
483
  });
483
- async function context(changeId) {
484
- const change = await stores.changes.get(changeId);
485
- if (change === null) throw new Error(`change ${changeId} does not exist`);
486
- if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
487
- const repository = await stores.repositories.get(change.repositoryId);
488
- if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
489
- return {
490
- repository: repository.slug,
491
- token: await options.tokenForRepository(repository.slug),
492
- fetch: options.fetch,
493
- pullRequestNumber: change.prNumber
494
- };
495
- }
496
484
  return {
497
485
  read_review_comment: defineTool({
498
486
  description: "Read an inline review comment on a change under verification or ready for human review. By default this refuses to return comments from people without write access to the repository.",
499
487
  inputSchema: commentInputSchema,
500
488
  async execute({ changeId, commentId }) {
489
+ const change = await stores.changes.get(changeId);
490
+ if (change === null) throw new Error(`change ${changeId} does not exist`);
491
+ if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
492
+ const repository = await stores.repositories.get(change.repositoryId);
493
+ if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
501
494
  return readReviewComment({
502
- ...await context(changeId),
495
+ repository: repository.slug,
496
+ token: await options.tokenForRepository(repository.slug),
497
+ fetch: options.fetch,
503
498
  commentId
504
499
  });
505
500
  }
506
501
  }),
507
502
  answer_review_comment: defineTool({
508
- description: "Reply to an inline review comment on a change under verification or ready for human review. The repository and pull request come from the ledger, and the comment author must have write access.",
503
+ description: "Reply to an inline review comment on a change under verification or ready for human review. Use for findings and low-confidence responses, not for a concrete medium- or high-confidence fix.",
509
504
  inputSchema: commentInputSchema.extend({ body: z.string().min(1).max(5e3).describe("The Markdown reply to post.") }),
510
505
  async execute({ changeId, commentId, body }) {
506
+ const change = await stores.changes.get(changeId);
507
+ if (change === null) throw new Error(`change ${changeId} does not exist`);
508
+ if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
509
+ const repository = await stores.repositories.get(change.repositoryId);
510
+ if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
511
511
  return answerReviewComment({
512
- ...await context(changeId),
512
+ repository: repository.slug,
513
+ token: await options.tokenForRepository(repository.slug),
514
+ fetch: options.fetch,
515
+ pullRequestNumber: change.prNumber,
513
516
  commentId,
514
517
  body
515
518
  });
516
519
  }
520
+ }),
521
+ suggest_review_fix: defineTool({
522
+ description: "Post a concrete, apply-able GitHub suggested change for a medium-confidence fix. The replacement must cover exactly the lines attached to the inline review comment.",
523
+ inputSchema: commentInputSchema.extend({
524
+ confidence: z.literal("medium"),
525
+ replacement: z.string().min(1).max(5e3).describe("Exact replacement text for the commented lines."),
526
+ summary: z.string().min(1).max(1e3).describe("A brief explanation of the suggested fix.")
527
+ }),
528
+ async execute({ changeId, commentId, replacement, summary }) {
529
+ const change = await stores.changes.get(changeId);
530
+ if (change === null) throw new Error(`change ${changeId} does not exist`);
531
+ if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
532
+ const repository = await stores.repositories.get(change.repositoryId);
533
+ if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
534
+ return answerReviewComment({
535
+ repository: repository.slug,
536
+ token: await options.tokenForRepository(repository.slug),
537
+ fetch: options.fetch,
538
+ pullRequestNumber: change.prNumber,
539
+ commentId,
540
+ body: `${summary}\n\n\`\`\`suggestion\n${replacement}\n\`\`\``
541
+ });
542
+ }
543
+ }),
544
+ apply_review_fix: defineTool({
545
+ description: "Publish a tested, committed high-confidence fix to the existing same-repository pull request branch. Fails if the checkout is dirty, the PR changed, or the PR comes from a fork.",
546
+ inputSchema: commentInputSchema.extend({
547
+ confidence: z.literal("high"),
548
+ summary: z.string().min(1).max(1e3).describe("What was fixed and verified.")
549
+ }),
550
+ async execute({ changeId, commentId, summary }, ctx) {
551
+ const change = await stores.changes.get(changeId);
552
+ if (change === null) throw new Error(`change ${changeId} does not exist`);
553
+ if (change.state !== "verifying" && change.state !== "ready") throw new Error(`change is ${change.state}, not open for review`);
554
+ const repository = await stores.repositories.get(change.repositoryId);
555
+ if (repository === null) throw new Error(`repository ${change.repositoryId} does not exist`);
556
+ if (options.withCredential === void 0) throw new Error("applying review fixes is not configured");
557
+ const token = await options.tokenForRepository(repository.slug);
558
+ await readReviewComment({
559
+ repository: repository.slug,
560
+ token,
561
+ fetch: options.fetch,
562
+ commentId
563
+ });
564
+ const head = await readPullRequestHead({
565
+ repository: repository.slug,
566
+ token,
567
+ fetch: options.fetch,
568
+ pullRequestNumber: change.prNumber
569
+ });
570
+ if (head.repository !== repository.slug.toLowerCase()) throw new Error("cannot apply a fix to a pull request from a fork");
571
+ const sandbox = await ctx.getSandbox();
572
+ const git = `git -C ${repoPath}`;
573
+ if ((await sandbox.run({ command: `${git} status --porcelain` })).stdout.trim() !== "") throw new Error("commit the review fix before publishing");
574
+ if ((await sandbox.run({ command: `${git} merge-base --is-ancestor ${head.sha} HEAD` })).exitCode !== 0) throw new Error("pull request changed after it was fetched; fetch it again before applying the fix");
575
+ if ((await sandbox.run({ command: `${git} rev-parse HEAD` })).stdout.trim() === head.sha) throw new Error("no committed review fix to publish");
576
+ const push = await options.withCredential(sandbox, repository.slug, `${git} push --force-with-lease=refs/heads/${head.branch}:${head.sha} origin HEAD:refs/heads/${head.branch}`);
577
+ if (typeof push.exitCode === "number" && push.exitCode !== 0) throw new Error(`could not update pull request branch: ${(push.stderr ?? "").trim()}`);
578
+ const reply = await answerReviewComment({
579
+ repository: repository.slug,
580
+ token,
581
+ fetch: options.fetch,
582
+ pullRequestNumber: change.prNumber,
583
+ commentId,
584
+ body: summary
585
+ });
586
+ return {
587
+ updated: true,
588
+ prNumber: change.prNumber,
589
+ prUrl: change.prUrl,
590
+ reply
591
+ };
592
+ }
517
593
  })
518
594
  };
519
595
  }