@desplega.ai/agent-swarm 1.92.2 → 1.93.0

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 (76) hide show
  1. package/openapi.json +63 -3
  2. package/package.json +5 -5
  3. package/src/be/db.ts +91 -6
  4. package/src/be/memory/boot-reembed.ts +0 -1
  5. package/src/be/memory/providers/sqlite-store.ts +42 -25
  6. package/src/be/memory/raters/llm-client.ts +12 -5
  7. package/src/be/memory/types.ts +3 -0
  8. package/src/be/migrations/088_script_runs_list_indexes.sql +10 -0
  9. package/src/be/migrations/089_harness_variant.sql +2 -0
  10. package/src/be/modelsdev-cache.json +1222 -986
  11. package/src/be/seed-pricing.ts +1 -0
  12. package/src/be/seed-scripts/catalog/boot-triage.inline.ts +221 -0
  13. package/src/be/seed-scripts/catalog/catalog-report.inline.ts +457 -0
  14. package/src/be/seed-scripts/catalog/compound-insights.inline.ts +863 -0
  15. package/src/be/seed-scripts/catalog/ops-catalog-audit.inline.ts +506 -0
  16. package/src/be/seed-scripts/index.ts +5 -5
  17. package/src/be/skill-sync.ts +28 -179
  18. package/src/commands/runner.ts +124 -7
  19. package/src/http/api-keys.ts +42 -0
  20. package/src/http/mcp-bridge.ts +1 -1
  21. package/src/http/memory.ts +23 -24
  22. package/src/http/tasks.ts +10 -6
  23. package/src/providers/claude-adapter.ts +33 -1
  24. package/src/providers/claude-managed-adapter.ts +3 -0
  25. package/src/providers/claude-managed-models.ts +7 -0
  26. package/src/providers/codex-adapter.ts +8 -1
  27. package/src/providers/codex-models.ts +1 -0
  28. package/src/providers/codex-oauth/auth-json.ts +1 -0
  29. package/src/providers/harness-version.ts +7 -0
  30. package/src/providers/opencode-adapter.ts +11 -4
  31. package/src/providers/pi-mono-adapter.ts +12 -2
  32. package/src/providers/types.ts +2 -0
  33. package/src/scripts-runtime/egress-secrets.ts +83 -0
  34. package/src/scripts-runtime/eval-harness.ts +4 -0
  35. package/src/scripts-runtime/executors/types.ts +7 -0
  36. package/src/scripts-runtime/loader.ts +2 -0
  37. package/src/server-user.ts +2 -2
  38. package/src/slack/channel-join.ts +41 -0
  39. package/src/tests/additive-buffer.test.ts +0 -1
  40. package/src/tests/api-key-tracking.test.ts +113 -0
  41. package/src/tests/approval-requests.test.ts +0 -6
  42. package/src/tests/claude-managed-setup.test.ts +0 -4
  43. package/src/tests/codex-pool.test.ts +2 -6
  44. package/src/tests/http-api-integration.test.ts +4 -6
  45. package/src/tests/memory-edges.test.ts +0 -2
  46. package/src/tests/memory-rate-endpoint.test.ts +0 -2
  47. package/src/tests/memory-rater-e2e.test.ts +0 -2
  48. package/src/tests/memory-store.test.ts +19 -1
  49. package/src/tests/memory.test.ts +51 -0
  50. package/src/tests/model-control.test.ts +1 -1
  51. package/src/tests/reload-config.test.ts +33 -17
  52. package/src/tests/runner-skills-refresh.test.ts +216 -46
  53. package/src/tests/script-runs-http.test.ts +7 -1
  54. package/src/tests/scripts-runtime-secret-egress.test.ts +129 -0
  55. package/src/tests/seed-scripts.test.ts +13 -1
  56. package/src/tests/session-attach.test.ts +6 -6
  57. package/src/tests/skill-fs-writer.test.ts +250 -0
  58. package/src/tests/slack-attachments-block.test.ts +0 -1
  59. package/src/tests/slack-blocks.test.ts +0 -1
  60. package/src/tests/slack-channel-join.test.ts +80 -0
  61. package/src/tests/slack-identity-resolution.test.ts +0 -1
  62. package/src/tests/structured-output.test.ts +0 -2
  63. package/src/tests/use-dismissible-card.test.ts +0 -4
  64. package/src/tools/schedules/create-schedule.ts +2 -2
  65. package/src/tools/schedules/update-schedule.ts +1 -1
  66. package/src/tools/send-task.ts +2 -2
  67. package/src/tools/slack-post.ts +18 -15
  68. package/src/tools/slack-read.ts +9 -11
  69. package/src/tools/slack-reply.ts +18 -15
  70. package/src/tools/slack-start-thread.ts +17 -14
  71. package/src/tools/task-action.ts +2 -2
  72. package/src/types.ts +11 -0
  73. package/src/utils/context-window.ts +3 -0
  74. package/src/utils/credentials.ts +22 -2
  75. package/src/utils/skill-fs-writer.ts +220 -0
  76. package/src/utils/skills-refresh.ts +123 -40
