@agent-native/core 0.203.1 → 0.203.2-nightly-20261007213109

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 (25) hide show
  1. package/dist/agent/thread-message-projection.d.ts +1 -0
  2. package/dist/agent/thread-message-projection.js +56 -6
  3. package/dist/client/chat/agentkit-agent-native.js +104 -24
  4. package/dist/collab/awareness.d.ts +2 -2
  5. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  6. package/dist/notifications/routes.d.ts +3 -3
  7. package/dist/observability/routes.d.ts +4 -4
  8. package/dist/observability/store.js +11 -5
  9. package/dist/observability/traces.js +6 -0
  10. package/dist/observability/types.d.ts +2 -0
  11. package/dist/progress/routes.d.ts +1 -1
  12. package/dist/resources/handlers.d.ts +1 -1
  13. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  14. package/dist/server/agent-engine-default-model-route.d.ts +1 -1
  15. package/dist/server/core-routes-plugin.js +14 -6
  16. package/dist/server/realtime-token.d.ts +1 -1
  17. package/dist/sharing/actions/list-resource-shares.d.ts +1 -1
  18. package/dist/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +34 -44
  19. package/dist/templates/factory/.agents/skills/review-latest-feedback/references/reproduction-and-verification.md +46 -0
  20. package/dist/templates/factory/.agents/skills/review-prs/SKILL.md +5 -3
  21. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  22. package/package.json +3 -3
  23. package/src/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +34 -44
  24. package/src/templates/factory/.agents/skills/review-latest-feedback/references/reproduction-and-verification.md +46 -0
  25. package/src/templates/factory/.agents/skills/review-prs/SKILL.md +5 -3
@@ -9,6 +9,7 @@ export declare function projectRootAssistantMessages(input: {
9
9
  }): {
10
10
  representedRootMessageIds: Set<string>;
11
11
  snapshotMessageIdsByRootMessageId: Map<string, string>;
12
+ snapshotRunIdsByMessageId: Map<string, string>;
12
13
  };
13
14
  /** Root assistant IDs represented by the AgentKit snapshot. */
