@agent-native/core 0.161.8 → 0.161.10

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 (64) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/calendar/changelog/2026-08-18-calendar-event-time-editing-respects-the-event-timezone.md +6 -0
  3. package/corpus/templates/clips/app/components/player/video-player.tsx +1 -1
  4. package/corpus/templates/clips/app/i18n/en-US.ts +1 -1
  5. package/corpus/templates/clips/changelog/2026-08-18-centered-clip-play-button-and-comment-panel-hint.md +6 -0
  6. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +180 -37
  7. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +18 -3
  8. package/corpus/templates/design/app/components/layout/Layout.tsx +4 -1
  9. package/corpus/templates/design/app/hooks/use-navigation-state.ts +13 -2
  10. package/corpus/templates/design/app/i18n-data.ts +11 -0
  11. package/corpus/templates/design/app/lib/agent-chat.ts +30 -0
  12. package/corpus/templates/design/app/lib/builder-host-chat.ts +34 -0
  13. package/corpus/templates/design/app/lib/builder-host-origin.ts +31 -0
  14. package/corpus/templates/design/app/lib/embed-chrome.ts +70 -0
  15. package/corpus/templates/design/app/lib/shell-design.ts +113 -0
  16. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +89 -0
  17. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +85 -12
  18. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +43 -19
  19. package/corpus/templates/design/app/pages/design-editor/screen-command-utils.ts +9 -2
  20. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +13 -0
  21. package/corpus/templates/design/app/root.tsx +23 -1
  22. package/corpus/templates/design/server/lib/fusion-screens.ts +17 -1
  23. package/corpus/templates/design/server/plugins/builder-host-embed-headers.ts +37 -0
  24. package/corpus/templates/design/server/routes/[...page].get.ts +1 -0
  25. package/corpus/templates/design/shared/builder-preview-url.ts +113 -0
  26. package/corpus/templates/design/shared/full-app.ts +19 -0
  27. package/corpus/templates/design/shared/shell-screens.ts +139 -0
  28. package/corpus/templates/design/shared/source-mode.ts +10 -0
  29. package/corpus/templates/factory/actions/start-builder-for-item.ts +241 -22
  30. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -5
  31. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +50 -14
  32. package/corpus/templates/factory/server/triage/builder-executor.ts +1 -0
  33. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +231 -9
  34. package/corpus/templates/slides/app/context/DeckContext.tsx +99 -3
  35. package/corpus/templates/slides/app/i18n/en-US.ts +6 -0
  36. package/corpus/templates/slides/app/pages/DeckEditor.tsx +8 -2
  37. package/dist/agent/chat-health-alert.d.ts +25 -0
  38. package/dist/agent/chat-health-alert.js +132 -0
  39. package/dist/client/RuntimeConfigNotice.js +3 -0
  40. package/dist/client/api-surface.d.ts +19 -0
  41. package/dist/client/api-surface.js +32 -0
  42. package/dist/client/application-state.js +4 -0
  43. package/dist/client/builder-frame.d.ts +6 -0
  44. package/dist/client/builder-frame.js +1 -1
  45. package/dist/client/client-status-requests.js +5 -0
  46. package/dist/client/host/index.d.ts +1 -0
  47. package/dist/client/host/index.js +1 -0
  48. package/dist/client/use-action.d.ts +1 -1
  49. package/dist/client/use-action.js +17 -0
  50. package/dist/client/use-session.js +5 -0
  51. package/dist/collab/awareness.d.ts +2 -2
  52. package/dist/collab/routes.d.ts +1 -1
  53. package/dist/observability/routes.d.ts +5 -5
  54. package/dist/progress/routes.d.ts +1 -1
  55. package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
  56. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  57. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  58. package/dist/secrets/routes.d.ts +9 -9
  59. package/dist/server/agent-chat-plugin.js +15 -2
  60. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  61. package/dist/server/realtime-token.d.ts +1 -1
  62. package/dist/server/transcribe-voice.d.ts +1 -1
  63. package/package.json +1 -1
  64. /package/corpus/templates/design/app/routes/{visual-edit.$id.tsx → visual-edit_.$id.tsx} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { defineAction } from "@agent-native/core/action";
2
- import { and, eq } from "drizzle-orm";
2
+ import { and, eq, inArray } from "drizzle-orm";
3
3
  import { z } from "zod";