@@ -0,0 +1,506 @@
1
+ import { z } from "zod";
2
+ import {
3
+ type CatalogReport,
4
+ publishCatalogReportPage,
5
+ renderCatalogReportPage,
6
+ } from "./catalog-report";
7
+
8
+ export const argsSchema = z.object({
9
+ nowIso: z.string().optional().describe("Audit clock override (default: current time)"),
10
+ publishPage: z.boolean().optional().describe("Publish an authed HTML page (default true)"),
11
+ includeSamples: z
12
+ .boolean()
13
+ .optional()
14
+ .describe("Include small row samples for each finding cluster (default true)"),
15
+ staleScheduleDays: z
16
+ .number()
17
+ .int()
18
+ .positive()
19
+ .optional()
20
+ .describe("Flag enabled schedules with no run for this many days (default 14)"),
21
+ tempScheduleNames: z
22
+ .array(z.string())
23
+ .optional()
24
+ .describe("Known temporary schedule name fragments that should self-lift"),
25
+ });
26
+
27
+ const CODE_WORK_RE =
28
+ /\b(git|github|gh\b|gh-cli|docker|docker-compose|bun|npm|pnpm|yarn|tsc|eslint|lint|test|pr\b|pull request|branch|commit|repo|worktree|typescript|javascript)\b/i;
29
+ const CODE_AGENT_RE =
30
+ /\b(code|coder|coding|implement|implementation|engineer|software|typescript|javascript|repo|github)\b/i;
31
+ const NON_CODE_AGENT_RE = /\b(content|reviewer|research|sales|gtm|support|ops|lead)\b/i;
32
+ const SMOKE_WORKFLOW_RE =
33
+ /\b(smoke|demo|litmus-smoke|one[- ]shot|validation|des-462-gate-validation|gsc-runtime-smoke)\b/i;
34
+ const GATE_WORKFLOW_RE = /\b(litmus|gate|eval|review|validation|quality|structured)\b/i;
35
+ const STALE_URL_RE =
36
+ /\b(localhost|127\.0\.0\.1|api\.example-swarm\.dev|app\.example-swarm\.dev|example-swarm\.dev|fly\.dev)\b/i;
37
+ const CONTRADICTORY_RE =
38
+ /\b(do not|don't|never)\b[\s\S]{0,240}\b(always|must|required)\b|\b(always|must|required)\b[\s\S]{0,240}\b(do not|don't|never)\b/i;
39
+
40
+ const CODE_REGISTRY_EVENTS =
41
+ "agentmail.email.followup agentmail.email.mapped_lead agentmail.email.mapped_worker agentmail.email.no_agent agentmail.email.unmapped common.command_suggestions.github_comment_issue common.command_suggestions.github_comment_pr common.command_suggestions.github_issue common.command_suggestions.github_pr common.command_suggestions.gitlab_issue common.command_suggestions.gitlab_mr common.delegation_instruction common.delegation_instruction.gitlab github.check_run.failed github.check_suite.failed github.comment.mentioned github.issue.assigned github.issue.labeled github.issue.mentioned github.pull_request.assigned github.pull_request.closed github.pull_request.labeled github.pull_request.mentioned github.pull_request.review_requested github.pull_request.review_submitted github.pull_request.synchronize github.workflow_run.failed gitlab.comment.mentioned gitlab.issue.assigned gitlab.merge_request.opened gitlab.pipeline.failed heartbeat.boot-triage heartbeat.checklist jira.issue.assigned jira.issue.commented jira.issue.followup kapso.message.received linear.issue.assigned linear.issue.followup linear.issue.reassigned slack.assistant.greeting slack.assistant.offline slack.assistant.suggested_prompts slack.message.thread_context system.agent.agent_fs system.agent.artifacts system.agent.code_quality system.agent.context_mode system.agent.filesystem system.agent.lead system.agent.register system.agent.role system.agent.seed_scripts system.agent.self_awareness system.agent.services system.agent.share_urls system.agent.slack system.agent.system system.agent.worker system.agent.worker.remote system.agent.worker.slack system.session.lead system.session.worker system.session.worker.pi system.session.worker.remote task.budget.refused task.requester.profile task.worker.completed task.worker.failed".split(
42
+ " ",
43
+ );
44
+
45
+ function rowsToObjects(res: any): any[] {
46
+ const p = res?.data ?? res;
47
+ const cols: string[] = p?.columns ?? [];
48
+ return (p?.rows ?? []).map((r: any) =>
49
+ Array.isArray(r) ? Object.fromEntries(cols.map((c, i) => [c, r[i]])) : r,
50
+ );
51
+ }
52
+
53
+ async function query(ctx: any, sql: string, params?: unknown[]): Promise<any[]> {
54
+ try {
55
+ return rowsToObjects(await ctx.swarm.db_query({ sql, params }));
56
+ } catch (error) {
57
+ return [{ unavailable: error instanceof Error ? error.message : String(error) }];
58
+ }
59
+ }
60
+
61
+ function safeJson(value: unknown, fallback: any = null): any {
62
+ if (typeof value !== "string") return value ?? fallback;
63
+ try {
64
+ return JSON.parse(value);
65
+ } catch {
66
+ return fallback;
67
+ }
68
+ }
69
+
70
+ function asBool(value: unknown): boolean {
71
+ return value === true || value === 1 || value === "1";
72
+ }
73
+
74
+ function asText(value: unknown): string {
75
+ return typeof value === "string" ? value : value == null ? "" : JSON.stringify(value);
76
+ }
77
+
78
+ function compactText(value: unknown, max = 180): string {
79
+ return asText(value).replace(/\s+/g, " ").trim().slice(0, max);
80
+ }
81
+
82
+ function formatMetric(value: unknown): string {
83
+ if (typeof value === "number") return new Intl.NumberFormat("en-US").format(value);
84
+ return asText(value);
85
+ }
86
+
87
+ function daysSince(iso: string | null | undefined, nowMs: number): number | null {
88
+ if (!iso) return null;
89
+ const t = Date.parse(iso);
90
+ if (!Number.isFinite(t)) return null;
91
+ return Math.round((nowMs - t) / 86400000);
92
+ }
93
+
94
+ function severity(rank: number): "critical" | "high" | "medium" | "low" {
95
+ if (rank >= 4) return "critical";
96
+ if (rank === 3) return "high";
97
+ if (rank === 2) return "medium";
98
+ return "low";
99
+ }
100
+
101
+ function sample<T>(rows: T[], includeSamples: boolean, limit = 5): T[] {
102
+ return includeSamples ? rows.slice(0, limit) : [];
103
+ }
104
+
105
+ function isCodeCapable(agent: any): boolean {
106
+ if (!agent) return false;
107
+ const text = [
108
+ agent.name,
109
+ agent.role,
110
+ agent.description,
111
+ Array.isArray(agent.capabilities) ? agent.capabilities.join(" ") : agent.capabilities,
112
+ ]
113
+ .filter(Boolean)
114
+ .join(" ");
115
+ if (!CODE_AGENT_RE.test(text)) return false;
116
+ if (NON_CODE_AGENT_RE.test(text) && !/\bpicateclas\b/i.test(text)) return false;
117
+ return true;
118
+ }
119
+
120
+ function nodeList(definition: any): any[] {
121
+ return Array.isArray(definition?.nodes) ? definition.nodes : [];
122
+ }
123
+
124
+ function hasStructuredOutput(value: any): boolean {
125
+ return /"outputSchema"|"schema"|"jsonSchema"|"structured"/i.test(JSON.stringify(value ?? {}));
126
+ }
127
+
128
+ export function buildReport(result: any): CatalogReport {
129
+ return {
130
+ title: "Ops Catalog Audit",
131
+ slug: "ops-catalog-audit",
132
+ description: "Clustered audit-as-code report for schedules, workflows, and prompts/templates.",
133
+ generatedAt: result.generatedAt,
134
+ lede: `A re-runnable audit of schedules, workflows, and prompt/template catalogs. It found ${formatMetric(
135
+ result.summary.findingsTotal,
136
+ )} actionable issue cluster(s), with the highest-risk items called out first inside each group.`,
137
+ metrics: [
138
+ ["Findings", result.summary.findingsTotal],
139
+ ["Schedules enabled", result.summary.schedulesEnabled],
140
+ ["Workflows enabled", result.summary.workflowsEnabled],
141
+ ["Prompt templates", result.summary.promptTemplates],
142
+ ],
143
+ sections: ["schedules", "workflows", "promptsTemplates"].map((key) => ({
144
+ key,
145
+ goal: result.goals[key].goal,
146
+ findingCount: result.goals[key].findingCount,
147
+ checks: result.goals[key].checks,
148
+ findings: result.goals[key].findings,
149
+ })),
150
+ appendix: result,
151
+ };
152
+ }
153
+
154
+ export function renderPage(result: any): string {
155
+ return renderCatalogReportPage(buildReport(result));
156
+ }
157
+
158
+ /** Audit schedules, workflows, and prompt/template catalogs by goal, with optional authed page output. */
159
+ export default async function opsCatalogAudit(args: any, ctx: any) {
160
+ const parsed = argsSchema.safeParse(args || {});
161
+ if (!parsed.success) return { error: "invalid args: " + parsed.error.message };
162
+
163
+ const now = parsed.data.nowIso ? new Date(parsed.data.nowIso) : new Date();
164
+ const nowMs = now.getTime();
165
+ const publishPage = parsed.data.publishPage !== false;
166
+ const includeSamples = parsed.data.includeSamples !== false;
167
+ const staleScheduleDays = parsed.data.staleScheduleDays || 14;
168
+ const tempScheduleNames = parsed.data.tempScheduleNames || [
169
+ "memory-gate-597",
170
+ "swarm-postdeploy-memory",
171
+ ];
172
+
173
+ const scheduleRows = await query(
174
+ ctx,
175
+ `SELECT s.*, a.name as targetAgentName, a.role as targetAgentRole,
176
+ a.description as targetAgentDescription, a.capabilities as targetAgentCapabilities,
177
+ a.provider as targetAgentProvider, a.harness_provider as targetAgentHarnessProvider
178
+ FROM scheduled_tasks s LEFT JOIN agents a ON a.id = s.targetAgentId
179
+ WHERE s.enabled = 1 ORDER BY s.name ASC`,
180
+ );
181
+ const schedules = scheduleRows.filter((r) => !r.unavailable);
182
+ const agentsById = new Map<string, any>();
183
+ for (const s of schedules) {
184
+ if (!s.targetAgentId) continue;
185
+ agentsById.set(s.targetAgentId, {
186
+ id: s.targetAgentId,
187
+ name: s.targetAgentName,
188
+ role: s.targetAgentRole,
189
+ description: s.targetAgentDescription,
190
+ capabilities: safeJson(s.targetAgentCapabilities, []),
191
+ provider: s.targetAgentProvider,
192
+ harnessProvider: s.targetAgentHarnessProvider,
193
+ });
194
+ }
195
+
196
+ const duplicateCronGroups = Object.values(
197
+ schedules.reduce((acc: any, s: any) => {
198
+ const cron = s.cronExpression || (s.intervalMs ? `interval:${s.intervalMs}` : "");
199
+ if (!cron) return acc;
200
+ const key = `${cron}::${s.timezone || "UTC"}`;
201
+ acc[key] ??= { key, cron, timezone: s.timezone || "UTC", schedules: [] };
202
+ acc[key].schedules.push({ id: s.id, name: s.name });
203
+ return acc;
204
+ }, {}),
205
+ ).filter((g: any) => g.schedules.length > 1);
206
+
207
+ const deadSchedules = schedules.filter((s: any) => {
208
+ const noNext = !s.nextRunAt && s.scheduleType === "recurring";
209
+ const stale = (daysSince(s.lastRunAt, nowMs) ?? 0) >= staleScheduleDays;
210
+ return noNext || stale || Number(s.consecutiveErrors || 0) > 0;
211
+ });
212
+
213
+ const tempSchedules = schedules.filter((s: any) => {
214
+ const haystack = `${s.name || ""}\n${s.description || ""}\n${s.taskTemplate || ""}`.toLowerCase();
215
+ const known = tempScheduleNames.some((name) => haystack.includes(name.toLowerCase()));
216
+ const dateMatches = [
217
+ ...haystack.matchAll(
218
+ /\b(?:self[- ]lift|remove|disable|until|through|expires?)\D{0,40}(\d{4}-\d{2}-\d{2})/g,
219
+ ),
220
+ ];
221
+ const expired = dateMatches.some((m) => (m[1] ? Date.parse(m[1]) <= nowMs : false));
222
+ return known || expired;
223
+ });
224
+
225
+ const routingRisks = schedules
226
+ .filter((s: any) => CODE_WORK_RE.test(`${s.name || ""}\n${s.tags || ""}\n${s.taskTemplate || ""}`))
227
+ .map((s: any) => {
228
+ const target = s.targetAgentId ? agentsById.get(s.targetAgentId) : null;
229
+ const pool = !s.targetAgentId;
230
+ const opencode = target?.provider === "opencode" || target?.harnessProvider === "opencode";
231
+ const risky = pool || !isCodeCapable(target) || opencode;
232
+ return risky
233
+ ? {
234
+ id: s.id,
235
+ name: s.name,
236
+ targetAgentId: s.targetAgentId || null,
237
+ targetAgentName: target?.name || null,
238
+ reason: pool
239
+ ? "pool-targeted code work"
240
+ : opencode
241
+ ? "opencode target for code work"
242
+ : "target is not code-capable",
243
+ action: "Pin this schedule to a code-capable worker targetAgentId before it runs again.",
244
+ }
245
+ : null;
246
+ })
247
+ .filter(Boolean);
248
+
249
+ const workflowRows = await query(
250
+ ctx,
251
+ `SELECT id, name, description, enabled, definition, triggers, input, triggerSchema, createdAt, lastUpdatedAt
252
+ FROM workflows ORDER BY name ASC`,
253
+ );
254
+ const workflows = workflowRows.filter((r) => !r.unavailable);
255
+ const enabledWorkflows = workflows.filter((w: any) => asBool(w.enabled));
256
+ const smokeEnabled = enabledWorkflows
257
+ .filter((w: any) => SMOKE_WORKFLOW_RE.test(`${w.name || ""}\n${w.description || ""}`))
258
+ .map((w: any) => ({
259
+ id: w.id,
260
+ name: w.name,
261
+ action: "Disable or delete if this was only a smoke/eval fixture.",
262
+ }));
263
+ const gateCoverageGaps = enabledWorkflows
264
+ .map((w: any) => {
265
+ const definition = safeJson(w.definition, {});
266
+ const text = `${w.name || ""}\n${w.description || ""}\n${JSON.stringify(definition)}`;
267
+ const structured =
268
+ hasStructuredOutput(definition) ||
269
+ hasStructuredOutput(safeJson(w.input, {})) ||
270
+ hasStructuredOutput(safeJson(w.triggerSchema, {}));
271
+ if (!GATE_WORKFLOW_RE.test(text) || structured) return null;
272
+ return {
273
+ id: w.id,
274
+ name: w.name,
275
+ nodeCount: nodeList(definition).length,
276
+ action: "Add outputSchema/schema coverage to litmus/eval/gate nodes so downstream checks are deterministic.",
277
+ };
278
+ })
279
+ .filter(Boolean);
280
+ const workflowTypeRows = enabledWorkflows.map((w: any) => {
281
+ const definition = safeJson(w.definition, {});
282
+ const nodes = nodeList(definition);
283
+ const nodeTypes = Array.from(new Set(nodes.map((n: any) => n.type).filter(Boolean))).sort();
284
+ const loadBearing =
285
+ !SMOKE_WORKFLOW_RE.test(`${w.name || ""}\n${w.description || ""}`) && nodes.length > 1;
286
+ return {
287
+ id: w.id,
288
+ name: w.name,
289
+ nodeCount: nodes.length,
290
+ nodeTypes,
291
+ class: loadBearing ? "load-bearing" : "fixture-or-small",
292
+ };
293
+ });
294
+
295
+ const promptRows = await query(
296
+ ctx,
297
+ `SELECT id, eventType, scope, scopeId, state, body, isDefault, version, createdBy, updatedAt
298
+ FROM prompt_templates ORDER BY eventType ASC, scope ASC`,
299
+ );
300
+ const prompts = promptRows.filter((r) => !r.unavailable);
301
+ const expectedEvents = new Set(CODE_REGISTRY_EVENTS);
302
+ const defaultEvents = new Set(prompts.filter((p: any) => asBool(p.isDefault)).map((p: any) => p.eventType));
303
+ const liveEvents = new Set(prompts.map((p: any) => p.eventType));
304
+ const missingDefaultEvents = [...expectedEvents].filter((e) => !defaultEvents.has(e)).sort();
305
+ const dbOnlyEvents = [...liveEvents].filter((e) => !expectedEvents.has(e) && !e.startsWith("test.")).sort();
306
+
307
+ const bodyGroups: Record<string, any[]> = {};
308
+ for (const p of prompts) {
309
+ const key = compactText(p.body, 500);
310
+ if (!key) continue;
311
+ bodyGroups[key] ??= [];
312
+ bodyGroups[key].push({
313
+ id: p.id,
314
+ eventType: p.eventType,
315
+ scope: p.scope,
316
+ scopeId: p.scopeId,
317
+ });
318
+ }
319
+ const duplicatePromptBodies = Object.values(bodyGroups).filter((group) => group.length > 1);
320
+ const staleUrlPrompts = prompts
321
+ .filter((p: any) => STALE_URL_RE.test(p.body || ""))
322
+ .map((p: any) => ({
323
+ id: p.id,
324
+ eventType: p.eventType,
325
+ scope: p.scope,
326
+ match: (p.body.match(STALE_URL_RE) || [])[0],
327
+ }));
328
+ const contradictoryPrompts = prompts
329
+ .filter((p: any) => CONTRADICTORY_RE.test(p.body || ""))
330
+ .map((p: any) => ({ id: p.id, eventType: p.eventType, scope: p.scope }));
331
+ const skillDuplicateRows = await query(
332
+ ctx,
333
+ `SELECT name, count(*) as count,
334
+ group_concat(scope || ':' || coalesce(ownerAgentId, 'global'), ', ') as locations
335
+ FROM skills
336
+ WHERE isEnabled = 1 AND systemDefault = 1
337
+ GROUP BY name HAVING count(*) > 1 ORDER BY count DESC, name ASC`,
338
+ );
339
+ const systemDefaultSkillDuplicates = skillDuplicateRows.filter((r) => !r.unavailable);
340
+ const skillDuplicateUnavailable = skillDuplicateRows.find((r) => r.unavailable)?.unavailable;
341
+
342
+ const findings = {
343
+ schedules: [
344
+ duplicateCronGroups.length && {
345
+ id: "schedules.duplicate-crons",
346
+ severity: severity(duplicateCronGroups.length > 2 ? 3 : 2),
347
+ summary: `${duplicateCronGroups.length} duplicate enabled cron/interval group(s).`,
348
+ action: "Confirm whether each duplicate group is intentional; consolidate or retarget redundant schedules.",
349
+ samples: sample(duplicateCronGroups, includeSamples),
350
+ },
351
+ deadSchedules.length && {
352
+ id: "schedules.dead-or-stale",
353
+ severity: severity(deadSchedules.length > 3 ? 3 : 2),
354
+ summary: `${deadSchedules.length} enabled schedule(s) look dead, stale, or erroring.`,
355
+ action: `Disable dead schedules or repair nextRunAt/cron/errors. Stale threshold: ${staleScheduleDays} days.`,
356
+ samples: sample(
357
+ deadSchedules.map((s: any) => ({
358
+ id: s.id,
359
+ name: s.name,
360
+ nextRunAt: s.nextRunAt,
361
+ lastRunAt: s.lastRunAt,
362
+ consecutiveErrors: s.consecutiveErrors,
363
+ })),
364
+ includeSamples,
365
+ ),
366
+ },
367
+ tempSchedules.length && {
368
+ id: "schedules.temporary-self-lift",
369
+ severity: "high",
370
+ summary: `${tempSchedules.length} temporary monitor schedule(s) may be past self-lift.`,
371
+ action: "Review the named temporary monitors and disable/delete the ones whose guard window has expired.",
372
+ samples: sample(
373
+ tempSchedules.map((s: any) => ({ id: s.id, name: s.name })),
374
+ includeSamples,
375
+ ),
376
+ },
377
+ routingRisks.length && {
378
+ id: "schedules.rule-13-15-routing",
379
+ severity: "critical",
380
+ summary: `${routingRisks.length} enabled code-work schedule(s) are not pinned to a code-capable worker.`,
381
+ action: "Set targetAgentId to a code-capable worker; never leave git/docker/bun/gh work on the pool.",
382
+ samples: sample(routingRisks, includeSamples),
383
+ },
384
+ ].filter(Boolean),
385
+ workflows: [
386
+ smokeEnabled.length && {
387
+ id: "workflows.enabled-fixtures",
388
+ severity: severity(smokeEnabled.length > 2 ? 3 : 2),
389
+ summary: `${smokeEnabled.length} enabled workflow(s) look like smoke/demo/one-shot fixtures.`,
390
+ action: "Disable fixture workflows unless they are explicitly load-bearing production gates.",
391
+ samples: sample(smokeEnabled, includeSamples),
392
+ },
393
+ gateCoverageGaps.length && {
394
+ id: "workflows.structured-output-gaps",
395
+ severity: "high",
396
+ summary: `${gateCoverageGaps.length} litmus/eval/gate workflow(s) lack visible structured-output schema coverage.`,
397
+ action: "Add outputSchema/schema coverage so gate outputs can be regression-checked.",
398
+ samples: sample(gateCoverageGaps, includeSamples),
399
+ },
400
+ ].filter(Boolean),
401
+ promptsTemplates: [
402
+ (missingDefaultEvents.length || dbOnlyEvents.length) && {
403
+ id: "prompts.registry-drift",
404
+ severity: severity(missingDefaultEvents.length > 0 ? 3 : 2),
405
+ summary: `${missingDefaultEvents.length} code registry event(s) missing default DB rows; ${dbOnlyEvents.length} DB event(s) absent from code registry.`,
406
+ action: "Re-seed prompt templates or remove stale DB-only templates after confirming no runtime still emits them.",
407
+ samples: sample(
408
+ [
409
+ ...missingDefaultEvents.slice(0, 5).map((eventType) => ({
410
+ kind: "missing-default",
411
+ eventType,
412
+ })),
413
+ ...dbOnlyEvents.slice(0, 5).map((eventType) => ({ kind: "db-only", eventType })),
414
+ ],
415
+ includeSamples,
416
+ 10,
417
+ ),
418
+ },
419
+ duplicatePromptBodies.length && {
420
+ id: "prompts.redundant-bodies",
421
+ severity: "medium",
422
+ summary: `${duplicatePromptBodies.length} prompt body group(s) are duplicated across templates.`,
423
+ action: "Extract shared text into a template reference or remove redundant overrides.",
424
+ samples: sample(duplicatePromptBodies, includeSamples, 3),
425
+ },
426
+ staleUrlPrompts.length && {
427
+ id: "prompts.stale-urls-hosts",
428
+ severity: "high",
429
+ summary: `${staleUrlPrompts.length} prompt template(s) contain stale/local/example hosts.`,
430
+ action: "Replace hardcoded hosts with runtime env-var guidance or current public hosts.",
431
+ samples: sample(staleUrlPrompts, includeSamples),
432
+ },
433
+ contradictoryPrompts.length && {
434
+ id: "prompts.contradictory-instructions",
435
+ severity: "medium",
436
+ summary: `${contradictoryPrompts.length} prompt template(s) contain nearby must/never style conflicts worth review.`,
437
+ action: "Tighten redundant or conflicting instruction blocks so workers do not receive mixed routing guidance.",
438
+ samples: sample(contradictoryPrompts, includeSamples),
439
+ },
440
+ (systemDefaultSkillDuplicates.length || skillDuplicateUnavailable) && {
441
+ id: "prompts.system-default-skill-duplicates",
442
+ severity: systemDefaultSkillDuplicates.length ? "high" : "low",
443
+ summary: skillDuplicateUnavailable
444
+ ? `Could not query systemDefault skill duplicates: ${skillDuplicateUnavailable}`
445
+ : `${systemDefaultSkillDuplicates.length} systemDefault skill name(s) are duplicated.`,
446
+ action: "Deduplicate system-default skills so prompt skill seeding is stable and non-redundant.",
447
+ samples: sample(systemDefaultSkillDuplicates, includeSamples),
448
+ },
449
+ ].filter(Boolean),
450
+ };
451
+
452
+ const result: any = {
453
+ generatedAt: now.toISOString(),
454
+ script: "ops-catalog-audit",
455
+ summary: {
456
+ schedulesEnabled: schedules.length,
457
+ workflowsTotal: workflows.length,
458
+ workflowsEnabled: enabledWorkflows.length,
459
+ promptTemplates: prompts.length,
460
+ findingsTotal:
461
+ findings.schedules.length + findings.workflows.length + findings.promptsTemplates.length,
462
+ },
463
+ goals: {
464
+ schedules: {
465
+ goal: "Reduce schedule cost/context waste and prevent misrouted code work.",
466
+ findingCount: findings.schedules.length,
467
+ checks: {
468
+ duplicateCronGroups: duplicateCronGroups.length,
469
+ deadOrStaleSchedules: deadSchedules.length,
470
+ temporarySelfLiftSchedules: tempSchedules.length,
471
+ routingRisks: routingRisks.length,
472
+ },
473
+ findings: findings.schedules,
474
+ },
475
+ workflows: {
476
+ goal: "Separate load-bearing workflows from fixtures and enforce deterministic gate outputs.",
477
+ findingCount: findings.workflows.length,
478
+ checks: {
479
+ enabledFixtures: smokeEnabled.length,
480
+ structuredOutputGaps: gateCoverageGaps.length,
481
+ loadBearingCount: workflowTypeRows.filter((w) => w.class === "load-bearing").length,
482
+ fixtureOrSmallCount: workflowTypeRows.filter((w) => w.class === "fixture-or-small").length,
483
+ },
484
+ workflowClasses: sample(workflowTypeRows, includeSamples, 20),
485
+ findings: findings.workflows,
486
+ },
487
+ promptsTemplates: {
488
+ goal: "Keep prompt registry, runtime defaults, host guidance, and skill seed blocks aligned.",
489
+ findingCount: findings.promptsTemplates.length,
490
+ checks: {
491
+ codeRegistryEvents: CODE_REGISTRY_EVENTS.length,
492
+ dbOnlyEvents: dbOnlyEvents.length,
493
+ missingDefaultEvents: missingDefaultEvents.length,
494
+ duplicatePromptBodyGroups: duplicatePromptBodies.length,
495
+ staleUrlPrompts: staleUrlPrompts.length,
496
+ contradictoryPrompts: contradictoryPrompts.length,
497
+ systemDefaultSkillDuplicates: systemDefaultSkillDuplicates.length,
498
+ },
499
+ findings: findings.promptsTemplates,
500
+ },
501
+ },
502
+ };
503
+
504
+ if (publishPage) result.page = await publishCatalogReportPage(buildReport(result), ctx);
505
+ return result;
506
+ }
@@ -22,10 +22,10 @@ import { getScript, upsertScriptByName } from "../scripts/db";
22
22
  import { extractArgsJsonSchema } from "../scripts/extract-schema";
