@desplega.ai/agent-swarm 1.91.0 → 1.92.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +3 -2
  2. package/openapi.json +1005 -152
  3. package/package.json +6 -6
  4. package/plugin/skills/pages/SKILL.md +5 -2
  5. package/src/be/db.ts +662 -19
  6. package/src/be/memory/constants.ts +2 -1
  7. package/src/be/memory/providers/openai-embedding.ts +2 -5
  8. package/src/be/memory/providers/sqlite-store.ts +293 -76
  9. package/src/be/memory/types.ts +35 -0
  10. package/src/be/migrations/083_script_workflows.sql +51 -0
  11. package/src/be/migrations/084_script_run_journal_duration.sql +5 -0
  12. package/src/be/migrations/085_script_runs_kind.sql +9 -0
  13. package/src/be/migrations/086_pages_default_authed.sql +64 -0
  14. package/src/be/migrations/087_skill_files.sql +19 -0
  15. package/src/be/modelsdev-cache.json +42310 -38617
  16. package/src/be/scripts/typecheck.ts +49 -0
  17. package/src/be/seed-scripts/catalog/boot-triage.ts +221 -0
  18. package/src/be/seed-scripts/catalog/catalog-report.ts +457 -0
  19. package/src/be/seed-scripts/catalog/compound-insights.ts +310 -6
  20. package/src/be/seed-scripts/catalog/gh-pr-snapshot.ts +1 -1
  21. package/src/be/seed-scripts/catalog/memory-eval.ts +1059 -0
  22. package/src/be/seed-scripts/catalog/ops-catalog-audit.ts +506 -0
  23. package/src/be/seed-scripts/catalog/schedule-health.ts +78 -2
  24. package/src/be/seed-scripts/catalog/task-context-gathering.ts +92 -0
  25. package/src/be/seed-scripts/catalog/task-failure-audit.ts +48 -1
  26. package/src/be/seed-scripts/catalog/tool-usage.ts +6 -3
  27. package/src/be/seed-scripts/index.ts +51 -5
  28. package/src/be/seed-skills/index.ts +3 -3
  29. package/src/be/skill-sync.ts +91 -7
  30. package/src/be/swarm-config-guard.ts +17 -0
  31. package/src/commands/runner.ts +49 -4
  32. package/src/heartbeat/templates.ts +20 -16
  33. package/src/http/db-query.ts +20 -5
  34. package/src/http/index.ts +51 -7
  35. package/src/http/mcp-user.ts +23 -0
  36. package/src/http/mcp.ts +58 -0
  37. package/src/http/memory.ts +58 -0
  38. package/src/http/pages.ts +1 -1
  39. package/src/http/script-runs.ts +557 -0
  40. package/src/http/scripts.ts +39 -2
  41. package/src/http/skills.ts +225 -0
  42. package/src/prompts/session-templates.ts +24 -4
  43. package/src/providers/claude-adapter.ts +107 -28
  44. package/src/script-workflows/executor.ts +110 -0
  45. package/src/script-workflows/harness.ts +73 -0
  46. package/src/script-workflows/label-lint.ts +51 -0
  47. package/src/script-workflows/limits.ts +22 -0
  48. package/src/script-workflows/supervisor.ts +139 -0
  49. package/src/script-workflows/workflow-ctx.ts +209 -0
  50. package/src/scripts-runtime/sdk-allowlist.ts +4 -0
  51. package/src/scripts-runtime/swarm-sdk.ts +13 -0
  52. package/src/scripts-runtime/types/stdlib.d.ts +61 -0
  53. package/src/scripts-runtime/types/swarm-sdk.d.ts +61 -0
  54. package/src/server.ts +4 -0
  55. package/src/slack/handlers.ts +11 -4
  56. package/src/slack/message-text.ts +98 -0
  57. package/src/slack/thread-buffer.ts +5 -3
  58. package/src/tests/claude-adapter-binary.test.ts +271 -74
  59. package/src/tests/create-page-tool.test.ts +19 -2
  60. package/src/tests/db-query.test.ts +28 -0
  61. package/src/tests/error-tracker.test.ts +121 -0
  62. package/src/tests/harness-provider-resolution.test.ts +33 -0
  63. package/src/tests/heartbeat-checklist.test.ts +36 -0
  64. package/src/tests/mcp-tools.test.ts +6 -0
  65. package/src/tests/mcp-transport-gc.test.ts +58 -0
  66. package/src/tests/memory-health-endpoint.test.ts +78 -0
  67. package/src/tests/memory-store.test.ts +221 -1
  68. package/src/tests/pages-http.test.ts +20 -2
  69. package/src/tests/pages-storage.test.ts +26 -0
  70. package/src/tests/prompt-template-session.test.ts +34 -5
  71. package/src/tests/script-runs-http.test.ts +278 -0
  72. package/src/tests/script-workflows-label-lint.test.ts +43 -0
  73. package/src/tests/script-workflows-runtime-e2e.test.ts +170 -0
  74. package/src/tests/scripts-mcp-e2e.test.ts +102 -2
  75. package/src/tests/seed-scripts.test.ts +468 -3
  76. package/src/tests/skill-files-http.test.ts +171 -0
  77. package/src/tests/skill-files.test.ts +162 -0
  78. package/src/tests/skill-get-file-tool.test.ts +110 -0
  79. package/src/tests/skill-sync.test.ts +125 -6
  80. package/src/tests/slack-message-text.test.ts +250 -0
  81. package/src/tests/system-default-skills.test.ts +40 -0
  82. package/src/tools/create-page.ts +2 -2
  83. package/src/tools/db-query.ts +16 -6
  84. package/src/tools/script-runs.ts +123 -0
  85. package/src/tools/skills/index.ts +1 -0
  86. package/src/tools/skills/skill-get-file.ts +80 -0
  87. package/src/tools/slack-read.ts +12 -3
  88. package/src/tools/tool-config.ts +6 -2
  89. package/src/types.ts +72 -0
  90. package/src/utils/error-tracker.ts +40 -1
  91. package/src/utils/internal-ai/complete-structured.ts +10 -4
  92. package/src/workflows/executors/raw-llm.ts +76 -59
  93. package/templates/schedules/daily-blocker-digest/content.md +68 -54
  94. package/templates/schedules/daily-compounding-reflection/content.md +4 -4
  95. package/templates/schedules/daily-hn-briefing/content.md +5 -5
  96. package/templates/schedules/daily-workflow-health-audit/content.md +6 -6
  97. package/templates/schedules/gtm-weekly-review/content.md +9 -9
  98. package/templates/schedules/weekly-dependabot-triage/content.md +24 -20
  99. package/templates/skills/agentmail-sending/content.md +6 -7
  100. package/templates/skills/desloppify/content.md +8 -9
  101. package/templates/skills/jira-interaction/content.md +25 -33
  102. package/templates/skills/kapso-whatsapp/content.md +29 -30
  103. package/templates/skills/linear-interaction/content.md +8 -9
  104. package/templates/skills/pages/content.md +205 -55
  105. package/templates/skills/profile-corruption-escalation/content.md +44 -85
  106. package/templates/skills/script-workflows/config.json +14 -0
  107. package/templates/skills/script-workflows/content.md +68 -0
  108. package/templates/skills/sprite-cli/content.md +4 -5
  109. package/templates/skills/swarm-scripts/content.md +2 -3
  110. package/templates/skills/turso-interaction/content.md +14 -17
  111. package/templates/skills/workflow-iterate/content.md +38 -391
  112. package/templates/skills/x-api-interactions/content.md +4 -6
  113. package/templates/skills/scheduled-task-resilience/config.json +0 -14
  114. package/templates/skills/scheduled-task-resilience/content.md +0 -95
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { publishCatalogReportPage } from "./catalog-report";
2
3
 
