@zibby/workflow-templates 0.4.2 → 0.7.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.
package/index.js CHANGED
@@ -53,8 +53,7 @@ export const TEMPLATES = {
53
53
  'Composition: character centered, slight forward lean, dynamic motion lines. Mood: cheerful, playful, fast.',
54
54
  'NO text, NO browser UI screenshots, NO outline wireframes.',
55
55
  ].join('\n'),
56
- category: 'Testing',
57
- tags: ['testing', 'playwright', 'e2e', 'browser'],
56
+ tags: ['Testing'],
58
57
  capabilities: [
59
58
  'Preflight LLM extracts assertions from a plain-English spec',
60
59
  'Live Playwright execution with screenshots + DOM at every step',
@@ -106,8 +105,7 @@ export const TEMPLATES = {
106
105
  'Composition: brackets centered, subtle drop shadow on the canvas. Mood: high-end, technical, confident — like the cover of a developer tool launch.',
107
106
  'NO text, NO outline wireframes, NO flat sticker style — this one is DEEP and 3D-rendered.',
108
107
  ].join('\n'),
109
- category: 'Engineering',
110
- tags: ['code-analysis', 'jira', 'github', 'test-generation'],
108
+ tags: ['Code Review', 'Testing'],
111
109
  capabilities: [
112
110
  'Clone repos + snapshot git baseline so changes are diff-able',
113
111
  'LLM gate: skip code-gen when ticket can\'t be implemented as-is',
@@ -150,8 +148,7 @@ export const TEMPLATES = {
150
148
  'Mood: friendly, approachable, slightly handmade. Like a children\'s book illustration applied to a developer tool.',
151
149
  'NO text, NO photo-realism, NO sleek 3D render — this one is hand-drawn and warm.',
152
150
  ].join('\n'),
153
- category: 'Testing',
154
- tags: ['testing', 'test-generation', 'pull-request', 'review'],
151
+ tags: ['Code Review', 'Testing'],
155
152
  capabilities: [
156
153
  'Skips ticket analysis — feed it the diff directly',
157
154
  'LLM explores the codebase to ground test steps in real components',
@@ -195,8 +192,7 @@ export const TEMPLATES = {
195
192
  'Mood: focused, energetic, signal-not-noise — the canonical Slack-flavored notification worker.',
196
193
  'NO text, NO letters, NO photo-realism, NO sleek 3D render, NO literal Slack wordmark — the colored pinwheel shape is allowed as the brand reference.',
197
194
  ].join('\n'),
198
- category: 'Notifications',
199
- tags: ['slack', 'notification', 'alert', 'child-workflow'],
195
+ tags: ['Notifications', 'child-workflow'],
200
196
  capabilities: [
201
197
  'Severity-coded Block Kit message (low/medium/high/critical)',
202
198
  'Code snippet + action button + caller mentions',
@@ -239,8 +235,7 @@ export const TEMPLATES = {
239
235
  'Mood: focused, professional, signal-not-noise.',
240
236
  'NO Lark / Feishu logo trademark, NO text, NO photo-realism.',
241
237
  ].join('\n'),
242
- category: 'Notifications',
243
- tags: ['lark', 'feishu', 'notification', 'alert', 'child-workflow'],
238
+ tags: ['Notifications', 'child-workflow'],
244
239
  capabilities: [
245
240
  'Severity-coded Lark Interactive Card',
246
241
  'Auto-detects receive_id_type from id prefix (chat_id / open_id / email)',
@@ -279,16 +274,14 @@ export const TEMPLATES = {
279
274
  slug: 'notify-notion',
280
275
  tagline: 'Reusable Notion archiver — durable record for any workflow.',
281
276
  iconPrompt: [
282
- 'Hand-painted gouache illustration with soft brushwork and gentle painterly texture, in the same family as the sentry-triage and generate-test-cases marketplace icons but with its own distinct character.',
283
- 'Subject: a friendly anthropomorphic notebook-document mascot a small rounded notebook character with two big smiling eyes and a rosy blush, its open pages showing three painted horizontal ink-lines and a tiny checkmark in the corner. A soft halo of two or three little pastel page-flutter sparkles dance around it, suggesting a freshly-written entry being archived.',
284
- 'Background: a pale neutral Notion-flavored off-white gradient warm cream at the top blending into a soft dove-grey at the base (#F7F3EC #E8E4DA), with a single faint paper-grain texture and a couple of small floating pastel ink-spot dots for friendliness.',
285
- 'Centered composition with the notebook character as the focal point in the lower-center, sparkles arcing across the upper third; plenty of breathing room so the silhouette reads at 64×64 in the marketplace grid.',
286
- 'Mood is calm, archival, gently studious the friendly notebook companion that keeps a tidy record, NOT corporate productivity or wall-of-text database.',
287
- 'Soft rounded square 1024×1024 canvas with a subtle paper-grain texture.',
288
- 'NO text, NO logo or trademarked marks, NO photo-realism, NO sleek 3D render, NO literal Notion trademark.',
277
+ 'A premium, hi-fi app icon for "Notify Notion" a workflow node that publishes reports to a Notion database. The real Notion brand mark will be composited on top in a post-process step; this prompt generates the BACKGROUND ONLY, with a clear empty area for the overlay.',
278
+ 'Visual style: 3D-rendered hero object floating in space, in the style of Apple Vision Pro icons, Linear\'s changelog hero illustrations, or a Stripe product render. Glossy, dimensional, with subtle reflections and a soft rim-light. Same family as the code-analysis marketplace icon.',
279
+ 'Subject: a single 3D-rendered page-document hero object made of glossy frosted glass / brushed silver metal, captured in head-on or near-head-on perspective (NOT three-quarter — keep the page face flat to the camera so the logo overlay sits cleanly). The page surface is COMPLETELY EMPTY — no lines, no text, no icons, no markings of any kind. The right edge curls slightly forward like a fresh page being filed, but the front face stays clean. A tiny cyan-teal glow accent sits in the upper-right corner of the page as a "freshly archived" signal.',
280
+ 'Background: a deep midnight-navy gradient (#0F172A at the top, #1E1B4B at the bottom), with a single soft cyan glow behind the page and a few faint star-like specks scattered across the canvas. Square format, 1024×1024.',
281
+ 'Composition: page centered, page face takes ~60-70% of the canvas and is empty/blank so a logo overlay can sit naturally on its surface. Subtle drop shadow. Mood: high-end, durable, archival — premium devtool aesthetic.',
282
+ 'CRITICAL: the page front face must be COMPLETELY CLEAN AND EMPTY. NO N, NO letters, NO horizontal text lines, NO checkmark, NO icons, NO embossing, NO etching, NO decoration on the page surface. Just blank glossy material. NO Notion logo, NO Notion wordmark, NO trademarked marks. NO text anywhere. NO outline wireframes, NO flat sticker style, NO mascot, NO smiling face, NO cartoon. The page surface MUST be empty so a real logo PNG can be composited on it.',
289
283
  ].join('\n'),
290
- category: 'Operations',
291
- tags: ['notion', 'docs', 'reporting', 'knowledge-base', 'archive'],
284
+ tags: ['Notifications', 'Docs', 'Reports'],
292
285
  capabilities: [
293
286
  'Create a new page in a Notion database (POST /v1/pages)',
294
287
  'Append blocks to an existing page (PATCH /v1/blocks/{pageId}/children)',
@@ -306,49 +299,45 @@ export const TEMPLATES = {
306
299
  },
307
300
  },
308
301
 
309
- // ── sentry-triage: parent workflow that uses notify-slack/-lark ──
302
+ // ── sentry-triage: hourly LLM triage Slack OR Lark ─────────────
310
303
  'sentry-triage': {
311
304
  name: 'sentry-triage',
312
305
  displayName: 'Sentry Triage Bot',
313
- description: 'Hourly Sentry triage pulls new issues, drops obvious noise with a regex pre-filter, classifies survivors with LLM (CRITICAL/HIGH/MEDIUM/LOW/NOISE), and fans out alerts to a notify-slack OR notify-lark child workflow.',
306
+ description: 'Hourly Sentry triage that classifies new issues with an LLM rubric (CRITICAL/HIGH/MEDIUM/LOW/NOISE) and posts above-threshold alerts to your Slack or Lark whichever you have connected.',
314
307
  path: join(__dirname, 'sentry-triage'),
315
308
  defaultSlug: 'sentry-triage',
316
- deps: { zod: '^3.23.0' },
309
+ deps: { zod: '^3.23.0', '@zibby/skills': '^0.1.25' },
317
310
  features: [
318
- '4-node graph: fetchfilter_noise → classify → dispatch_alerts',
319
- 'Regex noise filter before LLM cuts ~80% of classification cost',
320
- 'LLM severity classifier with explicit rubric (rules 1-5)',
321
- 'Sub-graph fan-out to notify-slack OR notify-lark (choose at deploy)',
322
- 'Per-issue failure isolation one Slack hiccup doesn\'t stall the run',
323
- 'Configurable severityThreshold (don\'t notify on LOW noise)',
311
+ '3-node LLM graph: fetch_issues → classify → dispatch_alerts',
312
+ 'Severity rubric with auditable reasoning per issue',
313
+ 'Posts to Slack OR Lark (whichever the project has connected — chat_notify OR-group)',
314
+ 'LLM dispatcher batches related issues into one message, dedupes near-duplicates',
315
+ 'CRITICAL alerts get optional @-mentions; lower severities don\'t',
324
316
  'Cron-friendly: hourly schedule, default sinceMinutes=60',
325
317
  ],
326
318
  marketplace: {
327
319
  slug: 'sentry-triage',
328
- tagline: 'Filter noise, classify severity, ping the right channel every hour.',
320
+ tagline: 'Triage Sentry, ping your teamhourly.',
329
321
  iconPrompt: [
330
- 'Hand-painted storybook illustration in a warm gouache style with soft brushwork and gentle painterly texture, featuring the friendly round lighthouse mascot character with two big smiling eyes and a rosy blush on its white-and-coral-striped tower body, perched on a tiny mint-green island and clutching a small glowing purple SHIELD BADGE in front of its body — the badge is a rounded geometric emblem in Sentry\'s signature deep violet (#362D59 / #7553FF) with a stylized white "S"-mark inside it formed from overlapping rounded parallelogram shapes, painted with the same soft gouache brushstrokes as the rest of the scene so it feels integrated rather than corporate.',
322
+ 'Hand-painted storybook illustration in a warm gouache style with soft brushwork and gentle painterly texture, featuring the friendly round lighthouse mascot character with two big smiling eyes and a rosy blush on its white-and-coral-striped tower body, perched on a tiny mint-green island and clutching a small glowing purple SHIELD BADGE in front of its body — the badge is a rounded geometric emblem in Sentry\'s signature deep violet (#362D59 / #7553FF) with a stylized white "S"-mark inside it formed from overlapping rounded parallelogram shapes.',
331
323
  'The lighthouse lantern emits a soft golden beam that catches one glowing amber alert orb while three faded grey noise specks drift harmlessly past, reinforcing the "filter the signal, calm the noise" idea.',
332
324
  'Background is a soft sunrise gradient of pale peach at the top blending through buttercream into a gentle wash of dusty lavender at the base, tying the warm scene to the violet of the badge; a few small fluffy pastel clouds float in for friendliness.',
333
- 'Centered composition with the purple shield badge as the immediate focal point in the lower-center, the lighthouse rising behind and slightly above it, beam angled diagonally; plenty of breathing room so the silhouette reads at 64×64 with the violet badge clearly visible at a glance.',
334
- 'Mood is warm, reassuring, optimistic — the friendly Sentry-flavored night-watch character, NOT tactical or corporate or alarming.',
325
+ 'Centered composition with the purple shield badge as the immediate focal point in the lower-center, the lighthouse rising behind and slightly above it, beam angled diagonally; plenty of breathing room so the silhouette reads at 64×64.',
326
+ 'Mood is warm, reassuring, optimistic — friendly night-watch character, NOT tactical or corporate or alarming.',
335
327
  'Soft rounded square 1024×1024 canvas with a subtle paper-grain texture.',
336
- 'NO text, NO letters, NO photo-realism, NO sleek 3D render, NO magnifying glass, NO speech bubbles, NO dark navy or near-black backgrounds, NO bug or insect imagery, NO literal Sentry wordmark.',
328
+ 'NO text, NO letters, NO photo-realism, NO sleek 3D render, NO magnifying glass, NO speech bubbles, NO dark navy or near-black backgrounds, NO bug or insect imagery, NO literal Sentry / Slack / Lark wordmark.',
337
329
  ].join('\n'),
338
- category: 'Operations',
339
- tags: ['sentry', 'observability', 'on-call', 'triage', 'alerting'],
330
+ tags: ['On-call', 'Bug Triage', 'Notifications'],
340
331
  capabilities: [
341
332
  'Hourly scheduled triage of new Sentry issues',
342
- 'Deterministic regex filter drops Script error / ResizeObserver / extension noise',
343
333
  'LLM severity classifier with auditable rubric',
344
- 'Dispatches to notify-slack or notify-lark (sub-graph, ~5ms in-process)',
345
- 'CRITICAL alerts get caller-supplied @-mentions; lower severities don\'t',
334
+ 'Posts to Slack or Lark whichever your project has connected',
335
+ 'Batches related issues; CRITICAL-only @-mentions for on-call',
346
336
  'Configurable severity threshold per deploy',
347
337
  ],
348
338
  conversationStarters: [
349
339
  'Triage all new Sentry issues from the last hour',
350
- 'Notify #sentry-alerts when severity is HIGH or above',
351
- 'Run hourly and post a summary to our team Slack',
340
+ 'Notify the on-call channel when severity is HIGH or above',
352
341
  'Page on-call when a CRITICAL error appears in checkout',
353
342
  ],
354
343
  },
@@ -375,16 +364,14 @@ export const TEMPLATES = {
375
364
  slug: 'ai-spend-weekly-digest',
376
365
  tagline: 'Track and explain your OpenAI / Anthropic / Cursor spending — every Monday morning, in Lark or Slack.',
377
366
  iconPrompt: [
378
- 'Hand-painted gouache illustration with soft brushwork and gentle painterly texture, featuring a friendly chubby pastel-pink piggy-bank character with two big smiling eyes and rosy blush, its body marked with a soft glowing dollar-sign sigil; the piggy is gently cradling a small stack of three coloured coins floating just above its back — a sky-blue coin for OpenAI-ish, a warm violet coin for Anthropic-ish, and a soft mint-green coin for Cursor-ish — each coin painted in the same loose gouache style with no logos or text on them, just abstract round chips.',
379
- 'A thin painterly trend-line ribbon arcs gently upward behind the piggy from lower-left to upper-right, suggesting "money over time", rendered as a soft watercolor ribbon in dusty rose with a tiny gentle peak near the top right.',
380
- 'Background is a calm sunrise gradient of pale buttercream at the top blending through soft peach into a gentle wash of pale mint at the base, with a few small pastel clouds for friendliness.',
381
- 'Centered composition with the piggy as the immediate focal point in the lower-center, the floating coins arcing across the upper third, the trend ribbon as background scaffolding; plenty of breathing room so the silhouette reads at 64×64.',
382
- 'Mood is warm, optimistic, gently informativefeels like a thoughtful finance friend, NOT corporate spreadsheet, NOT alarmist red.',
383
- 'Soft rounded square 1024×1024 canvas with a subtle paper-grain texture.',
384
- 'NO text, NO letters, NO numbers, NO photo-realism, NO sleek 3D render, NO chart axes or grid lines, NO dark navy or near-black backgrounds, NO literal OpenAI / Anthropic / Cursor logos or wordmarks, NO bar charts.',
367
+ 'A premium, hi-fi app icon for "AI Spend Weekly Digest" — a workflow that reports cross-vendor LLM spend to engineering leaders.',
368
+ 'Visual style: 3D-rendered hero object floating in space, in the style of Apple Vision Pro icons, Linear\'s changelog hero illustrations, or a Stripe product render. Glossy, dimensional, with subtle reflections and a soft rim-light.',
369
+ 'Subject: a 3D-rendered stack of three glossy disc-shaped layered chips — like a tiny ascending bar-chart-of-coins slightly rotated in three-quarter perspective. Each disc is a different premium accent color: top disc warm gold, middle disc cool platinum-silver, bottom disc deep iridescent purple. A single thin trending-up glowing line traces from lower-left to upper-right behind the stack, rendered as a soft neon ribbon (#7553FF violet glow), suggesting cost-over-time at a glance without literal axes.',
370
+ 'Background: a deep midnight-navy gradient (#0F172A at the top, #1E1B4B at the bottom), with a single soft violet glow behind the stack and a few faint star-like specks scattered across the canvas. Square format, 1024×1024.',
371
+ 'Composition: stack centered, subtle drop shadow on the canvas. Mood: high-end, executive, confident — like the cover image of a finance-ops product launch.',
372
+ 'NO text, NO numbers, NO axes or grid lines, NO outline wireframes, NO flat sticker style, NO mascot, NO piggy bank, NO cartoon faces, NO trademarked OpenAI / Anthropic / Cursor logos — this one is DEEP and 3D-rendered.',
385
373
  ].join('\n'),
386
- category: 'Operations',
387
- tags: ['cost', 'finance', 'reporting', 'openai', 'anthropic', 'cursor', 'digest', 'weekly'],
374
+ tags: ['AI Spend', 'Cost Tracking', 'Reports'],
388
375
  capabilities: [
389
376
  'Pulls org-wide cost+usage from OpenAI, Anthropic, and Cursor admin APIs in parallel',
390
377
  'Joins customer attribution from provider-native project / workspace / member metadata',
@@ -0,0 +1,4 @@
1
+ <svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z" fill="#fff"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z" fill="#000"/>
4
+ </svg>
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/workflow-templates",
3
- "version": "0.4.2",
3
+ "version": "0.7.1",
4
4
  "description": "Built-in workflow templates for Zibby — browser-test-automation, code-analysis, generate-test-cases, notify-slack, notify-lark, notify-notion, sentry-triage.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -72,6 +72,7 @@
72
72
  "dependencies": {
73
73
  "@anthropic-ai/sdk": "^0.88.0",
74
74
  "@zibby/agent-workflow": "^0.4.2",
75
+ "@zibby/skills": "^0.1.27",
75
76
  "axios": "^1.15.0",
76
77
  "handlebars": "^4.7.9",
77
78
  "zod": "^3.23.0 || ^4.0.0"
@@ -1,51 +1,41 @@
1
1
  /**
2
- * sentry-triage — parent workflow.
2
+ * sentry-triage — parent workflow. Hourly Sentry issue triage.
3
3
  *
4
- * Pipeline:
4
+ * Pipeline (3 LLM nodes, end-to-end agent-driven):
5
5
  *
6
- * fetch_issues (LLM + SKILLS.SENTRY)
6
+ * fetch_issues (LLM + SKILLS.SENTRY) → list recent unresolved issues
7
7
  * ↓
8
- * filter_noise (pure JS regex pre-filter — kills ~80% of LLM cost)
8
+ * classify (LLM, no tools) → label NOISE/LOW/MEDIUM/HIGH/CRITICAL
9
9
  * ↓
10
- * classify (LLM assigns CRITICAL/HIGH/MEDIUM/LOW/NOISE per issue)
11
- *
12
- * dispatch_alerts (custom execute — sub-graphs to notify-slack OR notify-lark
13
- * per issue at or above severityThreshold)
10
+ * dispatch_alerts (LLM + SKILLS.CHAT_NOTIFY) → batch + post to Slack OR Lark for
11
+ * issues ≥ SEVERITY_THRESHOLD
14
12
  *
15
- * Sub-graph dispatch: each "real" alert fans out to ONE notify-* child
16
- * workflow (configurable per deploy via state.notifyWorker). Failures
17
- * on individual alerts don't kill the triage run — failed entries are
18
- * reported in dispatch_alerts.summary.failed and surfaced in
19
- * onComplete logging.
13
+ * Why all three nodes are LLM (not deterministic for-loops):
14
+ * - At hourly cadence with ≤20 issues/run, LLM cost is $1.50–$32/mo
15
+ * depending on model. Trivial relative to Sentry / Slack subscriptions.
16
+ * - LLM dispatch can BATCH related issues (5 errors in /checkout/ →
17
+ * 1 consolidated message) and DE-DUP near-duplicates. A
18
+ * deterministic for-loop can't.
19
+ * - outputSchema enforcement guarantees every above-threshold issue
20
+ * either gets a "sent" record or an explicit "failed/skipped" —
21
+ * no silent drops.
20
22
  *
21
- * In-process sub-graph execution (when both parent + child are bundled
22
- * in the same Fargate task) means each fan-out adds ~5ms overhead vs
23
- * an HTTP /trigger round-trip's 80s cold-start. For 20 issues that's
24
- * 100ms vs 1600s — the architecture is what makes this template
25
- * cheap enough to run hourly.
23
+ * Customize prompts: each node's prompt lives in its own module under
24
+ * nodes/. Override per-deploy by editing the file or by passing a
25
+ * custom prompt string via inputSchema (planned).
26
26
  */
27
27
 
28
- import { readFileSync, existsSync } from 'fs';
29
- import { join, dirname } from 'path';
30
- import { fileURLToPath } from 'url';
31
28
  import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
32
29
 
33
30
  import { fetchIssuesNode } from './nodes/fetch-issues-node.js';
34
- import { filterNoiseNode } from './nodes/filter-noise-node.js';
35
31
  import { classifyNode } from './nodes/classify-node.js';
36
- import { dispatchAlertsNode } from './nodes/dispatch-alerts-node.js';
32
+ import { dispatchNode } from './nodes/dispatch-node.js';
37
33
 
38
34
  import {
39
35
  sentryTriageInputSchema,
40
36
  sentryTriageContextSchema,
41
37
  } from './state.js';
42
38
 
43
- const __dirname = dirname(fileURLToPath(import.meta.url));
44
- function loadPrompt(filename) {
45
- const path = join(__dirname, 'prompts', filename);
46
- return existsSync(path) ? readFileSync(path, 'utf-8') : '';
47
- }
48
-
49
39
  export class SentryTriageAgent extends WorkflowAgent {
50
40
  buildGraph() {
51
41
  const graph = new WorkflowGraph();
@@ -53,14 +43,22 @@ export class SentryTriageAgent extends WorkflowAgent {
53
43
  .setInputSchema(sentryTriageInputSchema)
54
44
  .setContextSchema(sentryTriageContextSchema);
55
45
 
56
- graph.addNode('fetch_issues', fetchIssuesNode, { prompt: loadPrompt('fetch-issues.md') });
57
- graph.addNode('filter_noise', filterNoiseNode);
58
- graph.addNode('classify', classifyNode, { prompt: loadPrompt('classify.md') });
59
- graph.addNode('dispatch_alerts', dispatchAlertsNode);
46
+ graph.addNode('fetch_issues', fetchIssuesNode);
47
+ graph.addNode('classify', classifyNode);
48
+ graph.addNode('dispatch_alerts', dispatchNode);
60
49
 
61
50
  graph.setEntryPoint('fetch_issues');
62
- graph.addEdge('fetch_issues', 'filter_noise');
63
- graph.addEdge('filter_noise', 'classify');
51
+ // Short-circuit when Sentry returned nothing for this window. The
52
+ // empty-list case is the common idle path (steady-state apps don't
53
+ // throw new errors every hour), and running classify + dispatch on
54
+ // an empty input wastes two Claude calls per run — at hourly cadence
55
+ // across many tenants that adds up. Cleaner to route directly to END
56
+ // at the graph level than to short-circuit inside each downstream
57
+ // node's prompt (which still spends a model round-trip).
58
+ graph.addConditionalEdges('fetch_issues', (state) => {
59
+ const issues = state?.fetch_issues?.issues || [];
60
+ return issues.length === 0 ? 'END' : 'classify';
61
+ });
64
62
  graph.addEdge('classify', 'dispatch_alerts');
65
63
  graph.addEdge('dispatch_alerts', 'END');
66
64
 
@@ -69,10 +67,11 @@ export class SentryTriageAgent extends WorkflowAgent {
69
67
 
70
68
  async onComplete(result) {
71
69
  const s = result?.state?.dispatch_alerts?.summary || {};
72
- const dropped = result?.state?.filter_noise?.dropped?.length || 0;
70
+ const classifications = result?.state?.classify?.classifications || [];
71
+ const noise = classifications.filter((c) => c.severity === 'NOISE').length;
73
72
  const fetched = result?.state?.fetch_issues?.issues?.length || 0;
74
73
  console.log(
75
- `[sentry-triage] complete — fetched=${fetched}, noise=${dropped}, ` +
74
+ `[sentry-triage] complete — fetched=${fetched}, noise=${noise}, ` +
76
75
  `sent=${s.sent || 0}, skipped=${s.skipped || 0}, failed=${s.failed || 0}`,
77
76
  );
78
77
  }
Binary file
@@ -1,14 +1,13 @@
1
1
  /**
2
2
  * classify node — LLM-driven severity classification.
3
3
  *
4
- * No tools — pure prompt + structured output. The prompt
5
- * (prompts/classify.md) carries the rubric (CRITICAL/HIGH/MEDIUM/LOW/
6
- * NOISE) and the LLM emits one classification record per kept issue.
4
+ * No tools — the LLM sees the rubric AND the concrete issues array
5
+ * (inlined as JSON at render time) and emits one classification record
6
+ * per issue. NOISE detection is part of the rubric itself; no separate
7
+ * pre-filter step.
7
8
  *
8
- * Temperature should be 0 (set by the runner via `model: 'auto'`'s
9
- * defaults for classification-style nodes). Schema enforcement
10
- * guarantees the emitted shape; bad models get a retry with the
11
- * outputSchema in the prompt.
9
+ * Severity threshold (skip-floor) lives on dispatch, NOT here this
10
+ * node always classifies every issue. dispatch decides whether to send.
12
11
  */
13
12
 
14
13
  import { z } from '@zibby/core';
@@ -27,12 +26,98 @@ const ClassifyOutputSchema = z.object({
27
26
  classifications: z.array(ClassificationShape),
28
27
  });
29
28
 
29
+ const RUBRIC = `You are the classify node of a Sentry triage workflow. Classify each Sentry issue into a severity bucket and explain WHY.
30
+
31
+ The list of issues is appended below as a JSON array. Treat it as authoritative — do NOT call any tool, you have everything you need.
32
+
33
+ # Severity rubric (apply IN ORDER, stop at first match)
34
+
35
+ 1. **NOISE** — these never warrant a human ping. Match if ANY:
36
+ - Title is "Script error." (cross-origin opaque error, no stack, useless)
37
+ - Title contains "Non-Error promise rejection captured"
38
+ - Title contains "ResizeObserver loop limit exceeded" or "ResizeObserver loop completed"
39
+ - culprit or metadata.filename URL starts with chrome-extension://, safari-extension://, moz-extension://, webkit-masked-url:// (user's extension crashed, not your code)
40
+ - Title or culprit mentions analytics SDKs: gtag, fbq, _paq, dataLayer, googletagmanager, piwik
41
+ - Title is "AbortError", contains "cancelled", or "Load failed" AND userCount < 3 (user navigated away)
42
+ - Title says "Test ", "Demo ", "[STAGING]" (wrong environment leakage)
43
+ - Stack trace has zero inApp:true frames (3rd-party only — not your code)
44
+ - User-agent in tags indicates a bot (Googlebot, AhrefsBot, etc.)
45
+
46
+ 2. **CRITICAL** if ANY of:
47
+ - userCount >= 20 (≥ 20 users affected — real prod impact)
48
+ - culprit or metadata.filename matches /payment|billing|checkout|auth|login|signup|session/i (security/revenue path)
49
+ - level === "fatal" and count >= 10
50
+ - count >= 100 AND firstSeen-to-lastSeen window is < 30 min (active spike)
51
+
52
+ 3. **HIGH** if ANY of:
53
+ - userCount >= 5 AND count >= 50
54
+ - level === "fatal" (any count)
55
+ - level === "error" AND userCount >= 3 AND count >= 20
56
+ - Errors in non-critical-but-important paths: settings, profile, search, dashboard, admin
57
+
58
+ 4. **MEDIUM** if ANY of:
59
+ - count >= 20 AND userCount >= 2
60
+ - count >= 50 regardless of userCount
61
+ - level === "error" AND count >= 10
62
+
63
+ 5. **LOW** — anything else (count < 20 AND userCount < 5, or level === "warning" | "info")
64
+
65
+ # Recommended action per severity
66
+
67
+ - CRITICAL → page_oncall (always notify, always mention rotation)
68
+ - HIGH → notify_channel (notify, no @ unless deploy author known)
69
+ - MEDIUM → notify_channel
70
+ - LOW → digest_only (rolled into a daily summary, not real-time)
71
+ - NOISE → ignore
72
+
73
+ # Output shape
74
+
75
+ For EACH issue in the JSON array below, emit ONE record:
76
+
77
+ \`\`\`json
78
+ {
79
+ "classifications": [
80
+ {
81
+ "issueId": "1234567890",
82
+ "severity": "CRITICAL",
83
+ "confidence": 0.95,
84
+ "reasoning": "12 users affected, culprit handleCheckout (payment path). Likely regression after recent deploy.",
85
+ "suggestedAction": "page_oncall",
86
+ "ruleMatched": "rule 2 (culprit matches /checkout/)"
87
+ }
88
+ ]
89
+ }
90
+ \`\`\`
91
+
92
+ # Rules
93
+
94
+ - confidence reflects how cleanly the issue matched. CRITICAL in /payment/ with userCount=50 → 0.95. Borderline → 0.6.
95
+ - reasoning is ONE sentence written for an on-call engineer. Lead with the impact metric.
96
+ - ruleMatched is which numbered rule fired. Helps operators tune the rubric over time.
97
+ - Be consistent: same issue twice should always get the same severity.
98
+ - Temperature 0. Classification, not creative writing.
99
+
100
+ # Do NOT
101
+
102
+ - Classify more issues than appear in the array below.
103
+ - Skip issues — every issue in the array must appear in the output (NOISE included).
104
+ - Use any severity outside NOISE|LOW|MEDIUM|HIGH|CRITICAL.
105
+ - Call any tools.`;
106
+
107
+ const CLASSIFY_PROMPT = (state = {}) => {
108
+ const issues = state?.fetch_issues?.issues || [];
109
+ return `${RUBRIC}
110
+
111
+ ## Issues to classify
112
+
113
+ \`\`\`json
114
+ ${JSON.stringify(issues, null, 2)}
115
+ \`\`\`
116
+ `;
117
+ };
118
+
30
119
  export const classifyNode = {
31
120
  name: 'classify',
32
- // NO skills — this is a pure reasoning step; the LLM has all data
33
- // it needs in state.filter_noise.kept. Adding skills would let the
34
- // LLM call Sentry tools for "more context", which we don't want
35
- // (rubric is supposed to be deterministic).
36
121
  outputSchema: ClassifyOutputSchema,
37
- timeout: 90 * 1000,
122
+ prompt: CLASSIFY_PROMPT,
38
123
  };