@agent-native/core 0.164.15 → 0.164.18

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 (53) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/netlify.toml +4 -0
  3. package/corpus/templates/clips/actions/request-transcript.ts +10 -6
  4. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +298 -3
  5. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +3 -0
  6. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +18 -4
  7. package/corpus/templates/content/app/i18n-data.ts +160 -0
  8. package/corpus/templates/content/docs/solutions/2026-08-18-colored-text-highlighting-shape.md +693 -0
  9. package/corpus/templates/content/package.json +1 -0
  10. package/corpus/templates/content/shared/nfm.ts +14 -3
  11. package/corpus/templates/factory/AGENTS.md +24 -29
  12. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +8 -28
  13. package/corpus/templates/factory/actions/get-triage-config.ts +1 -0
  14. package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +67 -59
  15. package/corpus/templates/factory/actions/list-triage-items.ts +6 -1
  16. package/corpus/templates/factory/actions/reconcile-triage-run.ts +7 -1
  17. package/corpus/templates/factory/actions/save-triage-config.ts +27 -7
  18. package/corpus/templates/factory/actions/start-builder-for-item.ts +113 -31
  19. package/corpus/templates/factory/actions/view-screen.ts +12 -0
  20. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +457 -0
  21. package/corpus/templates/factory/app/components/factory/FactoryWorkspaceActions.tsx +53 -0
  22. package/corpus/templates/factory/app/components/layout/Layout.tsx +2 -0
  23. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +3 -1
  24. package/corpus/templates/factory/app/hooks/use-navigation-state.ts +10 -7
  25. package/corpus/templates/factory/app/i18n/en-US.ts +3 -0
  26. package/corpus/templates/factory/app/routes/agents.tsx +22 -1
  27. package/corpus/templates/factory/app/routes/factory-settings.tsx +34 -0
  28. package/corpus/templates/factory/app/routes/factory.tsx +33 -323
  29. package/corpus/templates/factory/server/connectors/slack.ts +62 -0
  30. package/corpus/templates/factory/server/db/schema.ts +1 -0
  31. package/corpus/templates/factory/server/lib/slack-feedback-prompt.ts +30 -0
  32. package/corpus/templates/factory/server/plugins/agent-chat.ts +9 -8
  33. package/corpus/templates/factory/server/plugins/factory-migrations.ts +7 -0
  34. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +43 -25
  35. package/corpus/templates/factory/server/triage/ai-services-git.ts +2 -0
  36. package/corpus/templates/factory/server/triage/pr-monitor.ts +2 -1
  37. package/corpus/templates/factory/server/triage/pr-policy.ts +197 -19
  38. package/corpus/templates/factory/server/triage/review-skill-alignment.ts +127 -0
  39. package/corpus/templates/factory/server/triage/slack-client.ts +79 -6
  40. package/dist/client/MultiTabAssistantChat.js +28 -1
  41. package/dist/client/use-chat-threads.d.ts +1 -0
  42. package/dist/client/use-chat-threads.js +13 -3
  43. package/dist/collab/awareness.d.ts +2 -2
  44. package/dist/collab/routes.d.ts +1 -1
  45. package/dist/collab/struct-routes.d.ts +1 -1
  46. package/dist/integrations/plugin.js +18 -1
  47. package/dist/notifications/routes.d.ts +3 -3
  48. package/dist/resources/handlers.d.ts +2 -2
  49. package/dist/secrets/routes.d.ts +9 -9
  50. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  51. package/dist/server/auth.js +2 -2
  52. package/dist/vite/client.js +2 -0
  53. package/package.json +1 -1
@@ -16,6 +16,15 @@ import { and, eq, isNull, lt, ne, or } from "drizzle-orm";
16
16
 
17
17
  import { getDb } from "../db/index.js";
18
18
  import { triageConfig } from "../db/schema.js";
19
+ import {
20
+ repairSlackFeedbackPrompt,
21
+ SLACK_HANDOFF_INSTRUCTION,
22
+ SLACK_MENTION_GUARD,
23
+ } from "../lib/slack-feedback-prompt.js";
24
+ import {
25
+ syncManagedReviewSkillAlignment,
26
+ type FactoryAutomationName,
27
+ } from "../triage/review-skill-alignment.js";
19
28
 