3
4
  export const argsSchema = z.object({
4
5
  days: z
@@ -13,10 +14,15 @@ export const argsSchema = z.object({
13
14
  .optional()
14
15
  .describe("Include schedule health flags (default true)"),
15
16
  includeMemoryHealth: z.boolean().optional().describe("Include memory health stats (default true)"),
17
+ includeScriptCandidates: z
18
+ .boolean()
19
+ .optional()
20
+ .describe("Include high-frequency tool-triplet candidates for future seed scripts (default true)"),
16
21
  includeByAgent: z
17
22
  .boolean()
18
23
  .optional()
19
24
  .describe("Include per-agent task/completion/failure breakdown (default true)"),
25
+ publishPage: z.boolean().optional().describe("Publish an authed HTML page (default true)"),
20
26
  });
21
27
 
22
28
  /**
@@ -40,6 +46,66 @@ function rowsToObjects(res: any): any[] {
40
46
  );
41
47
  }
42
48
 
49
+ function asNumber(value: any): number {
50
+ const n = Number(value ?? 0);
51
+ return Number.isFinite(n) ? n : 0;
52
+ }
53
+
54
+ function round1(value: number): number {
55
+ return Math.round(value * 10) / 10;
56
+ }
57
+
58
+ function percent(part: number, total: number): number {
59
+ return total > 0 ? round1((part / total) * 100) : 0;
60
+ }
61
+
62
+ function extractToolName(content: string): string | null {
63
+ const match = content.match(/"type"\s*:\s*"tool_use"[\s\S]*?"name"\s*:\s*"([^"]+)"/);
64
+ return match?.[1] ?? null;
65
+ }
66
+
67
+ function toolSlug(tool: string): string {
68
+ return tool
69
+ .replace(/^mcp__/, "")
70
+ .replace(/__/g, "-")
71
+ .replace(/_/g, "-")
72
+ .replace(/[^a-zA-Z0-9-]+/g, "-")
73
+ .replace(/^-+|-+$/g, "")
74
+ .toLowerCase();
75
+ }
76
+
77
+ function decodeFloat32Blob(value: any): Float32Array | null {
78
+ if (!value) return null;
79
+ let bytes: Uint8Array | null = null;
80
+ if (value instanceof Uint8Array) bytes = value;
81
+ else if (Array.isArray(value)) bytes = Uint8Array.from(value);
82
+ else if (typeof value === "object" && Array.isArray(value.data)) bytes = Uint8Array.from(value.data);
83
+ else if (typeof value === "object") {
84
+ const keys = Object.keys(value);
85
+ if (keys.length > 0 && keys.every((key) => /^\d+$/.test(key))) {
86
+ bytes = Uint8Array.from(Object.values(value) as number[]);
87
+ }
88
+ }
89
+ if (!bytes || bytes.byteLength < 4 || bytes.byteLength % 4 !== 0) return null;
90
+ return new Float32Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
91
+ }
92
+
93
+ function cosineSimilarity(a: Float32Array, b: Float32Array): number {
94
+ const len = Math.min(a.length, b.length);
95
+ let dot = 0;
96
+ let na = 0;
97
+ let nb = 0;
98
+ for (let i = 0; i < len; i++) {
99
+ const av = a[i] ?? 0;
100
+ const bv = b[i] ?? 0;
101
+ dot += av * bv;
102
+ na += av * av;
103
+ nb += bv * bv;
104
+ }
105
+ if (na === 0 || nb === 0) return 0;
106
+ return dot / (Math.sqrt(na) * Math.sqrt(nb));
107
+ }
108
+
43
109
  /**
44
110
  * Daily compounding insights — compressed JSON for Phase 0 evolution.
45
111
  *
@@ -54,7 +120,9 @@ export default async function compoundInsights(args: any, ctx: any) {
54
120
  const includeToolUsage = parsed.data.includeToolUsage !== false;
55
121
  const includeScheduleHealth = parsed.data.includeScheduleHealth !== false;
56
122
  const includeMemoryHealth = parsed.data.includeMemoryHealth !== false;
123
+ const includeScriptCandidates = parsed.data.includeScriptCandidates !== false;
57
124
  const includeByAgent = parsed.data.includeByAgent !== false;
125
+ const publishPage = parsed.data.publishPage !== false;
58
126
 
59
127
  // `days` is a validated positive int, so it is safe to interpolate into the
60
128
  // SQLite datetime modifier. EXCLUDED_FAIL is a fixed constant list.
@@ -144,27 +212,172 @@ export default async function compoundInsights(args: any, ctx: any) {
144
212
  ).map((r: any) => ({ tool: r.tool, calls: r.calls }));
145
213
  }
146
214
 
147
- // Memory health (whole store, by scope + source).
215
+ // Memory health (whole store, by scope + source). Pollution markers are
216
+ // SQL-light counts plus JS-side embedding similarity where available; prod
217
+ // SQLite does not expose a scalar cosine_similarity() function.
148
218
  if (includeMemoryHealth) {
149
219
  const memRows = rowsToObjects(
150
220
  await ctx.swarm.db_query({
151
- sql: `SELECT scope, source, count(*) as cnt FROM agent_memory GROUP BY scope, source`,
221
+ sql: `SELECT scope, source, count(*) as cnt,
222
+ sum(case when accessCount = 0 then 1 else 0 end) as zeroAccess,
223
+ sum(case when sourceTaskId IS NOT NULL OR sourcePath IS NOT NULL then 1 else 0 end) as referenced
224
+ FROM agent_memory GROUP BY scope, source`,
152
225
  }),
153
226
  );
154
227
  const totalMem = memRows.reduce((s: number, r: any) => s + (r.cnt ?? 0), 0);
228
+ const bySource: any = {};
229
+ for (const r of memRows) {
230
+ bySource[r.source] ??= {
231
+ total: 0,
232
+ percentOfStore: 0,
233
+ zeroAccess: 0,
234
+ zeroAccessPercent: 0,
235
+ referenced: 0,
236
+ };
237
+ bySource[r.source].total += asNumber(r.cnt);
238
+ bySource[r.source].zeroAccess += asNumber(r.zeroAccess);
239
+ bySource[r.source].referenced += asNumber(r.referenced);
240
+ }
241
+ for (const source of Object.keys(bySource)) {
242
+ bySource[source].percentOfStore = percent(bySource[source].total, totalMem);
243
+ bySource[source].zeroAccessPercent = percent(bySource[source].zeroAccess, bySource[source].total);
244
+ }
245
+
246
+ const autoSnapshotSources = ["session_summary", "task_completion"];
247
+ const autoSnapshotTotal = autoSnapshotSources.reduce(
248
+ (sum, source) => sum + (bySource[source]?.total ?? 0),
249
+ 0,
250
+ );
251
+ const popularButUseless = rowsToObjects(
252
+ await ctx.swarm.db_query({
253
+ sql: `SELECT id, name, source, accessCount, alpha, beta,
254
+ round(alpha / nullif(alpha + beta, 0), 3) as usefulness,
255
+ substr(content, 1, 180) as preview
256
+ FROM agent_memory
257
+ WHERE source IN ('session_summary','task_completion')
258
+ AND accessCount >= 5
259
+ AND alpha <= beta
260
+ ORDER BY accessCount DESC, beta DESC LIMIT 10`,
261
+ }),
262
+ ).map((r: any) => ({
263
+ id: r.id,
264
+ name: r.name,
265
+ source: r.source,
266
+ accessCount: asNumber(r.accessCount),
267
+ usefulness: Number(r.usefulness ?? 0),
268
+ preview: r.preview,
269
+ }));
270
+ const zeroAccessStaleRefRows = rowsToObjects(
271
+ await ctx.swarm.db_query({
272
+ sql: `SELECT source, count(*) as count
273
+ FROM agent_memory
274
+ WHERE accessCount = 0
275
+ AND (sourceTaskId IS NOT NULL OR sourcePath IS NOT NULL)
276
+ AND createdAt < datetime('now','-${days} days')
277
+ GROUP BY source ORDER BY count DESC`,
278
+ }),
279
+ );
280
+
281
+ const similarityRows = rowsToObjects(
282
+ await ctx.swarm.db_query({
283
+ sql: `SELECT id, name, source, accessCount, embedding
284
+ FROM agent_memory
285
+ WHERE source IN ('session_summary','task_completion')
286
+ AND embedding IS NOT NULL
287
+ ORDER BY accessCount DESC LIMIT 30`,
288
+ }),
289
+ );
290
+ let strongestAutoSnapshotPair: any = null;
291
+ const vectors = similarityRows
292
+ .map((r: any) => ({ ...r, vector: decodeFloat32Blob(r.embedding) }))
293
+ .filter((r: any) => r.vector);
294
+ for (let i = 0; i < vectors.length; i++) {
295
+ for (let j = i + 1; j < vectors.length; j++) {
296
+ const similarity = cosineSimilarity(vectors[i].vector, vectors[j].vector);
297
+ if (!strongestAutoSnapshotPair || similarity > strongestAutoSnapshotPair.similarity) {
298
+ strongestAutoSnapshotPair = {
299
+ similarity: round1(similarity * 100) / 100,
300
+ a: { id: vectors[i].id, name: vectors[i].name, source: vectors[i].source },
301
+ b: { id: vectors[j].id, name: vectors[j].name, source: vectors[j].source },
302
+ };
303
+ }
304
+ }
305
+ }
306
+
155
307
  insights.memoryHealth = {
156
308
  total: totalMem,
157
309
  byScope: memRows.reduce((m: any, r: any) => {
158
310
  m[r.scope] = (m[r.scope] ?? 0) + r.cnt;
159
311
  return m;
160
312
  }, {}),
161
- bySource: memRows.reduce((m: any, r: any) => {
162
- m[r.source] = (m[r.source] ?? 0) + r.cnt;
163
- return m;
164
- }, {}),
313
+ bySource,
314
+ pollution: {
315
+ autoSnapshotSources,
316
+ autoSnapshotTotal,
317
+ autoSnapshotPercent: percent(autoSnapshotTotal, totalMem),
318
+ popularButUselessAutoSnapshots: popularButUseless,
319
+ zeroAccessStaleRefs: {
320
+ total: zeroAccessStaleRefRows.reduce((sum: number, r: any) => sum + asNumber(r.count), 0),
321
+ bySource: zeroAccessStaleRefRows.reduce((m: any, r: any) => {
322
+ m[r.source] = asNumber(r.count);
323
+ return m;
324
+ }, {}),
325
+ },
326
+ similarityCheck: {
327
+ sqliteCosineSimilarityAvailable: false,
328
+ path: "js",
329
+ sampledAutoSnapshots: vectors.length,
330
+ strongestAutoSnapshotPair,
331
+ },
332
+ },
165
333
  };
166
334
  }
167
335
 
336
+ // Evolution/self-scripting candidates: high-frequency consecutive tool
337
+ // triplets are good prompts for a future seed script.
338
+ if (includeScriptCandidates) {
339
+ const rows = rowsToObjects(
340
+ await ctx.swarm.db_query({
341
+ sql: `WITH raw AS (
342
+ SELECT sessionId, iteration, lineNumber, content,
343
+ json_extract(content, '$.tool_name') as jsonToolName
344
+ FROM session_logs
345
+ WHERE createdAt > ${w}
346
+ AND (content LIKE '%"type":"tool_use"%' OR json_extract(content, '$.tool_name') IS NOT NULL)
347
+ )
348
+ SELECT sessionId, iteration, lineNumber, jsonToolName, content
349
+ FROM raw ORDER BY sessionId, iteration, lineNumber LIMIT 100`,
350
+ }),
351
+ );
352
+ const bySession = new Map<string, string[]>();
353
+ for (const row of rows) {
354
+ const tool = row.jsonToolName || extractToolName(String(row.content ?? ""));
355
+ if (!tool) continue;
356
+ const key = String(row.sessionId ?? "unknown");
357
+ const tools = bySession.get(key) ?? [];
358
+ tools.push(tool);
359
+ bySession.set(key, tools);
360
+ }
361
+ const counts = new Map<string, { tools: string[]; count: number }>();
362
+ for (const tools of bySession.values()) {
363
+ for (let i = 0; i <= tools.length - 3; i++) {
364
+ const triplet = tools.slice(i, i + 3);
365
+ const key = triplet.join(" -> ");
366
+ const current = counts.get(key) ?? { tools: triplet, count: 0 };
367
+ current.count += 1;
368
+ counts.set(key, current);
369
+ }
370
+ }
371
+ insights.scriptCandidates = [...counts.values()]
372
+ .sort((a, b) => b.count - a.count)
373
+ .slice(0, 10)
374
+ .map((r) => ({
375
+ tools: r.tools,
376
+ count: r.count,
377
+ suggestedName: r.tools.map(toolSlug).filter(Boolean).slice(0, 3).join("-").slice(0, 80),
378
+ }));
379
+ }
380
+
168
381
  // Per-agent breakdown — covers every agent that ran a task in the window.
169
382
  if (includeByAgent) {
170
383
  insights.byAgent = rowsToObjects(
@@ -184,5 +397,96 @@ export default async function compoundInsights(args: any, ctx: any) {
184
397
  }));
185
398
  }
186
399
 
400
+ if (publishPage) {
401
+ const failureFindings = (insights.failureClusters || []).map((cluster: any) => ({
402
+ id: `failure.${String(cluster.reason || "unknown").slice(0, 48)}`,
403
+ severity: cluster.count >= 5 ? "high" : cluster.count >= 2 ? "medium" : "low",
404
+ summary: `${cluster.count} real failure(s): ${cluster.reason}`,
405
+ action: "Review the repeated failure mode and decide whether to fix, retry, or add a temporary watch item.",
406
+ samples: [cluster],
407
+ }));
408
+ const scheduleFindings = (insights.scheduleHealth || []).map((schedule: any) => ({
409
+ id: `schedule.${schedule.id}`,
410
+ severity: schedule.failureRate >= 50 ? "high" : "medium",
411
+ summary: `${schedule.name} has ${schedule.failureRate}% real-failure rate.`,
412
+ action: "Inspect recent schedule tasks and repair, retarget, or disable the schedule.",
413
+ samples: [schedule],
414
+ }));
415
+ const memoryPollution = insights.memoryHealth?.pollution;
416
+ const memoryFindings = memoryPollution?.autoSnapshotPercent
417
+ ? [
418
+ {
419
+ id: "memory.auto-snapshot-share",
420
+ severity: memoryPollution.autoSnapshotPercent >= 40 ? "high" : "medium",
421
+ summary: `Automatic snapshots are ${memoryPollution.autoSnapshotPercent}% of memory.`,
422
+ action: "Review memory gates and prune low-use automatic snapshots before adding more.",
423
+ samples: [memoryPollution],
424
+ },
425
+ ]
426
+ : [];
427
+ const scriptFindings = (insights.scriptCandidates || []).map((candidate: any) => ({
428
+ id: `script-candidate.${candidate.suggestedName || "unnamed"}`,
429
+ severity: candidate.count >= 3 ? "medium" : "low",
430
+ summary: `${candidate.count} repeated tool triplet(s): ${candidate.tools.join(" -> ")}`,
431
+ action: "Consider turning this repeated workflow into a reusable seeded script.",
432
+ samples: [candidate],
433
+ }));
434
+
435
+ insights.page = await publishCatalogReportPage(
436
+ {
437
+ title: "Compound Insights Audit",
438
+ slug: "compound-insights",
439
+ description: "Swarm-wide daily ops snapshot for compounding and reliability review.",
440
+ generatedAt: insights.generatedAt,
441
+ lede: `Swarm-wide ${days}-day snapshot: ${insights.taskSummary.total} task(s), ${insights.taskSummary.completionRate}% completion rate, ${insights.taskSummary.failureRate}% failure rate.`,
442
+ metrics: [
443
+ ["Tasks", insights.taskSummary.total],
444
+ ["Completed", insights.taskSummary.completed],
445
+ ["Failed", insights.taskSummary.failed],
446
+ ["Failure clusters", insights.failureClusters?.length || 0],
447
+ ],
448
+ sections: [
449
+ {
450
+ key: "failures",
451
+ goal: "Expose repeated real failure modes without counting bookkeeping noise.",
452
+ findingCount: failureFindings.length,
453
+ checks: insights.taskSummary,
454
+ findings: failureFindings,
455
+ },
456
+ {
457
+ key: "schedules",
458
+ goal: "Keep schedule failures visible before daily work compounds stale assumptions.",
459
+ findingCount: scheduleFindings.length,
460
+ checks: { unhealthySchedules: scheduleFindings.length },
461
+ findings: scheduleFindings,
462
+ },
463
+ {
464
+ key: "memory",
465
+ goal: "Detect memory bloat and low-use automatic snapshots.",
466
+ findingCount: memoryFindings.length,
467
+ checks: insights.memoryHealth
468
+ ? {
469
+ total: insights.memoryHealth.total,
470
+ autoSnapshotPercent: memoryPollution?.autoSnapshotPercent ?? 0,
471
+ sampledAutoSnapshots:
472
+ memoryPollution?.similarityCheck?.sampledAutoSnapshots ?? 0,
473
+ }
474
+ : {},
475
+ findings: memoryFindings,
476
+ },
477
+ {
478
+ key: "script-candidates",
479
+ goal: "Find repeated tool chains worth compressing into reusable scripts.",
480
+ findingCount: scriptFindings.length,
481
+ checks: { candidates: scriptFindings.length },
482
+ findings: scriptFindings,
483
+ },
484
+ ],
485
+ appendix: insights,
486
+ },
487
+ ctx,
488
+ );
489
+ }
490
+
187
491
  return insights;
188
492
  }
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  export const argsSchema = z.object({
4
- repo: z.string().describe("Repository in 'owner/name' form, e.g. 'desplega-ai/agent-swarm'"),
4
+ repo: z.string().describe("Repository in 'owner/name' form, e.g. 'owner/name'"),
5
5
  number: z.number().int().positive().describe("Pull request number"),
6
6
  token: z
7
7
  .string()