14
15
  export declare function representedRootAssistantMessageIds(input: {
@@ -80,11 +80,13 @@ function eventMessageIdsByRun(runIdsByMessageId) {
80
80
  }
81
81
  return result;
82
82
  }
83
- function assistantRunId(value, runIdsByMessageId) {
83
+ function assistantRunId(value, runIdsByMessageId, inferredRunId) {
84
84
  if (value.role !== "assistant")
85
85
  return undefined;
86
86
  const eventRunIds = typeof value.id === "string" ? runIdsByMessageId.get(value.id) : undefined;
87
- return eventRunIds?.size === 1 ? [...eventRunIds][0] : explicitRunId(value);
87
+ return ((eventRunIds?.size === 1 ? [...eventRunIds][0] : undefined) ??
88
+ explicitRunId(value) ??
89
+ (eventRunIds?.size ? undefined : inferredRunId));
88
90
  }
89
91
  function text(value, ignoreNonTextParts = false) {
90
92
  if (value.role !== "assistant")
@@ -231,11 +233,57 @@ export function projectRootAssistantMessages(input) {
231
233
  : [];
232
234
  const runIdsByMessageId = eventRunIds(input.events, input.runs);
233
235
  const messageIdsByRun = eventMessageIdsByRun(runIdsByMessageId);
236
+ const snapshotUserIdsByRun = new Map();
237
+ for (const root of rootMessages) {
238
+ if (root.role !== "user")
239
+ continue;
240
+ const custom = record(record(root.metadata)?.custom);
241
+ const snapshotId = typeof custom?.agentKitMessageId === "string"
242
+ ? custom.agentKitMessageId
243
+ : typeof root.id === "string"
244
+ ? root.id
245
+ : undefined;
246
+ const runId = custom?.submittedRunId;
247
+ if (!snapshotId || typeof runId !== "string")
248
+ continue;
249
+ snapshotUserIdsByRun.set(snapshotId, snapshotUserIdsByRun.has(snapshotId) ? null : runId);
250
+ }
251
+ const snapshotUserCounts = new Map();
252
+ for (const candidate of snapshotMessages) {
253
+ if (candidate.role !== "user" || typeof candidate.id !== "string")
254
+ continue;
255
+ snapshotUserCounts.set(candidate.id, (snapshotUserCounts.get(candidate.id) ?? 0) + 1);
256
+ }
257
+ for (const [snapshotId, runId] of snapshotUserIdsByRun) {
258
+ if (snapshotUserCounts.get(snapshotId) !== 1 || runId === null) {
259
+ snapshotUserIdsByRun.delete(snapshotId);
260
+ }
261
+ }
234
262
  const snapshotMessagesByRunId = new Map();
263
+ const snapshotRunIdsByMessageId = new Map();
264
+ let submittedRunId;
235
265
  for (const candidate of snapshotMessages) {
236
- const runId = assistantRunId(candidate, runIdsByMessageId);
237
- if (!runId)
266
+ if (candidate.role === "user") {
267
+ const candidateRunId = typeof candidate.id === "string"
268
+ ? snapshotUserIdsByRun.get(candidate.id)
269
+ : undefined;
270
+ submittedRunId =
271
+ typeof candidateRunId === "string" ? candidateRunId : undefined;
272
+ continue;
273
+ }
274
+ if (candidate.role !== "assistant")
275
+ continue;
276
+ const eventRunIds = typeof candidate.id === "string"
277
+ ? runIdsByMessageId.get(candidate.id)
278
+ : undefined;
279
+ const inferredRunId = !eventRunIds?.size && !explicitRunId(candidate)
280
+ ? submittedRunId
281
+ : undefined;
282
+ const runId = assistantRunId(candidate, runIdsByMessageId, inferredRunId);
283
+ if (!runId || typeof candidate.id !== "string")
238
284
  continue;
285
+ if (inferredRunId)
286
+ snapshotRunIdsByMessageId.set(candidate.id, runId);
239
287
  const candidates = snapshotMessagesByRunId.get(runId);
240
288
  if (candidates)
241
289
  candidates.push(candidate);
@@ -261,8 +309,9 @@ export function projectRootAssistantMessages(input) {
261
309
  const clientCanMatchPartial = matchingCandidate !== undefined &&
262
310
  (explicitRunId(matchingCandidate) === runId ||
263
311
  (typeof matchingCandidate.id === "string" &&
264
- runIdsByMessageId.get(matchingCandidate.id)?.size === 1 &&
265
- messageIdsByRun.get(runId)?.size === 1));
312
+ (snapshotRunIdsByMessageId.get(matchingCandidate.id) === runId ||
313
+ (runIdsByMessageId.get(matchingCandidate.id)?.size === 1 &&
314
+ messageIdsByRun.get(runId)?.size === 1))));
266
315
  if (sameRunMatches.length === 1 &&
267
316
  (exactTextMatch ||
268
317
  (sameRunCandidates.length === 1 && clientCanMatchPartial))) {
@@ -292,6 +341,7 @@ export function projectRootAssistantMessages(input) {
292
341
  return {
293
342
  representedRootMessageIds: represented,
294
343
  snapshotMessageIdsByRootMessageId,
344
+ snapshotRunIdsByMessageId,
295
345
  };
296
346
  }
297
347
  /** Root assistant IDs represented by the AgentKit snapshot. */
@@ -412,6 +412,80 @@ function withRefusedTurnMetadata(message, stored) {
412
412
  },
413
413
  };
414
414
  }
415
+ function orderMessagesByDurableSequence(snapshotMessages, missingMessages, durableMessages, durableIndexById, durableIdBySnapshotId) {
416
+ const messageByDurableId = new Map(missingMessages.map((message) => [message.id, message]));
417
+ const snapshotIdByDurableId = new Map();
418
+ const durableIndexBySnapshotId = new Map();
419
+ for (const message of snapshotMessages) {
420
+ const durableId = durableIdBySnapshotId.get(message.id) ??
421
+ (durableIndexById.has(message.id) ? message.id : undefined);
422
+ const durableIndex = durableId
423
+ ? durableIndexById.get(durableId)
424
+ : undefined;
425
+ if (!durableId ||
426
+ durableIndex === undefined ||
427
+ snapshotIdByDurableId.has(durableId)) {
428
+ continue;
429
+ }
430
+ snapshotIdByDurableId.set(durableId, message.id);
431
+ durableIndexBySnapshotId.set(message.id, durableIndex);
432
+ messageByDurableId.set(durableId, message);
433
+ }
434
+ if (snapshotIdByDurableId.size === 0) {
435
+ return [...snapshotMessages, ...missingMessages]
436
+ .map((message, index) => ({ message, index }))
437
+ .sort((left, right) => {
438
+ const leftCreatedAt = Date.parse(left.message.createdAt ?? "");
439
+ const rightCreatedAt = Date.parse(right.message.createdAt ?? "");
440
+ if (Number.isFinite(leftCreatedAt) &&
441
+ Number.isFinite(rightCreatedAt) &&
442
+ leftCreatedAt !== rightCreatedAt) {
443
+ return leftCreatedAt - rightCreatedAt;
444
+ }
445
+ return left.index - right.index;
446
+ })
447
+ .map(({ message }) => message);
448
+ }
449
+ const insertions = new Map();
450
+ for (let index = 0; index < snapshotMessages.length; index += 1) {
451
+ const message = snapshotMessages[index];
452
+ if (durableIndexBySnapshotId.has(message.id))
453
+ continue;
454
+ let insertionIndex;
455
+ for (let next = index + 1; next < snapshotMessages.length; next += 1) {
456
+ const durableIndex = durableIndexBySnapshotId.get(snapshotMessages[next].id);
457
+ if (durableIndex !== undefined) {
458
+ insertionIndex = durableIndex;
459
+ break;
460
+ }
461
+ }
462
+ if (insertionIndex === undefined) {
463
+ for (let previous = index - 1; previous >= 0; previous -= 1) {
464
+ const durableIndex = durableIndexBySnapshotId.get(snapshotMessages[previous].id);
465
+ if (durableIndex !== undefined) {
466
+ insertionIndex = durableIndex + 1;
467
+ break;
468
+ }
469
+ }
470
+ }
471
+ if (insertionIndex === undefined)
472
+ continue;
473
+ const messages = insertions.get(insertionIndex) ?? [];
474
+ messages.push(message);
475
+ insertions.set(insertionIndex, messages);
476
+ }
477
+ const ordered = [];
478
+ for (let index = 0; index <= durableMessages.length; index += 1) {
479
+ ordered.push(...(insertions.get(index) ?? []));
480
+ const durableMessage = durableMessages[index];
481
+ if (!durableMessage)
482
+ continue;
483
+ const projected = messageByDurableId.get(durableMessage.id);
484
+ if (projected)
485
+ ordered.push(projected);
486
+ }
487
+ return ordered;
488
+ }
415
489
  function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
416
490
  const submittedRunId = (message) => {
417
491
  const value = asRecord(asRecord(message.metadata)?.custom)?.submittedRunId;
@@ -495,6 +569,7 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
495
569
  toolCalls,
496
570
  });
497
571
  const representedDurableAssistantIds = rootAssistantProjection.representedRootMessageIds;
572
+ const snapshotRunIdsByMessageId = rootAssistantProjection.snapshotRunIdsByMessageId;
498
573
  const rootMessageIdsBySnapshotMessageId = new Map([...rootAssistantProjection.snapshotMessageIdsByRootMessageId].map(([rootId, snapshotId]) => [snapshotId, rootId]));
499
574
  const deduplicatedMessages = messages.filter((message) => !representedDurableAssistantIds.has(message.id));
500
575
  const representedSubmittedUserIds = new Set();
@@ -526,7 +601,8 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
526
601
  const metadataRunId = asRecord(message.metadata)?.runId;
527
602
  const runId = typeof metadataRunId === "string"
528
603
  ? metadataRunId
529
- : runByAssistantId.get(message.id);
604
+ : (snapshotRunIdsByMessageId.get(message.id) ??
605
+ runByAssistantId.get(message.id));
530
606
  if (runId)
531
607
  snapshotAssistantRunIds.add(runId);
532
608
  }
@@ -565,6 +641,9 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
565
641
  for (const message of matchingSnapshots) {
566
642
  matchedSnapshotUserIds.add(message.id);
567
643
  }
644
+ for (let index = 0; index < matchingSnapshots.length; index += 1) {
645
+ storedUserBySnapshotId.set(matchingSnapshots[index].id, candidates[index]);
646
+ }
568
647
  }
569
648
  const missingMessages = submittedUsers.filter((message) => !representedSubmittedUserIds.has(message.id));