20
29
  const LEGACY_JOB_PATH = "jobs/factory-observation-scheduler.md";
21
30
  const DEFAULT_SLACK_CHANNEL_ID = "C0ATH3CCZT4";
@@ -165,8 +174,7 @@ type AutomationSeed = {
165
174
  body: string;
166
175
  };
167
176
 
168
- const FACTORY_DEFAULT_MODEL =
169
- process.env.FACTORY_AUTOMATION_MODEL?.trim() || "gpt-5.6-luna";
177
+ const FACTORY_DEFAULT_MODEL = "gpt-5.6-luna";
170
178
  const FACTORY_DEFAULT_MAX_ITERATIONS = 32;
171
179
  const FACTORY_DEFAULT_MAX_RUN_INPUT_TOKENS = 1_000_000;
172
180
  const SKIP_RECORD_GUARD =
@@ -220,13 +228,9 @@ The action adds 👀 to every grouped Slack thread but posts one Builder reply i
220
228
  the representative thread. Do not start one Builder thread per duplicate.
221
229
  Separate reports only when their failure modes, surfaces, or owners differ.
222
230
 
223
- The Builder reply must tag @builder.io with the dot and tell it to run
224
- /address-feedback. It must point Builder to the relevant repository skills,
225
- the representative source, every related source, and the need to fix the
226
- underlying boundary across the whole cluster. Never add the reaction or tag
227
- Builder for owner-managed Clips, Design, or Content work, or for a non-bug
228
- report.
229
- After classifying each processed item, call start-builder-for-item with clearBug true or false and a short evidence-grounded reason so the skip or dispatch is recorded.
231
+ ${SLACK_HANDOFF_INSTRUCTION}
232
+ ${SKIP_RECORD_GUARD}
233
+ ${SLACK_MENTION_GUARD}
230
234
 
231
235
  Keep each run bounded. Preserve action errors and do not claim a Builder reply,
232
236
  PR, merge, or fix unless an action returned that state.
@@ -322,21 +326,23 @@ clear bug fix or has product or UX implications. Avoid duplicate review noise
322
326
  when no commit, review, comment, or check result changed. Call
323
327
  govern-agent-native-pull-request with the item id, repository, pull request
324
328
  number, clearBug, productUxImplications, and a short reason. The action fetches
325
- fresh CI and review evidence before approving or merging. For a verified
326
- current BuilderIO member, the internal-author exception means ordinary failed,
327
- pending, skipped, or unknown checks and unresolved ordinary feedback do not by
328
- themselves block approval; record their exact states and never call them clean.
329
- The exception does not waive membership, ownership, or the ultra-scary gate.
330
-
331
- Only auto-merge when the PR proves its Factory origin by retaining the Factory
332
- item id or source link in the PR description, or by using the Factory branch
333
- name. A normal open PR must never be treated as a Builder-triggered run.
334
-
335
- Never auto-approve or auto-merge Clips, Design, or Content PRs. Those apps are
336
- fully owned by their product owners. Auto-merge is limited to PRs with a
337
- verified Factory Builder run; all other PRs can at most pass the approval
338
- policy. Do not call GitHub write actions directly or claim a merge unless the
339
- governance action confirms it.
329
+ fresh CI and review evidence before approving. For a verified current BuilderIO
330
+ member, the internal-author exception means ordinary failed, pending, skipped,
331
+ or unknown checks and unresolved ordinary feedback do not by themselves block
332
+ approval; record their exact states and never call them clean. Apply the
333
+ verified Alice/Content, Nick/Slides, Enzo/Factory-specific, Sid/Design, and
334
+ docs-only owner exceptions from review-prs only after membership and an
335
+ explicit ultra-scary assessment. Those exceptions do not waive membership,
336
+ external-author, or ultra-scary gates.
337
+
338
+ Never auto-merge. Approval is the only GitHub write this workflow may request;
339
+ a normal open PR must never be treated as a Builder-triggered run.
340
+
341
+ Never auto-dispatch Clips, Design, or Content feedback. Those apps remain
342
+ fully owned by their product owners for feedback work, while the verified
343
+ PR-owner exceptions still apply to their own scoped PRs. Do not call GitHub
344
+ write actions directly or claim an approval unless the governance action
345
+ confirms it.
340
346
  `,
341
347
  },
342
348
  {
@@ -439,6 +445,10 @@ function automationContent(
439
445
  orgId: string,
440
446
  seed: AutomationSeed,
441
447
  ): string {
448
+ const body = syncManagedReviewSkillAlignment(
449
+ seed.body.trim(),
450
+ seed.name as FactoryAutomationName,
451
+ );
442
452
  return `---