23
23
  import { typecheckScript } from "../scripts/typecheck";
24
24
  import type { Seeder, SeedItem } from "../seed/types";
25
- import bootTriageSrc from "./catalog/boot-triage.ts" with { type: "text" };
26
- // @ts-expect-error Bun text imports return the raw source string for bundling standalone scripts.
27
- import catalogReportSrc from "./catalog/catalog-report.ts" with { type: "text" };
28
- import compoundInsightsSrc from "./catalog/compound-insights.ts" with { type: "text" };
25
+ import bootTriageSrc from "./catalog/boot-triage.inline.ts" with { type: "text" };
26
+ // @ts-expect-error Bun text imports synthesize a default string for this helper.
27
+ import catalogReportSrc from "./catalog/catalog-report.inline.ts" with { type: "text" };
28
+ import compoundInsightsSrc from "./catalog/compound-insights.inline.ts" with { type: "text" };
29
29
  import dateResolveSrc from "./catalog/date-resolve.ts" with { type: "text" };
30
30
  import fetchReadableSrc from "./catalog/fetch-readable.ts" with { type: "text" };
31
31
  import ghPrSnapshotSrc from "./catalog/gh-pr-snapshot.ts" with { type: "text" };
@@ -34,7 +34,7 @@ import jsonQuerySrc from "./catalog/json-query.ts" with { type: "text" };
34
34
  import linearIssueSrc from "./catalog/linear-issue.ts" with { type: "text" };
35
35
  import memoryDedupCheckSrc from "./catalog/memory-dedup-check.ts" with { type: "text" };
36
36
  import memoryEvalSrc from "./catalog/memory-eval.ts" with { type: "text" };
37
- import opsCatalogAuditSrc from "./catalog/ops-catalog-audit.ts" with { type: "text" };
37
+ import opsCatalogAuditSrc from "./catalog/ops-catalog-audit.inline.ts" with { type: "text" };
38
38
  import scheduleHealthSrc from "./catalog/schedule-health.ts" with { type: "text" };
39
39
  import slackThreadFlattenSrc from "./catalog/slack-thread-flatten.ts" with { type: "text" };
40
40
  import smartRecallSrc from "./catalog/smart-recall.ts" with { type: "text" };