4
4
 
5
5
  import { getDb } from "../server/db/index.js";
@@ -26,22 +26,107 @@ import { detectOwnerOwnedArea } from "../server/triage/pr-policy.js";
26
26
  import { createSlackReader } from "../server/triage/slack-client.js";
27
27
 
28
28
  const replyTextPrefix =
29
- "@builderio please fix this in a reply. This is a clear bug based on the feedback above. Please send a PR when ready.";
29
+ "@builder.io please run /address-feedback in the repo to address this feedback. Read the address-feedback, address-feedback-with-replies, review-latest-feedback, and review-prs skills as relevant, inspect the full thread and linked evidence, and fix the owning boundary. Please send a PR when ready.";
30
+ const legacyReplyTextPrefix = "@builderio please fix this in a reply.";
30
31
 
31
- function replyTextForItem(item: {
32
+ type RelatedFeedbackItem = {
32
33
  id: string;
33
34
  sourceUrl: string | null;
34
- }): string {
35
+ title: string;
36
+ };
37
+
38
+ const startedTriageRunStatuses = new Set([
39
+ "submitted",
40
+ "acknowledged",
41
+ "running",
42
+ "completed",
43
+ "timed_out",
44
+ "reconciliation_required",
45
+ ]);
46
+
47
+ export function hasFeedbackCluster(metadata: Record<string, unknown>): boolean {
48
+ return (
49
+ (typeof metadata.feedbackClusterRepresentativeId === "string" &&
50
+ metadata.feedbackClusterRepresentativeId.trim().length > 0) ||
51
+ (Array.isArray(metadata.feedbackClusterItemIds) &&
52
+ metadata.feedbackClusterItemIds.length > 0)
53
+ );
54
+ }
55
+
56
+ export function isStartedTriageRunStatus(status: string): boolean {
57
+ return startedTriageRunStatuses.has(status);
58
+ }
59
+
60
+ export function relatedDispatchConflictReason(
61
+ item: Pick<RelatedFeedbackItem, "id">,
62
+ metadata: Record<string, unknown>,
63
+ runStatuses: readonly string[],
64
+ ): string | null {
65
+ if (hasFeedbackCluster(metadata)) {
66
+ return `Related Factory item ${item.id} already belongs to a feedback cluster.`;
67
+ }
68
+ if (runStatuses.some(isStartedTriageRunStatus)) {
69
+ return `Related Factory item ${item.id} already has a started Builder run.`;
70
+ }
71
+ return null;
72
+ }
73
+
74
+ export function ownerOwnedAreaValuesForItem(
75
+ item: Pick<RelatedFeedbackItem, "title"> & {
76
+ summary: string | null;
77
+ repository: string | null;
78
+ },
79
+ metadata: Record<string, unknown>,
80
+ ): Array<string | undefined> {
81
+ return [
82
+ item.title,
83
+ item.summary ?? undefined,
84
+ item.repository ?? undefined,
85
+ typeof metadata.productArea === "string" ? metadata.productArea : undefined,
86
+ typeof metadata.path === "string" ? metadata.path : undefined,
87
+ ];
88
+ }
89
+
90
+ export function replyTextForItem(
91
+ item: {
92
+ id: string;
93
+ sourceUrl: string | null;
94
+ },
95
+ relatedItems: RelatedFeedbackItem[] = [],
96
+ ): string {
97
+ const relatedText =
98
+ relatedItems.length > 0
99
+ ? [
100
+ "Related feedback in the same issue cluster - inspect and fix all of these too:",
101
+ ...relatedItems.map(
102
+ (related) =>
103
+ `- ${related.title} (${related.id})${related.sourceUrl ? `: ${related.sourceUrl}` : ""}`,
104
+ ),
105
+ ]
106
+ : [];
35
107
  return [
36
108
  replyTextPrefix,
37
109
  `Factory item: ${item.id}`,
38
110
  item.sourceUrl ? `Source: ${item.sourceUrl}` : "",
39
- "Please include the Factory item and source link in the PR description.",
111
+ ...relatedText,
112
+ "Please include the Factory item, every related item, and their source links in the PR description, and make sure the fix covers the whole cluster rather than one report.",
40
113
  ]
41
114
  .filter(Boolean)
42
115
  .join("\n");
43
116
  }
44
117
 
118
+ function relatedFeedbackSummary(relatedItems: RelatedFeedbackItem[]): string {
119
+ if (relatedItems.length === 0) return "";
120
+ return [
121
+ "Related feedback in the same issue cluster:",
122
+ ...relatedItems.map(
123
+ (related) =>
124
+ `- ${related.title} (${related.id})${related.sourceUrl ? `: ${related.sourceUrl}` : ""}`,
125
+ ),
126
+ "Read and address every related report in one change.",
127
+ ].join("\n");
128
+ }
129
+
45
130
  async function writeMetadata(
46
131
  itemId: string,
47
132
  orgId: string,
@@ -104,17 +189,31 @@ async function recordDecision(input: {
104
189
 
105
190
  export default defineAction({
106
191
  description:
107
- "Start the governed clear-bug Builder flow for a Factory item. Slack items are kept in-thread by adding 👀 and tagging @builderio; GitHub issues and Sentry errors use the Builder agent run API. Owner-managed Clips, Design, and Content items are always left for their owner.",
192
+ "Start the governed clear-bug Builder flow for a Factory item. Slack items are kept in-thread by adding 👀 and tagging @builder.io; grouped Slack repeats share one Builder thread. GitHub issues and Sentry errors use the Builder agent run API. Owner-managed Clips, Design, and Content items are always left for their owner.",
108
193
  schema: z.object({
109
194
  itemId: z.string().min(1),
110
195
  clearBug: z.boolean(),
111
196
  reason: z.string().trim().min(1).max(4_000),
112
197
  productUxImplications: z.boolean().default(false),
113
198
  clearErrorReport: z.string().trim().max(8_000).optional(),
199
+ relatedItemIds: z
200
+ .array(z.string().trim().min(1))
201
+ .max(10)
202
+ .default([])
203
+ .describe(
204
+ "Other Factory item ids in the same verified feedback cluster; dispatches one Builder thread for the whole group.",
205
+ ),
114
206
  }),
115
207
  http: false,
116
208
  run: async (
117
- { itemId, clearBug, reason, productUxImplications, clearErrorReport },
209
+ {
210
+ itemId,
211
+ clearBug,
212
+ reason,
213
+ productUxImplications,
214
+ clearErrorReport,
215
+ relatedItemIds,
216
+ },
118
217
  context,
119
218
  ) => {
120
219
  const { userEmail, orgId } = await requireWorkspaceMember(
@@ -135,6 +234,65 @@ export default defineAction({
135
234
  )[0];
136
235
  if (!item) throw new Error("Factory item not found.");
137
236
  const metadata = parseTriageMetadata(item.metadataJson);
237
+ const relatedIds = [...new Set(relatedItemIds)].filter(
238
+ (relatedId) => relatedId !== itemId,
239
+ );
240
+ const relatedItems =
241
+ relatedIds.length > 0
242
+ ? await db
243
+ .select()
244
+ .from(triageItems)
245
+ .where(
246
+ and(
247
+ eq(triageItems.orgId, orgId),
248
+ inArray(triageItems.id, relatedIds),
249
+ ),
250
+ )
251
+ : [];
252
+ if (relatedItems.length !== relatedIds.length) {
253
+ throw new Error("One or more related Factory items were not found.");
254
+ }
255
+ if (relatedItems.some((related) => related.source !== item.source)) {
256
+ throw new Error(
257
+ "Grouped Factory items must come from the same feedback source.",
258
+ );
259
+ }
260
+ if (
261
+ item.source === "slack" &&
262
+ relatedItems.some((related) => !related.channelId || !related.threadTs)
263
+ ) {
264
+ throw new Error(
265
+ "Grouped Slack feedback is missing a channel or thread identity.",
266
+ );
267
+ }
268
+ const relatedMetadata = new Map(
269
+ relatedItems.map((related) => [
270
+ related.id,
271
+ parseTriageMetadata(related.metadataJson),
272
+ ]),
273
+ );
274
+ const relatedRunRows =
275
+ relatedIds.length > 0
276
+ ? await db
277
+ .select({ itemId: triageRuns.itemId, status: triageRuns.status })
278
+ .from(triageRuns)
279
+ .where(
280
+ and(
281
+ eq(triageRuns.orgId, orgId),
282
+ inArray(triageRuns.itemId, relatedIds),
283
+ ),
284
+ )
285
+ : [];
286
+ for (const related of relatedItems) {
287
+ const conflictReason = relatedDispatchConflictReason(
288
+ related,
289
+ relatedMetadata.get(related.id) ?? {},
290
+ relatedRunRows
291
+ .filter((run) => run.itemId === related.id)
292
+ .map((run) => run.status),
293
+ );
294
+ if (conflictReason) throw new Error(conflictReason);
295
+ }
138
296
  const ownerOwnedArea = detectOwnerOwnedArea([
139
297
  item.title,
140
298
  item.summary,
@@ -144,6 +302,18 @@ export default defineAction({
144
302
  : undefined,
145
303
  typeof metadata.path === "string" ? metadata.path : undefined,
146
304
  ]);
305
+ const relatedOwnerOwnedArea =
306
+ relatedItems
307
+ .map((related) =>
308
+ detectOwnerOwnedArea(
309
+ ownerOwnedAreaValuesForItem(
310
+ related,
311
+ relatedMetadata.get(related.id) ?? {},
312
+ ),
313
+ ),
314
+ )
315
+ .find(Boolean) ?? null;
316
+ const ownerManagedArea = ownerOwnedArea ?? relatedOwnerOwnedArea ?? null;
147
317
  const guardResults = [
148
318
  {
149
319
  code: "unknown_change",
@@ -159,12 +329,12 @@ export default defineAction({
159
329
  ? "Product or UX implications require manual ownership."
160
330
  : "No product or UX decision was detected.",
161
331
  },
162
- ...(ownerOwnedArea
332
+ ...(ownerManagedArea
163
333
  ? [
164
334
  {
165
335
  code: "owner_owned",
166
336
  passed: false,
167
- reason: `${ownerOwnedArea} is fully owned by its product owner and is excluded from autonomous Builder work.`,
337
+ reason: `${ownerManagedArea} is fully owned by its product owner and is excluded from autonomous Builder work.`,
168
338
  },
169
339
  ]
170
340
  : []),
@@ -198,7 +368,7 @@ export default defineAction({
198
368
  decisionId,
199
369
  clearBug,
200
370
  productUxImplications,
201
- ownerOwnedArea: ownerOwnedArea ?? null,
371
+ ownerOwnedArea: ownerManagedArea,
202
372
  guardResults,
203
373
  },
204
374
  },
@@ -302,13 +472,20 @@ export default defineAction({
302
472
  const workspace =
303
473
  config?.slackWorkspace === "secondary" ? "secondary" : "primary";
304
474
  const slack = createSlackReader({ ownerEmail: userEmail, orgId });
305
- if (!metadataBoolean(metadata, "slackEyesReactedAt")) {
475
+ for (const feedbackItem of [item, ...relatedItems]) {
476
+ const feedbackMetadata =
477
+ feedbackItem.id === itemId
478
+ ? metadata
479
+ : parseTriageMetadata(feedbackItem.metadataJson);
480
+ if (metadataBoolean(feedbackMetadata, "slackEyesReactedAt")) {
481
+ continue;
482
+ }
306
483
  const reaction = await slack.addEyesReaction(
307
484
  workspace,
308
- item.channelId,
309
- item.threadTs,
485
+ feedbackItem.channelId!,
486
+ feedbackItem.threadTs!,
310
487
  );
311
- await writeMetadata(itemId, orgId, {
488
+ await writeMetadata(feedbackItem.id, orgId, {
312
489
  slackEyesReactedAt: new Date().toISOString(),
313
490
  slackEyesReactionAlreadyPresent: reaction.already_present,
314
491
  });
@@ -319,11 +496,21 @@ export default defineAction({
319
496
  item.threadTs,
320
497
  100,
321
498
  );
499
+ const hasCurrentBuilderReply = thread.messages.some((message) =>
500
+ message.text.includes(replyTextPrefix),
501
+ );
502
+ const hasLegacyBuilderReply = thread.messages.some((message) =>
503
+ message.text.includes(legacyReplyTextPrefix),
504
+ );
505
+ const hasRelatedClusterDetails = thread.messages.some((message) =>
506
+ message.text.includes(
507
+ "Related feedback in the same issue cluster - inspect and fix all of these too:",
508
+ ),
509
+ );
322
510
  if (
323
511
  thread.has_more &&
324
- !thread.messages.some((message) =>
325
- message.text.includes(replyTextPrefix),
326
- )
512
+ !hasCurrentBuilderReply &&
513
+ !hasLegacyBuilderReply
327
514
  ) {
328
515
  throw new Error(
329
516
  "Slack thread is truncated; refusing to risk a duplicate Builder handoff.",
@@ -331,21 +518,47 @@ export default defineAction({
331
518
  }
332
519
  if (
333
520
  !metadataBoolean(metadata, "slackBuilderReplyAt") &&
334
- !thread.messages.some((message) =>
335
- message.text.includes(replyTextPrefix),
336
- )
521
+ ((!hasCurrentBuilderReply && !hasLegacyBuilderReply) ||
522
+ (relatedItems.length > 0 && !hasRelatedClusterDetails))
337
523
  ) {
338
524
  const posted = await slack.postThreadReply(
339
525
  workspace,
340
526
  item.channelId,
341
527
  item.threadTs,
342
- replyTextForItem(item),
528
+ replyTextForItem(item, relatedItems),
343
529
  );
344
530
  await writeMetadata(itemId, orgId, {
345
531
  slackBuilderReplyAt: new Date().toISOString(),
346
532
  slackBuilderReplyTs: posted.ts,
347
533
  });
348
534
  }
535
+ const clusterItemIds = [itemId, ...relatedItems.map(({ id }) => id)];
536
+ const clusterMetadata = {
537
+ feedbackClusterRepresentativeId: itemId,
538
+ feedbackClusterItemIds: clusterItemIds,
539
+ feedbackClusterGroupedAt: new Date().toISOString(),
540
+ };
541
+ await writeMetadata(itemId, orgId, clusterMetadata);
542
+ for (const related of relatedItems) {
543
+ await writeMetadata(related.id, orgId, clusterMetadata);
544
+ await db
545
+ .update(triageItems)
546
+ .set({
547
+ status: "automation_started",
548
+ updatedAt: new Date().toISOString(),
549
+ })
550
+ .where(
551
+ and(eq(triageItems.id, related.id), eq(triageItems.orgId, orgId)),
552
+ );
553
+ await recordDecision({
554
+ itemId: related.id,
555
+ userEmail,
556
+ orgId,
557
+ outcome: "propose_fix",
558
+ reason: `Grouped with Factory item ${itemId}: ${reason}`,
559
+ guardResults,
560
+ });
561
+ }
349
562
  await db
350
563
  .update(triageItems)
351
564
  .set({
@@ -363,7 +576,11 @@ export default defineAction({
363
576
  source: item.source,
364
577
  sourceUrl: item.sourceUrl,
365
578
  summary: "Tagged Builder in the Slack thread and requested a PR.",
366
- details: { provider: "bot-tag", runId },
579
+ details: {
580
+ provider: "bot-tag",
581
+ runId,
582
+ relatedItemIds: relatedItems.map(({ id }) => id),
583
+ },
367
584
  },
368
585
  );
369
586
  return {
@@ -385,8 +602,10 @@ export default defineAction({
385
602
  summary: item.summary,
386
603
  sourceUrl: item.sourceUrl,
387
604
  instructions: [
605
+ "Run /address-feedback in the repository and read the address-feedback, address-feedback-with-replies, review-latest-feedback, and review-prs skills as relevant. Inspect all linked evidence and repeat reports, then fix the smallest owning boundary and verify it before opening the PR.",
388
606
  reason,
389
607
  clearErrorReport ? `Error report:\n${clearErrorReport}` : "",
608
+ relatedFeedbackSummary(relatedItems),
390
609
  ]
391
610
  .filter(Boolean)
392
611
  .join("\n\n"),
@@ -87,11 +87,15 @@ requests, broad UX suggestions, vague questions, and incomplete context stay
87
87
  manual. Clips, Design, and Content are fully owner-managed: never react, tag
88
88
  Builder, auto-approve, or auto-merge those items. Slack clear bugs use the
89
89
  thread-preserving start-builder-for-item flow; GitHub and Sentry clear bugs use
90
- the Builder agent-run flow. For pull requests, auto-approval requires an internal
91
- BuilderIO author, a clear bug, passing CI, and handled review feedback. Auto-merge
92
- also requires a verified Factory Builder run. When a user says to do a review-gated
93
- item now, use the explicit approval action, which records the approver and applies
94
- the rule's configured executor policy. Keep Slack replies concise and link to the
90
+ the Builder agent-run flow. Slack repeat reports must be clustered by underlying
91
+ symptom, with one Builder thread for the cluster and 👀 on every grouped report.
92
+ Use /address-feedback for the repository feedback workflow. For pull requests,
93
+ follow review-prs: read the complete diff and review evidence, verify current
94
+ BuilderIO membership, preserve the ultra-scary safety gate, and distinguish
95
+ unknown or unresolved checks from clean ones. Auto-merge also requires a
96
+ verified Factory Builder run. When a user says to do a review-gated item now,
97
+ use the explicit approval action, which records the approver and applies the
98
+ rule's configured executor policy. Keep Slack replies concise and link to the
95
99
  Factory item when a review is needed. The scheduled builder-io-bot PR babysitter
96
100
  posts its exact feedback-fix request through GitHub, persists a 20-minute quiet
97
101
  window, and never approves or merges.`,
@@ -181,12 +181,21 @@ const AUTOMATION_SEEDS: AutomationSeed[] = [
181
181
  body: `
182
182
  # Factory Slack feedback triage
183
183
 
184
- Read the Factory configuration. When Slack polling is enabled and a channel is
185
- configured, call poll-slack-channel first. Then list at most 2 new or changed
186
- Slack items by passing needsReview true, source slack, and limit 2. Process
187
- them sequentially, and call get-slack-feedback-context for each item before
188
- classifying it. Never list the full queue or use the action's default page
189
- size.
184
+ Follow the repository's address-feedback, address-feedback-with-replies, and
185
+ review-latest-feedback skills for this workflow, and use review-prs when a PR
186
+ needs review. Read the Factory configuration. When Slack polling is enabled
187
+ and a channel is configured, call poll-slack-channel first. Then list at most 5
188
+ new or changed Slack items by passing needsReview true, source slack, and limit
189
+ 5. Use one additional bounded recent Slack lookup with needsReview false,
190
+ source slack, and limit 20 only to find possible repeat reports; never list the
191
+ full queue or use an action's default page size.
192
+
193
+ Call get-slack-feedback-context for every new item and every possible repeat
194
+ that may belong to the same issue before classifying it. Read the full parent,
195
+ replies, reactions, and linked evidence. An unreadable or truncated thread is
196
+ not a clear bug and must stay manual. Search recent Slack history, local Git
197
+ history, merged PRs, and linked issues for repeats or an existing fix when
198
+ those sources are available.
190
199
 
191
200
  Start work only for a clear bug: a concrete broken behavior, reproducible
192
201
  failure, error, regression, stuck run, incorrect result, or a report with a
@@ -201,10 +210,20 @@ and concrete auth or configuration failures. Use those examples to recognize
201
210
  the shape of a bug, not to turn similar-sounding reports into automatic work.
202
211
 
203
212
  For a clear bug outside Clips, Design, and Content, call start-builder-for-item
204
- with clearBug true and a short evidence-grounded reason. That action adds the
205
- eyes reaction and tags @builderio in the Slack thread, asking Builder to fix
206
- it in a reply and send a PR. Never add the reaction or tag Builder for
207
- owner-managed Clips, Design, or Content work, or for a non-bug report.
213
+ with clearBug true and a short evidence-grounded reason. If multiple reports
214
+ describe the same underlying issue, treat them as one similar-feedback cluster:
215
+ read and eyeball every report, choose one representative, and call
216
+ start-builder-for-item once with the other Factory item ids in relatedItemIds.
217
+ The action adds 👀 to every grouped Slack thread but posts one Builder reply in
218
+ the representative thread. Do not start one Builder thread per duplicate.
219
+ Separate reports only when their failure modes, surfaces, or owners differ.
220
+
221
+ The Builder reply must tag @builder.io with the dot and tell it to run
222
+ /address-feedback. It must point Builder to the relevant repository skills,
223
+ the representative source, every related source, and the need to fix the
224
+ underlying boundary across the whole cluster. Never add the reaction or tag
225
+ Builder for owner-managed Clips, Design, or Content work, or for a non-bug
226
+ report.
208
227
 
209
228
  Keep each run bounded. Preserve action errors and do not claim a Builder reply,
210
229
  PR, merge, or fix unless an action returned that state.
@@ -274,6 +293,19 @@ confirmation.
274
293
  body: `
275
294
  # Factory pull-request governance
276
295
 
296
+ Follow the repository's review-prs skill. Read the full PR title, body, linked
297
+ issue and source links, complete changed-file diff including generated and
298
+ migration files, every human and bot review comment and reply, actual check
299
+ conclusions, and the affected ownership boundary. Verify current BuilderIO
300
+ organization membership through the GitHub organization API; never infer it
301
+ from a name, email, association, branch, or bot label. Never approve external
302
+ or unverified authors. Apply the skill's ultra-scary gate for auth, permissions,
303
+ tenant isolation, secrets, destructive data loss, RCE, SSRF, payments,
304
+ deployment, or unexplained dependency and infrastructure risk. Record failed,
305
+ pending, skipped, unknown, and unresolved ordinary feedback accurately - never
306
+ call it clean just because the author is internal. Sid's verified Design-owner
307
+ exception still does not waive membership or the ultra-scary gate.
308
+
277
309
  Read the Factory configuration. When GitHub polling is enabled and a repository
278
310
  is configured, call poll-github-sources with includeIssues false and
279
311
  includePullRequests true. List at most 3 new or changed pull requests by
@@ -281,11 +313,15 @@ passing needsReview true, source github, and limit 3. Never list the full queue
281
313
  or use the action's default page size.
282
314
 
283
315
  For each open agent-native PR, inspect the item and classify whether it is a
284
- clear bug fix or has product or UX implications. Call
316
+ clear bug fix or has product or UX implications. Avoid duplicate review noise
317
+ when no commit, review, comment, or check result changed. Call
285
318
  govern-agent-native-pull-request with the item id, repository, pull request
286
319
  number, clearBug, productUxImplications, and a short reason. The action fetches
287
- fresh CI and review evidence before approving or merging. Clear internal bug
288
- fixes with passing CI and handled review feedback may be auto-approved.
320
+ fresh CI and review evidence before approving or merging. For a verified
321
+ current BuilderIO member, the internal-author exception means ordinary failed,
322
+ pending, skipped, or unknown checks and unresolved ordinary feedback do not by
323
+ themselves block approval; record their exact states and never call them clean.
324
+ The exception does not waive membership, ownership, or the ultra-scary gate.
289
325
 
290
326
  Only auto-merge when the PR proves its Factory origin by retaining the Factory
291
327
  item id or source link in the PR description, or by using the Factory branch
@@ -352,7 +388,7 @@ function defaultGithubPollingEnabled(): 0 | 1 {
352
388
  function automationPromptGuard(name: string): string | undefined {
353
389
  switch (name) {
354
390
  case "factory-slack-feedback":
355
- return "Runtime safety bound: call list-triage-items with needsReview true, source slack, and limit 2; process at most two Slack items sequentially, and never use the default page size.";
391
+ return "Runtime safety bound: call list-triage-items with needsReview true, source slack, and limit 5; process at most five new Slack items sequentially, and never use the default page size.";
356
392
  case "factory-sentry-errors":
357
393
  return "Runtime safety bound: call list-triage-items with needsReview true, source sentry, and limit 3; process at most three Sentry items.";
358
394
  case "factory-github-issues":
@@ -71,6 +71,7 @@ export async function startBuilderRun(
71
71
  input.sourceUrl ? `Source: ${input.sourceUrl}` : "",
72
72
  input.repository ? `Repository: ${input.repository}` : "",
73
73
  input.instructions ? `Triage instructions: ${input.instructions}` : "",
74
+ "Run /address-feedback in the repository and read the address-feedback, address-feedback-with-replies, review-latest-feedback, and review-prs skills as relevant. Search for repeat reports, fix the smallest owning boundary, and verify the full path before opening the pull request.",
74
75
  "Inspect the repository, make the smallest safe fix, run validation, and open a pull request.",
75
76
  "Include the Factory item id and source link in the pull request description.",
76
77
  "Do not touch protected areas: auth/session/identity, credentials/vault, migrations, payments, security, or publishable packages.",