570
649
  const completedRunIds = new Set([
@@ -600,39 +679,35 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
600
679
  missingMessages.push(message);
601
680
  representedAssistantIds.add(message.id);
602
681
  }
603
- const projectedMessages = [
604
- ...deduplicatedMessages,
605
- ...missingMessages.sort((left, right) => (durableIndexById.get(left.id) ?? 0) -
606
- (durableIndexById.get(right.id) ?? 0)),
607
- ]
608
- .map((message, index) => ({
609
- message,
610
- index,
611
- }))
612
- .sort((left, right) => {
613
- const leftCreatedAt = Date.parse(left.message.createdAt ?? "");
614
- const rightCreatedAt = Date.parse(right.message.createdAt ?? "");
615
- if (Number.isFinite(leftCreatedAt) &&
616
- Number.isFinite(rightCreatedAt) &&
617
- leftCreatedAt !== rightCreatedAt) {
618
- return leftCreatedAt - rightCreatedAt;
619
- }
620
- return left.index - right.index;
621
- })
622
- .map(({ message }) => message);
682
+ const durableIdBySnapshotId = new Map();
683
+ for (const [snapshotId, stored] of storedUserBySnapshotId) {
684
+ durableIdBySnapshotId.set(snapshotId, stored.id);
685
+ }
686
+ for (const [snapshotId, rootId] of rootMessageIdsBySnapshotMessageId) {
687
+ durableIdBySnapshotId.set(snapshotId, rootId);
688
+ }
689
+ const projectedMessages = orderMessagesByDurableSequence(deduplicatedMessages, missingMessages.sort((left, right) => (durableIndexById.get(left.id) ?? 0) -
690
+ (durableIndexById.get(right.id) ?? 0)), durable, durableIndexById, durableIdBySnapshotId);
623
691
  const snapshotRunId = (message) => {
624
692
  const metadataRunId = asRecord(message.metadata)?.runId;
625
693
  return (runByAssistantId.get(message.id) ??
694
+ snapshotRunIdsByMessageId.get(message.id) ??
626
695
  (typeof metadataRunId === "string" ? metadataRunId : undefined));
627
696
  };
628
697
  const textOf = (parts) => parts
629
698
  .filter((part) => part.type === "text")
630
699
  .map((part) => part.text)
631
700
  .join("");
632
- return projectedMessages.map((message) => {
701
+ const reconciledMessages = projectedMessages.map((message) => {
633
702
  if (message.role === "user") {
634
703
  const stored = storedUserBySnapshotId.get(message.id);
635
- return stored ? withRefusedTurnMetadata(message, stored) : message;
704
+ if (!stored)
705
+ return message;
706
+ const createdAt = message.createdAt ?? stored.createdAt;
707
+ const reconciled = createdAt === message.createdAt
708
+ ? message
709
+ : { ...message, ...(createdAt ? { createdAt } : {}) };
710
+ return withRefusedTurnMetadata(reconciled, stored);
636
711
  }
637
712
  if (message.role !== "assistant")
638
713
  return message;
@@ -646,10 +721,14 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
646
721
  representedRoot.status === "error"
647
722
  ? representedRoot.status
648
723
  : undefined;
649
- if (parts !== message.parts || terminalStatus !== undefined) {
724
+ const createdAt = message.createdAt ?? representedRoot.createdAt;
725
+ if (parts !== message.parts ||
726
+ terminalStatus !== undefined ||
727
+ createdAt !== message.createdAt) {
650
728
  message = {
651
729
  ...message,
652
730
  ...(parts !== message.parts ? { parts } : {}),
731
+ ...(createdAt ? { createdAt } : {}),
653
732
  ...(terminalStatus ? { status: terminalStatus } : {}),
654
733
  };
655
734
  }
@@ -759,6 +838,7 @@ function reconcileDurableMessages(messages, durable, events, runs, toolCalls) {
759
838
  }
760
839
  return { ...reconciled, parts };
761
840
  });
841
+ return reconciledMessages;
762
842
  }
763
843
  function messageStatus(value) {
764
844
  const status = asRecord(value)?.type ?? value;
@@ -38,19 +38,19 @@ export declare const postAwareness: import("h3").EventHandlerWithFetch<import("h
38
38
  error: string;
39
39
  states?: undefined;
40
40
  } | {
41
- error?: undefined;
42
41
  states: {
43
42
  clientId: number;
44
43
  state: string;
45
44
  }[];
45
+ error?: undefined;
46
46
  }>>;
47
47
  export declare const getActiveUsers: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
48
48
  error: string;
49
49
  users?: undefined;
50
50
  } | {
51
- error?: undefined;
52
51
  users: {
53
52
  clientId: number;
54
53
  lastSeen: number;
55
54
  }[];
55
+ error?: undefined;
56
56
  }>>;
@@ -42,8 +42,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
42
42
  url: string;
43
43
  id: string;
44
44
  provider: string;
45
- configured?: undefined;
46
45
  error?: undefined;
46
+ configured?: undefined;
47
47
  } | {
48
48
  url?: undefined;
49
49
  id?: undefined;
@@ -1,12 +1,12 @@
1
1
  export declare function createNotificationsHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<"" | import("./types.js").Notification[] | {
2
+ error?: undefined;
2
3
  count: number;
3
4
  updated?: undefined;
4
- error?: undefined;
5
5
  ok?: undefined;
6
6
  } | {
7
+ error?: undefined;
7
8
  count?: undefined;
8
9
  updated: number;
9
- error?: undefined;
10
10
  ok?: undefined;
11
11
  } | {
12
12
  count?: undefined;
@@ -14,8 +14,8 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
14
14
  error: string;
15
15
  ok?: undefined;
16
16
  } | {
17
+ error?: undefined;
17
18
  count?: undefined;
18
19
  updated?: undefined;
19
- error?: undefined;
20
20
  ok: boolean;
21
21
  }>>;
@@ -28,32 +28,32 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
28
28
  avgEvalScore: number;
29
29
  } | {
30
30
  id?: undefined;
31
- ok?: undefined;
32
31
  error: string;
33
32
  message: string;
34
33
  summary?: undefined;
35
34
  spans?: undefined;
35
+ ok?: undefined;
36
36
  } | {
37
37
  id?: undefined;
38
38
  error?: undefined;
39
- ok?: undefined;
40
39
  message?: undefined;
41
40
  summary: import("./types.js").TraceSummary;
42
41
  spans: import("./types.js").TraceSpan[];
42
+ ok?: undefined;
43
43
  } | {
44
44
  error?: undefined;
45
- ok?: undefined;
46
45
  message?: undefined;
47
46
  summary?: undefined;
48
47
  spans?: undefined;
49
48
  id: string;
49
+ ok?: undefined;
50
50
  } | {
51
51
  id?: undefined;
52
- ok?: undefined;
53
52
  message?: undefined;
54
53
  summary?: undefined;
55
54
  spans?: undefined;
56
55
  error: any;
56
+ ok?: undefined;
57
57
  } | {
58
58
  id?: undefined;
59
59
  error?: undefined;
@@ -138,7 +138,8 @@ export async function ensureObservabilityTables() {
138
138
  status TEXT NOT NULL DEFAULT 'success',
139
139
  error_message TEXT,
140
140
  metadata TEXT,
141
- created_at BIGINT NOT NULL
141
+ created_at BIGINT NOT NULL,
142
+ ended_at BIGINT
142
143
  )
143
144
  `;
144
145
  const traceSummariesCreateSql = `
@@ -290,6 +291,7 @@ export async function ensureObservabilityTables() {
290
291
  await ensureTableExists("agent_experiment_assignments", experimentAssignmentsCreateSql);
291
292
  await ensureTableExists("agent_experiment_results", experimentResultsCreateSql);
292
293
  await ensureColumnExists("agent_experiments", "owner_email", `ALTER TABLE agent_experiments ADD COLUMN IF NOT EXISTS owner_email TEXT`);
294
+ await ensureColumnExists("agent_trace_spans", "ended_at", `ALTER TABLE agent_trace_spans ADD COLUMN IF NOT EXISTS ended_at BIGINT`);
293
295
  for (const table of USER_SCOPED_TABLES) {
294
296
  await ensureColumnExists(table, "user_id", `ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS user_id TEXT`);
295
297
  }
@@ -344,8 +346,8 @@ export async function insertTraceSpan(span) {
344
346
  sql: `INSERT INTO agent_trace_spans
345
347
  (id, run_id, thread_id, user_id, org_id, parent_span_id, span_type, name,
346
348
  input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
347
- cost_cents_x100, duration_ms, status, error_message, metadata, created_at)
348
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
349
+ cost_cents_x100, duration_ms, status, error_message, metadata, created_at, ended_at)
350
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
349
351
  args: [
350
352
  span.id,
351
353
  span.runId,
@@ -365,6 +367,7 @@ export async function insertTraceSpan(span) {
365
367
  span.errorMessage,
366
368
  span.metadata ? JSON.stringify(span.metadata) : null,
367
369
  span.createdAt,
370
+ span.endedAt ?? span.createdAt + span.durationMs,
368
371
  ],
369
372
  });
370
373
  }
@@ -1733,6 +1736,8 @@ function rowToTraceSpan(row) {
1733
1736
  const exposesErrorText = row.span_type !== "tool_call" ||
1734
1737
  errorDetail === "full" ||
1735
1738
  errorDetail === "signature";
1739
+ const createdAt = Number(row.created_at);
1740
+ const durationMs = Number(row.duration_ms ?? 0);
1736
1741
  return {
1737
1742
  id: String(row.id),
1738
1743
  runId: String(row.run_id),
@@ -1747,14 +1752,15 @@ function rowToTraceSpan(row) {
1747
1752
  cacheReadTokens: Number(row.cache_read_tokens ?? 0),
1748
1753
  cacheWriteTokens: Number(row.cache_write_tokens ?? 0),
1749
1754
  costCentsX100: Number(row.cost_cents_x100 ?? 0),
1750
- durationMs: Number(row.duration_ms ?? 0),
1755
+ durationMs,
1751
1756
  status: row.status,
1752
1757
  errorMessage: errorMessage && exposesErrorText
1753
1758
  ? sanitizeToolErrorMessage(errorMessage)
1754
1759
  : null,
1755
1760
  ...(errorDetail ? { errorDetail } : {}),
1756
1761
  metadata,
1757
- createdAt: Number(row.created_at),
1762
+ createdAt,
1763
+ endedAt: row.ended_at == null ? createdAt + durationMs : Number(row.ended_at),
1758
1764
  };
1759
1765
  }
1760
1766
  function rowToTraceSummary(row) {
@@ -219,6 +219,8 @@ function emitLlmGenerationTrackingEvent(args) {
219
219
  model_selection_source: args.modelSelectionSource,
220
220
  created_at: new Date(args.createdAt).toISOString(),
221
221
  created_at_ms: args.createdAt,
222
+ ended_at: new Date(args.endedAt).toISOString(),
223
+ ended_at_ms: args.endedAt,
222
224
  $ai_trace_id: args.runId,
223
225
  $ai_session_id: args.threadId ?? undefined,
224
226
  $ai_span_id: args.llmSpanId,
@@ -1004,6 +1006,7 @@ export async function instrumentAgentLoop(opts) {
1004
1006
  spanId: trip.spanId,
1005
1007
  model: trip.usage?.model ?? runUsage.model,
1006
1008
  createdAt: trip.start,
1009
+ endedAt: trip.end,
1007
1010
  latencyMs: Math.max(0, trip.end - trip.start),
1008
1011
  callUsage: trip.usage,
1009
1012
  stopReason: trip.stopReason,
@@ -1025,6 +1028,7 @@ export async function instrumentAgentLoop(opts) {
1025
1028
  spanId: spanId(),
1026
1029
  model: runUsage.model,
1027
1030
  createdAt: runStart,
1031
+ endedAt: runStart + derivedLlmDurationMs,
1028
1032
  latencyMs: derivedLlmDurationMs,
1029
1033
  callUsage: usage,
1030
1034
  stopReason: undefined,
@@ -1113,6 +1117,7 @@ export async function instrumentAgentLoop(opts) {
1113
1117
  ? capturedContent
1114
1118
  : null,
1115
1119
  createdAt: generation.createdAt,
1120
+ endedAt: generation.endedAt,
1116
1121
  });
1117
1122
  emitLlmGenerationTrackingEvent({
1118
1123
  runId,
@@ -1154,6 +1159,7 @@ export async function instrumentAgentLoop(opts) {
1154
1159
  : undefined,
1155
1160
  delegation: opts.delegation,
1156
1161
  createdAt: generation.createdAt,
1162
+ endedAt: generation.endedAt,
1157
1163
  experimentAssignments: opts.experimentAssignments,
1158
1164
  modelSelectionSource: opts.modelSelectionSource,
1159
1165
  browserSessionId,
@@ -36,6 +36,8 @@ export interface TraceSpan {
36
36
  errorDetail?: SpanErrorDetail;
37
37
  metadata: Record<string, unknown> | null;
38
38
  createdAt: number;
39
+ /** Request/span end time in epoch milliseconds. Older rows derive this from duration. */
40
+ endedAt?: number;
39
41
  }
40
42
  export interface TraceSummary {
41
43
  runId: string;
@@ -2,6 +2,6 @@ export declare function createProgressHandler(): import("h3").EventHandlerWithFe
2
2
  error: string;
3
3
  ok?: undefined;
4
4
  } | {
5
- error?: undefined;
6
5
  ok: boolean;
6
+ error?: undefined;
7
7
  }>>;
@@ -39,8 +39,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
39
39
  error: string;
40
40
  }>;
41
41
  export declare function handleDeleteResource(event: any): Promise<{
42
- error: string;
43
42
  ok?: undefined;
43
+ error: string;
44
44
  } | {
45
45
  error?: undefined;
46
46
  ok: boolean;
@@ -66,12 +66,12 @@ export declare function createAgentEngineApiKeyHandler(): import("h3").EventHand
66
66
  code: any;
67
67
  } | {
68
68
  errorCode?: undefined;
69
- error?: undefined;
70
69
  code?: undefined;
71
70
  ok: boolean;
72
71
  key: string;
73
72
  baseUrlKey?: string;
74
73
  scope: AgentEngineApiKeyScope;
75
74
  defaultModel?: import("./agent-engine-default-model-route.js").SavedKeyDefaultModelOutcome;
75
+ error?: undefined;
76
76
  }>>;
77
77
  export {};
@@ -9,8 +9,8 @@ export declare const AGENT_ENGINE_API_KEY_ACTION_NAME = "agent-engine-api-key";
9
9
  */
10
10
  export declare function createAgentEngineDisconnectHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
11
11
  error: string;
12
- scope?: undefined;
13
12
  ok?: undefined;
13
+ scope?: undefined;
14
14
  } | {
15
15
  ok: boolean;
16
16
  error: any;
@@ -724,12 +724,14 @@ const BUILDER_WAITLIST_DEFAULT_USE_CASE = "builder_agent_background_coding";
724
724
  const BUILDER_WAITLIST_USE_CASES = new Set([
725
725
  BUILDER_WAITLIST_DEFAULT_USE_CASE,
726
726
  "design_publish_app",
727
- "design_make_real_waitlist",
728
- "design_system_workflows_waitlist",
729
- "design_system_waitlist",
730
727
  "docs_build_online_waitlist",
731
728
  "docs_edit_online_waitlist",
732
729
  ]);
730
+ const BUILDER_WAITLIST_USE_CASE_GROUPS = new Map([
731
+ ["design_make_real_waitlist", "design_publish_app"],
732
+ ["design_system_workflows_waitlist", "design_publish_app"],
733
+ ["design_system_waitlist", "design_publish_app"],
734
+ ]);
733
735
  const BUILDER_WAITLIST_FORM_TIMEOUT_MS = 8000;
734
736
  const BUILDER_WAITLIST_TEXT_LIMIT = 4000;
735
737
  const BUILDER_WAITLIST_RATE_LIMIT_WINDOW_MS = 60_000;
@@ -766,9 +768,15 @@ function cleanBuilderWaitlistText(value, maxLength = BUILDER_WAITLIST_TEXT_LIMIT
766
768
  }
767
769
  function normalizeBuilderWaitlistUseCase(value) {
768
770
  const useCase = cleanBuilderWaitlistText(value, 100);
769
- return useCase && BUILDER_WAITLIST_USE_CASES.has(useCase)
770
- ? useCase
771
- : BUILDER_WAITLIST_DEFAULT_USE_CASE;
771
+ if (!useCase)
772
+ return BUILDER_WAITLIST_DEFAULT_USE_CASE;
773
+ if (BUILDER_WAITLIST_USE_CASES.has(useCase))
774
+ return useCase;
775
+ // The published waitlist form validates this select against its own option
776
+ // list. Keep new Design entry points in the existing Design category; their
777
+ // exact surfaces remain in the text `source` field.
778
+ return (BUILDER_WAITLIST_USE_CASE_GROUPS.get(useCase) ??
779
+ BUILDER_WAITLIST_DEFAULT_USE_CASE);
772
780
  }
773
781
  function normalizeBuilderWaitlistTemplate(value) {
774
782
  const template = cleanBuilderWaitlistText(value, 100);
@@ -54,8 +54,8 @@ export declare function createRealtimeTokenHandler(): import("h3").EventHandlerW
54
54
  expiresAt?: undefined;
55
55
  ttlSeconds?: undefined;
56
56
  } | {
57
- error?: undefined;
58
57
  token: string;
59
58
  expiresAt: string;
60
59
  ttlSeconds: number;
60
+ error?: undefined;
61
61
  }>>;
@@ -2,7 +2,6 @@ declare const _default: import("../../action.js").ActionDefinition<{
2
2
  resourceType: string;
3
3
  resourceId: string;
4
4
  }, {
5
- role?: undefined;
6
5
  ownerEmail: any;
7
6
  visibility: any;
8
7
  shares: any[];
@@ -12,6 +11,7 @@ declare const _default: import("../../action.js").ActionDefinition<{
12
11
  supportsGroupShares?: boolean;
13
12
  };
14
13
  orgId?: undefined;
14
+ role?: undefined;
15
15
  agentReadable?: undefined;
16
16
  } | {
17
17
  ownerEmail: string;
@@ -70,9 +70,16 @@ unapproved work. Pair `✅` + `🎫` only for distinct scopes; name the fixed
70
70
  behavior and ticket action. **Shipped**/**Live verified** alone don't earn
71
71
  `✅`. Never remove reactions.
72
72
 
73
- If no safe repo-owned fix is evident, record the evidence limit. Ask only a
74
- question that could unblock a fix; after four days without an answer, record
75
- **Abandoned - no answer in 4 days**.
73
+ For every concrete objective defect, attempt to reproduce it before recording
74
+ an evidence limit or asking the reporter for more information. A screenshot,
75
+ error message, URL, or visible flow is enough to start tracing its owner; don't
76
+ wait for details that would not change the reproduction attempt. Phase 2's
77
+ local reproduction procedure applies to all defect reports, not only bashes.
78
+
79
+ If no safe repo-owned fix is evident after that attempt, record the specific
80
+ evidence limit. Ask only a question whose answer could change the reproduction
81
+ or unblock a fix; after four days without an answer, record **Abandoned - no
82
+ answer in 4 days**.
76
83
 
77
84
  Use **Skipped** only for non-defects, never breakage. **Open - no question**
78
85
  means you found neither a fix nor a useful question; state why in the thread.
@@ -340,6 +347,26 @@ screenshot is usually the whole diagnosis. Track an artifact that is
340
347
  permission-gated or expired separately from one that was never provided —
341
348
  inaccessible is not absent.
342
349
 
350
+ ### Attempt local reproduction before evidence-limiting
351
+
352
+ For each concrete objective defect, try the reported flow before deciding it
353
+ cannot be reproduced or fixed. Trace the screenshot, URL, visible action, and
354
+ error through the app to its route, action, provider, or form contract; use
355
+ those clues to choose the closest runnable local seam. Exercise the flow with
356
+ a local build and synthetic fixtures. If its external dependency is unavailable,
357
+ mock that boundary or submit the same shape to a local validator. Do not use a
358
+ reporter's real email or create a real external record merely to prove a
359
+ failure.
360
+
361
+ Record the route or action, build, fixture, steps, expected and actual result,
362
+ and error. Code inspection alone is not a reproduction attempt, and
363
+ **Not reproducible - attempted** requires an actual flow attempt. Ask the
364
+ reporter only when a specific missing detail would change the attempt or fix;
365
+ otherwise make the best local attempt with the evidence already available.
366
+ If the attempt stops at a concrete access or environment limit, name what was
367
+ tried and what exact behavior remains untested. Measure misses with
368
+ `feedback-no-local-repro`.
369
+
343
370
  **Sweep siblings before you claim anything is fixed.** Derive the fingerprint
344
371
  from the symptom, not the file — the exact crashing token, call shape, or
345
372
  literal — then search the repo for it and enumerate every hit in your recap
@@ -384,47 +411,10 @@ Measure this gate with friction keys `false-done` and
384
411
  --pattern <key>` for each before changing it and again later. A climbing count
385
412
  requires a mechanical proof or release gate, not more prose.
386
413
 
387
- ### Bug-bash reproduction contract
388
-
389
- For Design, Slides, Core/framework, and template bashes, the reachable reported
390
- surface is the contract:
391
-
392
- 1. **Reproduce before editing.** Use the exact URL/route, app/template,
393
- account/workspace/role, build/package, browser/device, fixture, and inputs;
394
- record expected/actual, errors, and attached artifacts.
395
- 2. **Sweep siblings and boundaries.** Test a negative control plus empty, wrong,
396
- whitespace, case, and permission variants; enumerate every shared fingerprint.
397
- 3. **Repeat on the changed running artifact.** Rerun the flow, refresh/navigate,
398
- read UI and persisted state, and cover failure/retry/cancel/async paths.
399
- Destructive flows require wrong/partial/exact confirmation and recovery;
400
- do not delete unless needed.
401
- 4. **Test release/race layers when needed.** Use concurrency/10 runs, a clean
402
- scaffold, and the exact package for package reports. Reproduce and verify
403
- locally by default; use beta only if the full symptom cannot be reproduced
404
- locally and hosted behavior is needed. Record why. These checks support
405
- **Shipped**/**Live verified**, not a merged **Fixed** claim.
406
- 5. Record untested layers. Before merge, use an evidence-limited status. After
407
- verified source merge, mark **Fixed** even if release/live layers remain;
408
- routine rollout and optional beta checks aren't ticketed follow-ups.
409
- **Shipped**/**Live verified** need their own bars. Don't mark **Fixed**/`✅`
410
- without merged-source proof. Reopen repeats only with a fresh failing
411
- pre-change reproduction.
412
-
413
- ### Reproduction ledger - required for every row
414
-
415
- For each row, record symptom/surface, reproduction steps and account, expected
416
- and pre/post behavior, tested commit/build, sibling results, untested layers,
417
- and runtime layer (`local`, `source-only`, `built`, `deployed`, `observed-live`).
418
-
419
- Without merged source proof, use an active or waiting disposition above. After
420
- merge, **Fixed** may coexist with release follow-up; **Live verified** requires
421
- all four bars. Status labels, reactions, and tests alone do not prove closure.
422
- Repeats require a new pre-change failure and link the earlier false claim.
423
-
424
- Regression claims require Red/Green proof: reverse-apply hunk with
425
- `git apply -R`, record failure, reapply, record pass. Repeat timing checks 10x.
426
- If output missing, build it and rerun on `origin/main` before calling them
427
- pre-existing.
414
+ ### Reproduction and verification detail
415
+
416
+ Use [`reproduction-and-verification.md`](references/reproduction-and-verification.md)
417
+ for the bug-bash contract, reproduction ledger, and regression proof.
428
418
 
429
419
  ### Npx and package reports have a release follow-up
430
420
 
@@ -0,0 +1,46 @@
1
+ # Reproduction and verification
2
+
3
+ Use this reference for the detailed reproduction ledger and deeper verification
4
+ contract. The skill's general rule still applies to every objective defect.
5
+
6
+ ## Bug-bash reproduction contract
7
+
8
+ For Design, Slides, Core/framework, and template bashes, the reachable reported
9
+ surface is the contract:
10
+
11
+ 1. **Reproduce before editing.** Use the exact URL/route, app/template,
12
+ account/workspace/role, build/package, browser/device, fixture, and inputs;
13
+ record expected/actual, errors, and attached artifacts.
14
+ 2. **Sweep siblings and boundaries.** Test a negative control plus empty, wrong,
15
+ whitespace, case, and permission variants; enumerate every shared fingerprint.
16
+ 3. **Repeat on the changed running artifact.** Rerun the flow, refresh/navigate,
17
+ read UI and persisted state, and cover failure/retry/cancel/async paths.
18
+ Destructive flows require wrong/partial/exact confirmation and recovery;
19
+ do not delete unless needed.
20
+ 4. **Test release/race layers when needed.** Use concurrency/10 runs, a clean
21
+ scaffold, and the exact package for package reports. Reproduce and verify
22
+ locally by default; use beta only if the full symptom cannot be reproduced
23
+ locally and hosted behavior is needed. Record why. These checks support
24
+ **Shipped**/**Live verified**, not a merged **Fixed** claim.
25
+ 5. Record untested layers. Before merge, use an evidence-limited status. After
26
+ verified source merge, mark **Fixed** even if release/live layers remain;
27
+ routine rollout and optional beta checks aren't ticketed follow-ups.
28
+ **Shipped**/**Live verified** need their own bars. Don't mark **Fixed**/`✅`
29
+ without merged-source proof. Reopen repeats only with a fresh failing
30
+ pre-change reproduction.
31
+
32
+ ## Reproduction ledger
33
+
34
+ For each row, record symptom/surface, reproduction steps and account, expected
35
+ and pre/post behavior, tested commit/build, sibling results, untested layers,
36
+ and runtime layer (`local`, `source-only`, `built`, `deployed`, `observed-live`).
37
+
38
+ Without merged source proof, use an active or waiting disposition above. After
39
+ merge, **Fixed** may coexist with release follow-up; **Live verified** requires
40
+ all four bars. Status labels, reactions, and tests alone do not prove closure.
41
+ Repeats require a new pre-change failure and link the earlier false claim.
42
+
43
+ Regression claims require Red/Green proof: reverse-apply the hunk with
44
+ `git apply -R`, record failure, reapply, and record pass. Repeat timing checks
45
+ 10x. If output is missing, build it and rerun on `origin/main` before calling
46
+ the behavior pre-existing.
@@ -284,9 +284,11 @@ the major product/UX decision above.
284
284
 
285
285
  For a PR that passes the applicable gate and lacks a current-head approval,
286
286
  submit one GitHub approval review and record the approval URL in the recap.
287
- Do not duplicate an existing current-head approval. Do not add a tag,
288
- assignment, mention, or explanatory comment unless the invocation explicitly
289
- asks for it.
287
+ Do not duplicate an existing current-head approval. This skill reviews other
288
+ people's PRs: do not add a tag, assignment, mention, or explanatory comment
289
+ unless the current invocation explicitly authorizes that communication on the
290
+ exact PR. This does not add a separate authorization step for replies required
291
+ on the active user's own PR under `/ship`, `/ship-now`, or `/babysit-pr`.
290
292
 
291
293
  Bot-authored PRs, including Dependabot, are outside this skill's review and
292
294
  merge scope and must remain completely untouched.
@@ -29,8 +29,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
29
29
  executionEngine?: undefined;
30
30
  executionCwd?: undefined;
31
31
  } | {
32
- created?: undefined;
33
32
  scope?: undefined;
33
+ created?: undefined;
34
34
  triggerType?: undefined;
35
35
  event?: undefined;
36
36
  deleted: boolean;
@@ -45,8 +45,8 @@ declare const _default: import("../../action.js").ActionDefinition<{
45
45
  nextRun?: undefined;
46
46
  webhookPath?: undefined;
47
47
  } | {
48
- created?: undefined;
49
48
  scope?: undefined;
49
+ created?: undefined;
50
50
  triggerType?: undefined;
51
51
  event?: undefined;
52
52
  deleted?: undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.203.1",
3
+ "version": "0.203.2-nightly-20261007213109",
4
4
  "description": "The agentic application framework for building autonomous agents with intuitive UIs",
5
5
  "homepage": "https://github.com/BuilderIO/agent-native#readme",
6
6
  "bugs": {
@@ -682,7 +682,7 @@
682
682
  "yaml": "^2.9.0",
683
683
  "yjs": "^13.6.33",
684
684
  "zod": "^4.3.6",
685
- "@agent-native/agentkit": "^0.203.1",
685
+ "@agent-native/agentkit": "^0.203.2-nightly-20261007213109",
686
686
  "@agent-native/recap-cli": "^0.5.65"
687
687
  },
688
688
  "devDependencies": {
@@ -753,7 +753,7 @@
753
753
  "xlsx": "^0.18.5"
754
754
  },
755
755
  "peerDependencies": {
756
- "@agent-native/core-corpus": ">=0.196.0",
756
+ "@agent-native/core-corpus": "0.203.2-nightly-20261007213109",
757
757
  "@ai-sdk/anthropic": ">=3",
758
758
  "@ai-sdk/cohere": ">=3",
759
759
  "@ai-sdk/google": ">=3",
@@ -70,9 +70,16 @@ unapproved work. Pair `✅` + `🎫` only for distinct scopes; name the fixed
70
70
  behavior and ticket action. **Shipped**/**Live verified** alone don't earn
71
71
  `✅`. Never remove reactions.
72
72
 
73
- If no safe repo-owned fix is evident, record the evidence limit. Ask only a
74
- question that could unblock a fix; after four days without an answer, record
75
- **Abandoned - no answer in 4 days**.
73
+ For every concrete objective defect, attempt to reproduce it before recording
74
+ an evidence limit or asking the reporter for more information. A screenshot,
75
+ error message, URL, or visible flow is enough to start tracing its owner; don't
76
+ wait for details that would not change the reproduction attempt. Phase 2's
77
+ local reproduction procedure applies to all defect reports, not only bashes.
78
+
79
+ If no safe repo-owned fix is evident after that attempt, record the specific
80
+ evidence limit. Ask only a question whose answer could change the reproduction
81
+ or unblock a fix; after four days without an answer, record **Abandoned - no
82
+ answer in 4 days**.
76
83
 
77
84
  Use **Skipped** only for non-defects, never breakage. **Open - no question**
78
85
  means you found neither a fix nor a useful question; state why in the thread.
@@ -340,6 +347,26 @@ screenshot is usually the whole diagnosis. Track an artifact that is
340
347
  permission-gated or expired separately from one that was never provided —
341
348
  inaccessible is not absent.
342
349
 
350
+ ### Attempt local reproduction before evidence-limiting
351
+
352
+ For each concrete objective defect, try the reported flow before deciding it
353
+ cannot be reproduced or fixed. Trace the screenshot, URL, visible action, and
354
+ error through the app to its route, action, provider, or form contract; use
355
+ those clues to choose the closest runnable local seam. Exercise the flow with
356
+ a local build and synthetic fixtures. If its external dependency is unavailable,
357
+ mock that boundary or submit the same shape to a local validator. Do not use a
358
+ reporter's real email or create a real external record merely to prove a
359
+ failure.
360
+
361
+ Record the route or action, build, fixture, steps, expected and actual result,
362
+ and error. Code inspection alone is not a reproduction attempt, and
363
+ **Not reproducible - attempted** requires an actual flow attempt. Ask the
364
+ reporter only when a specific missing detail would change the attempt or fix;
365
+ otherwise make the best local attempt with the evidence already available.
366
+ If the attempt stops at a concrete access or environment limit, name what was
367
+ tried and what exact behavior remains untested. Measure misses with
368
+ `feedback-no-local-repro`.
369
+
343
370
  **Sweep siblings before you claim anything is fixed.** Derive the fingerprint
344
371
  from the symptom, not the file — the exact crashing token, call shape, or
345
372
  literal — then search the repo for it and enumerate every hit in your recap
@@ -384,47 +411,10 @@ Measure this gate with friction keys `false-done` and
384
411
  --pattern <key>` for each before changing it and again later. A climbing count
385
412
  requires a mechanical proof or release gate, not more prose.
386
413
 
387
- ### Bug-bash reproduction contract
388
-
389
- For Design, Slides, Core/framework, and template bashes, the reachable reported
390
- surface is the contract:
391
-
392
- 1. **Reproduce before editing.** Use the exact URL/route, app/template,
393
- account/workspace/role, build/package, browser/device, fixture, and inputs;
394
- record expected/actual, errors, and attached artifacts.
395
- 2. **Sweep siblings and boundaries.** Test a negative control plus empty, wrong,
396
- whitespace, case, and permission variants; enumerate every shared fingerprint.
397
- 3. **Repeat on the changed running artifact.** Rerun the flow, refresh/navigate,
398
- read UI and persisted state, and cover failure/retry/cancel/async paths.
399
- Destructive flows require wrong/partial/exact confirmation and recovery;
400
- do not delete unless needed.
401
- 4. **Test release/race layers when needed.** Use concurrency/10 runs, a clean
402
- scaffold, and the exact package for package reports. Reproduce and verify
403
- locally by default; use beta only if the full symptom cannot be reproduced
404
- locally and hosted behavior is needed. Record why. These checks support
405
- **Shipped**/**Live verified**, not a merged **Fixed** claim.
406
- 5. Record untested layers. Before merge, use an evidence-limited status. After
407
- verified source merge, mark **Fixed** even if release/live layers remain;
408
- routine rollout and optional beta checks aren't ticketed follow-ups.
409
- **Shipped**/**Live verified** need their own bars. Don't mark **Fixed**/`✅`
410
- without merged-source proof. Reopen repeats only with a fresh failing
411
- pre-change reproduction.
412
-
413
- ### Reproduction ledger - required for every row
414
-
415
- For each row, record symptom/surface, reproduction steps and account, expected
416
- and pre/post behavior, tested commit/build, sibling results, untested layers,
417
- and runtime layer (`local`, `source-only`, `built`, `deployed`, `observed-live`).
418
-
419
- Without merged source proof, use an active or waiting disposition above. After
420
- merge, **Fixed** may coexist with release follow-up; **Live verified** requires
421
- all four bars. Status labels, reactions, and tests alone do not prove closure.
422
- Repeats require a new pre-change failure and link the earlier false claim.
423
-
424
- Regression claims require Red/Green proof: reverse-apply hunk with
425
- `git apply -R`, record failure, reapply, record pass. Repeat timing checks 10x.
426
- If output missing, build it and rerun on `origin/main` before calling them
427
- pre-existing.
414
+ ### Reproduction and verification detail
415
+
416
+ Use [`reproduction-and-verification.md`](references/reproduction-and-verification.md)
417
+ for the bug-bash contract, reproduction ledger, and regression proof.
428
418
 
429
419
  ### Npx and package reports have a release follow-up
430
420
 
@@ -0,0 +1,46 @@
1
+ # Reproduction and verification
2
+
3
+ Use this reference for the detailed reproduction ledger and deeper verification
4
+ contract. The skill's general rule still applies to every objective defect.
5
+
6
+ ## Bug-bash reproduction contract
7
+
8
+ For Design, Slides, Core/framework, and template bashes, the reachable reported
9
+ surface is the contract:
10
+
11
+ 1. **Reproduce before editing.** Use the exact URL/route, app/template,
12
+ account/workspace/role, build/package, browser/device, fixture, and inputs;
13
+ record expected/actual, errors, and attached artifacts.
14
+ 2. **Sweep siblings and boundaries.** Test a negative control plus empty, wrong,
15
+ whitespace, case, and permission variants; enumerate every shared fingerprint.
16
+ 3. **Repeat on the changed running artifact.** Rerun the flow, refresh/navigate,
17
+ read UI and persisted state, and cover failure/retry/cancel/async paths.
18
+ Destructive flows require wrong/partial/exact confirmation and recovery;
19
+ do not delete unless needed.
20
+ 4. **Test release/race layers when needed.** Use concurrency/10 runs, a clean
21
+ scaffold, and the exact package for package reports. Reproduce and verify
22
+ locally by default; use beta only if the full symptom cannot be reproduced
23
+ locally and hosted behavior is needed. Record why. These checks support
24
+ **Shipped**/**Live verified**, not a merged **Fixed** claim.
25
+ 5. Record untested layers. Before merge, use an evidence-limited status. After
26
+ verified source merge, mark **Fixed** even if release/live layers remain;
27
+ routine rollout and optional beta checks aren't ticketed follow-ups.
28
+ **Shipped**/**Live verified** need their own bars. Don't mark **Fixed**/`✅`
29
+ without merged-source proof. Reopen repeats only with a fresh failing
30
+ pre-change reproduction.
31
+
32
+ ## Reproduction ledger
33
+
34
+ For each row, record symptom/surface, reproduction steps and account, expected
35
+ and pre/post behavior, tested commit/build, sibling results, untested layers,
36
+ and runtime layer (`local`, `source-only`, `built`, `deployed`, `observed-live`).
37
+
38
+ Without merged source proof, use an active or waiting disposition above. After
39
+ merge, **Fixed** may coexist with release follow-up; **Live verified** requires
40
+ all four bars. Status labels, reactions, and tests alone do not prove closure.
41
+ Repeats require a new pre-change failure and link the earlier false claim.
42
+
43
+ Regression claims require Red/Green proof: reverse-apply the hunk with
44
+ `git apply -R`, record failure, reapply, and record pass. Repeat timing checks
45
+ 10x. If output is missing, build it and rerun on `origin/main` before calling
46
+ the behavior pre-existing.
@@ -284,9 +284,11 @@ the major product/UX decision above.
284
284
 
285
285
  For a PR that passes the applicable gate and lacks a current-head approval,
286
286
  submit one GitHub approval review and record the approval URL in the recap.
287
- Do not duplicate an existing current-head approval. Do not add a tag,
288
- assignment, mention, or explanatory comment unless the invocation explicitly
289
- asks for it.
287
+ Do not duplicate an existing current-head approval. This skill reviews other
288
+ people's PRs: do not add a tag, assignment, mention, or explanatory comment
289
+ unless the current invocation explicitly authorizes that communication on the
290
+ exact PR. This does not add a separate authorization step for replies required
291
+ on the active user's own PR under `/ship`, `/ship-now`, or `/babysit-pr`.
290
292
 
291
293
  Bot-authored PRs, including Dependabot, are outside this skill's review and
292
294
  merge scope and must remain completely untouched.