443
453
  schedule: "${seed.schedule}"
444
454
  ${seed.timezone ? `timezone: ${seed.timezone}\n` : ""}enabled: true
@@ -452,7 +462,7 @@ model: ${seed.model}
452
462
  maxIterations: ${seed.maxIterations}
453
463
  maxRunInputTokens: ${seed.maxRunInputTokens}
454
464
  ---
455
- ${seed.body.trim()}
465
+ ${body.trim()}
456
466
  `;
457
467
  }
458
468
 
@@ -535,6 +545,13 @@ async function ensureOrganizationAutomations(
535
545
  ) {
536
546
  repaired = `${repaired.trimEnd()}\n\n${SKIP_RECORD_GUARD}\n`;
537
547
  }
548
+ repaired = syncManagedReviewSkillAlignment(
549
+ repaired,
550
+ seed.name as FactoryAutomationName,
551
+ );
552
+ if (seed.name === "factory-slack-feedback") {
553
+ repaired = repairSlackFeedbackPrompt(repaired);
554
+ }
538
555
  if (repaired === existing.content) return;
539
556
 
540
557
  const updated = await resourcePutIfCurrent({
@@ -579,6 +596,7 @@ async function ensureDefaultTriageConfig(
579
596
  slackWorkspace: "primary",
580
597
  slackChannelId: DEFAULT_SLACK_CHANNEL_ID,
581
598
  slackChannelName: DEFAULT_SLACK_CHANNEL_NAME,
599
+ builderSlackUserId: null,
582
600
  pollingEnabled: 1,
583
601
  githubPollingEnabled: defaultGithubPollingEnabled(),
584
602
  sentryPollingEnabled: 0,
@@ -173,6 +173,8 @@ function normalizeReviewState(
173
173
  return "changes_requested";
174
174
  case "PENDING":
175
175
  return "pending";
176
+ case "DISMISSED":
177
+ return "dismissed";
176
178
  default:
177
179
  return "commented";
178
180
  }
@@ -4,7 +4,8 @@ export type PullRequestReviewState =
4
4
  | "approved"
5
5
  | "changes_requested"
6
6
  | "commented"
7
- | "pending";
7
+ | "pending"
8
+ | "dismissed";
8
9
 
9
10
  export interface PullRequestReviewObservation {
10
11
  author: string;
@@ -2,6 +2,13 @@ import type { GuardResult } from "./contracts.js";
2
2
 
3
3
  export type OwnerOwnedArea = "clips" | "design" | "content";
4
4
 
5
+ export type PullRequestOwnerException =
6
+ | "alice-content"
7
+ | "nick-slides"
8
+ | "enzo-factory"
9
+ | "sid-design"
10
+ | "docs-only";
11
+
5
12
  export interface PullRequestGovernanceInput {
6
13
  author: string;
7
14
  repository: string;
@@ -19,6 +26,7 @@ export interface PullRequestGovernanceInput {
19
26
 
20
27
  export interface PullRequestGovernanceDecision {
21
28
  ownerOwnedArea: OwnerOwnedArea | null;
29
+ ownerException: PullRequestOwnerException | null;
22
30
  autoApprove: boolean;
23
31
  autoMerge: boolean;
24
32
  reason: string;
@@ -69,6 +77,11 @@ export function decidePullRequestGovernance(
69
77
  input.summary,
70
78
  ...input.changedFiles,
71
79
  ]);
80
+ const ultraScary = isUltraScaryChange(input.changedFiles);
81
+ const ownerException = detectPullRequestOwnerException(input);
82
+ const verifiedOwnerException =
83
+ input.internalBuilderMember && !ultraScary ? ownerException : null;
84
+ const internalEvidenceException = input.internalBuilderMember;
72
85
  const gates: GuardResult[] = [
73
86
  {
74
87
  code: "identity",
@@ -79,17 +92,27 @@ export function decidePullRequestGovernance(
79
92
  },
80
93
  {
81
94
  code: "unknown_change",
82
- passed: input.clearBug,
83
- reason: input.clearBug
84
- ? "The automation classified this as a clear bug with a concrete failure signal."
85
- : "The automation did not establish a clear bug; product requests and guesses stay manual.",
95
+ passed: input.clearBug || verifiedOwnerException !== null,
96
+ reason:
97
+ input.clearBug || verifiedOwnerException !== null
98
+ ? "The automation classified this as a clear bug with a concrete failure signal."
99
+ : "The automation did not establish a clear bug; product requests and guesses stay manual.",
86
100
  },
87
101
  {
88
102
  code: "security",
89
- passed: input.checksPassed,
103
+ passed: !ultraScary,
104
+ reason: ultraScary
105
+ ? "Security-sensitive auth, tenant-isolation, execution, payment, or deployment changes always require manual review."
106
+ : "The changed paths do not match the ultra-scary manual-review categories.",
107
+ },
108
+ {
109
+ code: "security",
110
+ passed: internalEvidenceException || input.checksPassed,
90
111
  reason: input.checksPassed
91
112
  ? "All observed CI checks passed."
92
- : "CI is failing, cancelled, pending, or unavailable.",
113
+ : internalEvidenceException
114
+ ? "CI is failing, cancelled, pending, or unavailable; the verified internal-author exception does not treat that state as clean."
115
+ : "CI is failing, cancelled, pending, or unavailable.",
93
116
  },
94
117
  {
95
118
  code: "unknown_change",
@@ -100,21 +123,26 @@ export function decidePullRequestGovernance(
100
123
  },
101
124
  {
102
125
  code: "unknown_change",
103
- passed: input.reviewFeedbackHandled,
126
+ passed: internalEvidenceException || input.reviewFeedbackHandled,
104
127
  reason: input.reviewFeedbackHandled
105
128
  ? "All observed review feedback is fixed, resolved, replied to, or outdated."
106
- : "Review feedback is unanswered, unresolved, truncated, or otherwise unknown.",
129
+ : internalEvidenceException
130
+ ? "Review feedback is unanswered, unresolved, truncated, or otherwise unknown; the verified internal-author exception does not treat that state as clean."
131
+ : "Review feedback is unanswered, unresolved, truncated, or otherwise unknown.",
107
132
  },
108
133
  ];
109
134
 
110
- if (ownerOwnedArea) {
135
+ if (
136
+ ownerOwnedArea &&
137
+ !ownerExceptionCoversArea(verifiedOwnerException, ownerOwnedArea)
138
+ ) {
111
139
  gates.push({
112
140
  code: "owner_owned",
113
141
  passed: false,
114
142
  reason: `${ownerOwnedArea} is owner-managed and is never auto-approved, auto-merged, or dispatched by this Factory.`,
115
143
  });
116
144
  }
117
- if (input.productUxImplications) {
145
+ if (input.productUxImplications && verifiedOwnerException === null) {
118
146
  gates.push({
119
147
  code: "unknown_change",
120
148
  passed: false,
@@ -124,21 +152,171 @@ export function decidePullRequestGovernance(
124
152
 
125
153
  const baseEligible = gates.every((gate) => gate.passed);
126
154
  const autoApprove = baseEligible;
127
- const autoMerge = baseEligible && input.factoryTriggered;
128
- const reason = autoMerge
129
- ? "Clear internal bug fix, all CI and review gates are clean, and the PR came from the Factory Builder flow."
130
- : autoApprove
131
- ? "Clear internal bug fix with clean CI and review gates; approval is safe to automate."
132
- : gates
133
- .filter((gate) => !gate.passed)
134
- .map((gate) => gate.reason)
135
- .join(" ");
155
+ const autoMerge = false;
156
+ const reason = autoApprove
157
+ ? verifiedOwnerException
158
+ ? `Verified ${verifiedOwnerException} owner exception; approval is safe to automate while ordinary check and review states remain recorded.`
159
+ : "Clear internal bug fix with verified membership; approval is safe to automate while ordinary check and review states remain recorded."
160
+ : gates
161
+ .filter((gate) => !gate.passed)
162
+ .map((gate) => gate.reason)
163
+ .join(" ");
136
164
 
137
165
  return {
138
166
  ownerOwnedArea,
167
+ ownerException: verifiedOwnerException,
139
168
  autoApprove,
140
169
  autoMerge,
141
170
  reason,
142
171
  guardResults: gates,
143
172
  };
144
173
  }
174
+
175
+ export function detectPullRequestOwnerException(
176
+ input: Pick<PullRequestGovernanceInput, "author" | "changedFiles">,
177
+ ): PullRequestOwnerException | null {
178
+ const author = input.author.trim().toLowerCase();
179
+ const changedFiles = input.changedFiles.map(normalizePath);
180
+
181
+ if (author === "3mdistal" && isAppScoped(changedFiles, "content", true)) {
182
+ return "alice-content";
183
+ }
184
+ if (author === "nkoech123" && isAppScoped(changedFiles, "slides", true)) {
185
+ return "nick-slides";
186
+ }
187
+ if (author === "enzoames" && isFactoryScoped(changedFiles)) {
188
+ return "enzo-factory";
189
+ }
190
+ if (author === "sidmohanty11" && isAppScoped(changedFiles, "design", true)) {
191
+ return "sid-design";
192
+ }
193
+ if (
194
+ (author === "kapunahelewong" || author === "bwreid") &&
195
+ isDocsOnly(changedFiles)
196
+ ) {
197
+ return "docs-only";
198
+ }
199
+ return null;
200
+ }
201
+
202
+ export function hasCurrentPullRequestApproval(
203
+ reviews: readonly {
204
+ author: string;
205
+ state: string;
206
+ observedAt: string;
207
+ }[],
208
+ ): boolean {
209
+ const latestByAuthor = new Map<
210
+ string,
211
+ { state: string; observedAt: string; order: number }
212
+ >();
213
+ reviews.forEach((review, order) => {
214
+ const author = review.author.trim().toLowerCase();
215
+ if (!author) return;
216
+ const previous = latestByAuthor.get(author);
217
+ if (
218
+ !previous ||
219
+ Date.parse(review.observedAt) > Date.parse(previous.observedAt) ||
220
+ (review.observedAt === previous.observedAt && order > previous.order)
221
+ ) {
222
+ latestByAuthor.set(author, {
223
+ state: review.state,
224
+ observedAt: review.observedAt,
225
+ order,
226
+ });
227
+ }
228
+ });
229
+ return [...latestByAuthor.values()].some(
230
+ (review) => review.state === "approved",
231
+ );
232
+ }
233
+
234
+ export function isDocsOnly(changedFiles: readonly string[]): boolean {
235
+ return (
236
+ changedFiles.length > 0 &&
237
+ changedFiles.every((file) => {
238
+ const normalized = normalizePath(file);
239
+ return (
240
+ normalized.startsWith("docs/") ||
241
+ normalized.startsWith("packages/docs/") ||
242
+ normalized.startsWith("packages/core/docs/") ||
243
+ normalized.startsWith(".agents/skills/") ||
244
+ normalized.endsWith("/agents.md") ||
245
+ normalized.endsWith("/claude.md") ||
246
+ normalized === "agents.md" ||
247
+ normalized === "claude.md" ||
248
+ (normalized.startsWith(".changeset/") && normalized.endsWith(".md")) ||
249
+ (normalized.endsWith(".mdx") &&
250
+ (normalized.startsWith("docs/") ||
251
+ normalized.startsWith(".agents/skills/") ||
252
+ normalized.includes("/docs/")))
253
+ );
254
+ })
255
+ );
256
+ }
257
+
258
+ export function isUltraScaryChange(changedFiles: readonly string[]): boolean {
259
+ return changedFiles.some((file) => {
260
+ const normalized = normalizePath(file);
261
+ return (
262
+ normalized.startsWith(".github/workflows/") ||
263
+ /(^|\/)(auth|authentication|identity|credentials?|secrets?|sessions?|permissions?|tenant|tenants|isolation|security|execution|sandbox|payments?|billing|deploy|deployment|netlify|publish|release|migrations?)(\/|[-_.]|$)/.test(
264
+ normalized,
265
+ )
266
+ );
267
+ });
268
+ }
269
+
270
+ function ownerExceptionCoversArea(
271
+ exception: PullRequestOwnerException | null,
272
+ area: OwnerOwnedArea,
273
+ ): boolean {
274
+ return (
275
+ (exception === "alice-content" && area === "content") ||
276
+ (exception === "sid-design" && area === "design")
277
+ );
278
+ }
279
+
280
+ function isAppScoped(
281
+ changedFiles: readonly string[],
282
+ app: "content" | "slides" | "design",
283
+ allowSharedSupport: boolean,
284
+ ): boolean {
285
+ const appPrefix = `templates/${app}/`;
286
+ const hasAppPath = changedFiles.some((file) => file.startsWith(appPrefix));
287
+ if (!hasAppPath) return false;
288
+ return changedFiles.every(
289
+ (file) =>
290
+ file.startsWith(appPrefix) ||
291
+ (allowSharedSupport && isSharedSupportPath(file)) ||
292
+ isChangeset(file),
293
+ );
294
+ }
295
+
296
+ function isFactoryScoped(changedFiles: readonly string[]): boolean {
297
+ const hasFactoryPath = changedFiles.some((file) =>
298
+ file.startsWith("templates/factory/"),
299
+ );
300
+ return (
301
+ hasFactoryPath &&
302
+ changedFiles.every(
303
+ (file) => file.startsWith("templates/factory/") || isChangeset(file),
304
+ )
305
+ );
306
+ }
307
+
308
+ function isSharedSupportPath(file: string): boolean {
309
+ return (
310
+ file.startsWith("packages/core/") ||
311
+ file.startsWith("packages/desktop-app/") ||
312
+ file.startsWith("packages/code-agents-ui/")
313
+ );
314
+ }
315
+
316
+ function isChangeset(file: string): boolean {
317
+ return file.startsWith(".changeset/") && file.endsWith(".md");
318
+ }
319
+
320
+ function normalizePath(file: string): string {
321
+ return file.trim().split("\\").join("/").toLowerCase();
322
+ }
@@ -0,0 +1,127 @@
1
+ const ALIGNMENT_START = "<!-- factory-skill-alignment:start -->";
2
+ const ALIGNMENT_END = "<!-- factory-skill-alignment:end -->";
3
+
4
+ const FEEDBACK_ALIGNMENT = `## Current review-latest-feedback contract
5
+
6
+ The repository's \`.agents/skills/review-latest-feedback/SKILL.md\` is
7
+ authoritative. Re-read it in the Builder worktree before acting. Its current
8
+ contract is evidence-first and reply-producing:
9
+
10
+ - Recheck answered clarifications and open In progress ownership before newer
11
+ work. Keep the newest unhandled item as the cursor, read older duplicates
12
+ into one verified cluster, and leave an auditable disposition for every
13
+ inspected Slack, GitHub issue, and Sentry item.
14
+ - Read the complete parent/thread, reactions, attachments, linked artifacts,
15
+ and newer follow-ups before asking anything or dispatching. An unavailable
16
+ source is unavailable evidence, never a no-results result.
17
+ - Keep one specific unanswered clarification per thread. Re-read the whole
18
+ thread when the requested detail is answered, try the fix first, and use the
19
+ task-scoped clarification ledger with a stable scheduler identity for
20
+ recurring rechecks. Do not claim scheduled coverage without stable durable
21
+ state.
22
+ - Check the existing Slack 👀 marker and owner before any write. Preserve an
23
+ existing marker; if reactions cannot be read, do not guess or add one. Do
24
+ not react to or dispatch Design UX/interaction work (Sid) or any Content
25
+ work (Alice); record the owner instead.
26
+ - For an actionable repo-owned Slack item with no existing marker, 👀 is the
27
+ first external write. Every parent this run marks must later receive a
28
+ verified @agent-native Fixed, In progress, or Clarification needed reply;
29
+ an eye, forward, generic acknowledgement, or another person's reply is not
30
+ a disposition. Group only genuinely repeated symptoms and dispatch one
31
+ Builder thread for the cluster.
32
+ - Choose the smallest owning seam: local regression for one symptom, shared
33
+ contract for repeated cross-surface evidence, discovery/action wiring for a
34
+ missing capability, and release/deployment diagnosis for source-versus-live
35
+ mismatches. Do not turn one report into a global prompt rule.
36
+ - Keep source-tested, built, published/deployed, and observed-live evidence
37
+ separate. Do not claim a fix, PR, reply, or deployment without confirmation
38
+ from the relevant action or runtime evidence.
39
+
40
+ After classifying every processed item, call \`start-builder-for-item\` with
41
+ \`clearBug: true\` or \`false\` and a concise evidence-grounded reason so every
42
+ skip or dispatch is recorded.`;
43
+
44
+ const PR_ALIGNMENT = `## Current review-prs contract
45
+
46
+ The repository's \`.agents/skills/review-prs/SKILL.md\` is authoritative.
47
+ Before selecting work, ignore drafts and pull requests with a current,
48
+ non-dismissed APPROVED review, including bot approvals; do not inspect or
49
+ recap those excluded items. For every remaining PR, read the complete title,
50
+ body, links, changed-file diff (including generated and migration files), all
51
+ review summaries/comments/replies, actual check conclusions, and ownership
52
+ boundary. Verify current BuilderIO organization membership through GitHub's
53
+ organization API - never infer it from a name, association, branch, email, or
54
+ bot label.
55
+
56
+ Never approve external or unverified authors. The internal-author exception
57
+ allows ordinary failed, pending, skipped, or unknown checks and ordinary
58
+ unresolved feedback for a verified BuilderIO member; record those exact states
59
+ and never call them clean. The ultra-scary gate always remains manual for auth,
60
+ permissions, tenant isolation, secrets, destructive data loss or migrations,
61
+ remote code execution, SSRF, payments, deployment, or unexplained dependency
62
+ and infrastructure risk.
63
+
64
+ The verified owner exceptions are current and must be applied only after
65
+ membership and the ultra-scary assessment: Alice (\`3mdistal\`) for Content,
66
+ Nick (\`NKoech123\`) for Slides, Enzo (\`enzoames\`) for Factory-specific PRs,
67
+ and Sid (\`sidmohanty11\`) for Design. Alice and Nick may include supporting
68
+ shared framework/Desktop plumbing required by their app feature. The docs-only
69
+ exception applies to \`kapunahelewong\` and Wes (\`bwreid\`) only when every
70
+ changed file is documentation, localization, docs navigation/redirect, or a
71
+ docs-specific test. These exceptions cover ordinary UX/refactor/check/review
72
+ gates but never membership, external-author, or ultra-scary gates.
73
+
74
+ Approve only when the remaining standard evidence supports a clear,
75
+ repo-owned, narrow root-cause change with unambiguous scope. Approval is a
76
+ trust decision only - never auto-merge. Never claim ignored checks or feedback
77
+ are resolved, and record one concise disposition for every PR that entered the
78
+ evidence sweep.`;
79
+
80
+ export type FactoryAutomationName =
81
+ | "factory-slack-feedback"
82
+ | "factory-sentry-errors"
83
+ | "factory-github-issues"
84
+ | "factory-pr-governance"
85
+ | "factory-pr-babysit";
86
+
87
+ export function managedReviewSkillAlignment(
88
+ name: FactoryAutomationName,
89
+ ): string | undefined {
90
+ if (
91
+ name === "factory-slack-feedback" ||
92
+ name === "factory-sentry-errors" ||
93
+ name === "factory-github-issues"
94
+ ) {
95
+ return FEEDBACK_ALIGNMENT;
96
+ }
97
+ if (name === "factory-pr-governance") return PR_ALIGNMENT;
98
+ return undefined;
99
+ }
100
+
101
+ export function syncManagedReviewSkillAlignment(
102
+ content: string,
103
+ name: FactoryAutomationName,
104
+ ): string {
105
+ const alignment = managedReviewSkillAlignment(name);
106
+ if (!alignment) return content;
107
+
108
+ const block = `${ALIGNMENT_START}\n${alignment.trim()}\n${ALIGNMENT_END}`;
109
+ const existingBlock = new RegExp(
110
+ `${escapeRegExp(ALIGNMENT_START)}[\\s\\S]*?${escapeRegExp(ALIGNMENT_END)}`,
111
+ );
112
+ if (existingBlock.test(content)) {
113
+ return content.replace(existingBlock, block);
114
+ }
115
+ return `${content.trimEnd()}\n\n${block}\n`;
116
+ }
117
+
118
+ export function managedReviewSkillAlignmentMarkers(): {
119
+ start: string;
120
+ end: string;
121
+ } {
122
+ return { start: ALIGNMENT_START, end: ALIGNMENT_END };
123
+ }
124
+
125
+ function escapeRegExp(value: string): string {
126
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
+ }