@sweny-ai/core 0.1.0 → 0.1.2
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/dist/claude.d.ts +1 -0
- package/dist/claude.js +24 -2
- package/dist/cli/config.d.ts +1 -0
- package/dist/cli/config.js +4 -1
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +81 -51
- package/dist/executor.js +3 -0
- package/dist/skills/github.js +17 -0
- package/dist/skills/linear.js +13 -0
- package/dist/types.d.ts +6 -0
- package/dist/workflows/triage.js +12 -9
- package/package.json +1 -1
package/dist/claude.d.ts
CHANGED
package/dist/claude.js
CHANGED
|
@@ -28,7 +28,7 @@ export class ClaudeClient {
|
|
|
28
28
|
this.mcpServers = opts.mcpServers ?? {};
|
|
29
29
|
}
|
|
30
30
|
async run(opts) {
|
|
31
|
-
const { instruction, context, tools, outputSchema } = opts;
|
|
31
|
+
const { instruction, context, tools, outputSchema, onProgress } = opts;
|
|
32
32
|
const toolCalls = [];
|
|
33
33
|
// Convert core tools to SDK MCP tools
|
|
34
34
|
const sdkTools = tools.map((t) => coreToolToSdkTool(t, this.defaultContext, toolCalls));
|
|
@@ -67,7 +67,22 @@ export class ClaudeClient {
|
|
|
67
67
|
},
|
|
68
68
|
});
|
|
69
69
|
for await (const message of stream) {
|
|
70
|
-
if (message.type === "
|
|
70
|
+
if (message.type === "tool_progress") {
|
|
71
|
+
const tp = message;
|
|
72
|
+
if (tp.tool_name && typeof tp.elapsed_time_seconds === "number") {
|
|
73
|
+
const name = stripMcpPrefix(tp.tool_name);
|
|
74
|
+
const secs = Math.round(tp.elapsed_time_seconds);
|
|
75
|
+
onProgress?.(`${name} (${secs}s)`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (message.type === "tool_use_summary") {
|
|
79
|
+
const ts = message;
|
|
80
|
+
if (ts.summary) {
|
|
81
|
+
const clean = ts.summary.replace(/\n/g, " ").trim();
|
|
82
|
+
onProgress?.(clean.length > 80 ? clean.slice(0, 79) + "\u2026" : clean);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (message.type === "result") {
|
|
71
86
|
const resultMsg = message;
|
|
72
87
|
if (resultMsg.subtype === "success" && "result" in resultMsg) {
|
|
73
88
|
response = resultMsg.result;
|
|
@@ -233,6 +248,13 @@ function jsonPropertyToZod(prop) {
|
|
|
233
248
|
}
|
|
234
249
|
}
|
|
235
250
|
}
|
|
251
|
+
/** Strip MCP server prefix: "mcp__server__tool" → "tool" */
|
|
252
|
+
function stripMcpPrefix(name) {
|
|
253
|
+
const parts = name.split("__");
|
|
254
|
+
if (parts.length >= 3 && parts[0] === "mcp")
|
|
255
|
+
return parts.slice(2).join("__");
|
|
256
|
+
return name;
|
|
257
|
+
}
|
|
236
258
|
// ─── JSON extraction ────────────────────────────────────────────
|
|
237
259
|
/**
|
|
238
260
|
* Extract a JSON object from Claude's text response.
|
package/dist/cli/config.d.ts
CHANGED
package/dist/cli/config.js
CHANGED
|
@@ -55,6 +55,7 @@ export function registerTriageCommand(program) {
|
|
|
55
55
|
.option("--betterstack-table-name <name>", 'Better Stack ClickHouse table name, e.g. "t273774.my_source"')
|
|
56
56
|
.option("--gitlab-base-url <url>", "GitLab base URL (default: https://gitlab.com)")
|
|
57
57
|
.option("--json", "Output results as JSON", false)
|
|
58
|
+
.option("--stream", "Stream NDJSON events to stdout (for Studio / automation)", false)
|
|
58
59
|
.option("--bell", "Ring terminal bell on completion", false)
|
|
59
60
|
.option("--cache-dir <path>", "Step cache directory (default: .sweny/cache)")
|
|
60
61
|
.option("--cache-ttl <seconds>", "Cache TTL in seconds, 0 = infinite (default: 86400)")
|
|
@@ -126,6 +127,7 @@ export function parseCliInputs(options, fileConfig = {}) {
|
|
|
126
127
|
repositoryOwner: env.GITHUB_REPOSITORY_OWNER || "",
|
|
127
128
|
// Per-invocation flags: CLI only
|
|
128
129
|
json: Boolean(options.json),
|
|
130
|
+
stream: Boolean(options.stream),
|
|
129
131
|
bell: Boolean(options.bell),
|
|
130
132
|
cacheDir: options.cacheDir || env.SWENY_CACHE_DIR || f("cache-dir") || ".sweny/cache",
|
|
131
133
|
cacheTtl: parseInt(String(options.cacheTtl || f("cache-ttl") || "86400"), 10),
|
|
@@ -528,7 +530,8 @@ export function registerImplementCommand(program) {
|
|
|
528
530
|
.option("--output-dir <path>", "Output directory for file providers (default: .sweny/output)")
|
|
529
531
|
.option("--workspace-tools <tools>", "Comma-separated workspace tool integrations to enable (slack, notion, pagerduty, monday)")
|
|
530
532
|
.option("--review-mode <mode>", "PR merge behavior: auto (GitHub auto-merge when CI passes) | review (human approval, default)", "review")
|
|
531
|
-
.option("--additional-instructions <text>", "Extra instructions for the coding agent")
|
|
533
|
+
.option("--additional-instructions <text>", "Extra instructions for the coding agent")
|
|
534
|
+
.option("--stream", "Stream NDJSON events to stdout (for Studio / automation)", false);
|
|
532
535
|
}
|
|
533
536
|
/**
|
|
534
537
|
* Parse a JSON string into MCP server configs.
|
package/dist/cli/main.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { Workflow } from "../types.js";
|
|
|
3
3
|
export declare function loadWorkflowFile(filePath: string): Workflow;
|
|
4
4
|
export declare function workflowRunAction(file: string, options: Record<string, unknown> & {
|
|
5
5
|
json?: boolean;
|
|
6
|
+
stream?: boolean;
|
|
6
7
|
}): Promise<void>;
|
|
7
8
|
export declare function workflowExportAction(name: string): void;
|
|
8
9
|
export declare function workflowValidateAction(file: string, options: {
|
package/dist/cli/main.js
CHANGED
|
@@ -22,6 +22,28 @@ import { registerTriageCommand, registerImplementCommand, parseCliInputs, valida
|
|
|
22
22
|
import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
|
|
23
23
|
import { checkProviderConnectivity } from "./check.js";
|
|
24
24
|
import { registerSetupCommand } from "./setup.js";
|
|
25
|
+
// ── Stream observer (NDJSON) ────────────────────────────────────────
|
|
26
|
+
/**
|
|
27
|
+
* Create an observer that writes NDJSON ExecutionEvents to stdout.
|
|
28
|
+
* Studio and other consumers parse these line-by-line.
|
|
29
|
+
*/
|
|
30
|
+
function createStreamObserver() {
|
|
31
|
+
return (event) => {
|
|
32
|
+
process.stdout.write(JSON.stringify(event) + "\n");
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** Compose multiple observers into one. */
|
|
36
|
+
function composeObservers(...observers) {
|
|
37
|
+
const valid = observers.filter((o) => o != null);
|
|
38
|
+
if (valid.length === 0)
|
|
39
|
+
return undefined;
|
|
40
|
+
if (valid.length === 1)
|
|
41
|
+
return valid[0];
|
|
42
|
+
return (event) => {
|
|
43
|
+
for (const o of valid)
|
|
44
|
+
o(event);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
25
47
|
// Auto-load .env before Commander parses (so env vars are available for defaults)
|
|
26
48
|
loadDotenv();
|
|
27
49
|
const program = new Command()
|
|
@@ -137,16 +159,17 @@ triageCmd.action(async (options) => {
|
|
|
137
159
|
cwd: process.cwd(),
|
|
138
160
|
logger: consoleLogger,
|
|
139
161
|
});
|
|
140
|
-
// ──
|
|
162
|
+
// ── Progress display state ─────────────────────────────────
|
|
141
163
|
const FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
142
164
|
const isTTY = !config.json && (process.stderr.isTTY ?? false);
|
|
165
|
+
const MAX_ACTIVITY = 3;
|
|
143
166
|
let spinnerInterval;
|
|
144
167
|
let spinnerActive = false;
|
|
145
168
|
let frameIdx = 0;
|
|
146
169
|
let stepStart = 0;
|
|
147
170
|
let stepLabel = "";
|
|
148
|
-
let
|
|
149
|
-
let
|
|
171
|
+
let recentActivity = [];
|
|
172
|
+
let renderedLines = 0; // how many lines the progress block currently occupies
|
|
150
173
|
let stepIndex = 0;
|
|
151
174
|
const totalNodes = Object.keys(triageWorkflow.nodes).length;
|
|
152
175
|
function formatElapsed(ms) {
|
|
@@ -156,33 +179,38 @@ triageCmd.action(async (options) => {
|
|
|
156
179
|
const m = Math.floor(s / 60);
|
|
157
180
|
return `${m}m ${s % 60}s`;
|
|
158
181
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
182
|
+
/** Render the multi-line progress block (spinner + activity lines). */
|
|
183
|
+
function renderProgressBlock() {
|
|
184
|
+
const cols = process.stderr.columns || 80;
|
|
185
|
+
const frame = chalk.cyan(FRAMES[frameIdx++ % FRAMES.length]);
|
|
186
|
+
const counter = c.subtle(`[${stepIndex}/${totalNodes}]`);
|
|
187
|
+
const elapsed = c.subtle(formatElapsed(Date.now() - stepStart));
|
|
188
|
+
const headerLine = ` ${frame} ${counter} ${stepLabel} ${elapsed}`;
|
|
189
|
+
const lines = [headerLine];
|
|
190
|
+
for (const msg of recentActivity) {
|
|
191
|
+
// Truncate to terminal width
|
|
192
|
+
const line = ` ${c.subtle("\u21B3")} ${c.subtle(msg)}`;
|
|
193
|
+
const vis = line.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
194
|
+
lines.push(vis > cols ? line.slice(0, cols - 1) : line);
|
|
195
|
+
}
|
|
196
|
+
// Move cursor up to clear previous render, then clear to end of screen
|
|
197
|
+
if (renderedLines > 0) {
|
|
198
|
+
process.stderr.write(`\x1B[${renderedLines}A\x1B[J`);
|
|
162
199
|
}
|
|
200
|
+
process.stderr.write(lines.join("\n") + "\n");
|
|
201
|
+
renderedLines = lines.length;
|
|
163
202
|
}
|
|
164
203
|
function startSpinner(label) {
|
|
165
204
|
stepStart = Date.now();
|
|
166
205
|
stepLabel = label;
|
|
167
|
-
|
|
206
|
+
recentActivity = [];
|
|
168
207
|
frameIdx = 0;
|
|
169
208
|
spinnerActive = true;
|
|
209
|
+
renderedLines = 0;
|
|
170
210
|
if (isTTY) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
const counter = c.subtle(`[${stepIndex}/${totalNodes}]`);
|
|
175
|
-
const elapsed = c.subtle(formatElapsed(Date.now() - stepStart));
|
|
176
|
-
const status = spinnerStatus ? ` ${c.subtle("\u2014")} ${c.subtle(spinnerStatus)}` : "";
|
|
177
|
-
// Truncate to terminal width to prevent line wrapping
|
|
178
|
-
let line = ` ${frame} ${counter} ${stepLabel}${status} ${elapsed}`;
|
|
179
|
-
const visibleLen = line.replace(/\x1B\[[0-9;]*m/g, "").length;
|
|
180
|
-
if (visibleLen > cols) {
|
|
181
|
-
// Re-render without status if too wide
|
|
182
|
-
line = ` ${frame} ${counter} ${stepLabel} ${elapsed}`;
|
|
183
|
-
}
|
|
184
|
-
process.stderr.write(`\r\x1b[K${line}`);
|
|
185
|
-
}, 80);
|
|
211
|
+
process.stderr.write("\x1B[?25l"); // hide cursor
|
|
212
|
+
renderProgressBlock();
|
|
213
|
+
spinnerInterval = setInterval(() => renderProgressBlock(), 100);
|
|
186
214
|
}
|
|
187
215
|
else if (!config.json) {
|
|
188
216
|
spinnerInterval = setInterval(() => {
|
|
@@ -196,59 +224,59 @@ triageCmd.action(async (options) => {
|
|
|
196
224
|
clearInterval(spinnerInterval);
|
|
197
225
|
spinnerInterval = undefined;
|
|
198
226
|
}
|
|
199
|
-
if (isTTY) {
|
|
200
|
-
process.stderr.write(
|
|
227
|
+
if (isTTY && renderedLines > 0) {
|
|
228
|
+
process.stderr.write(`\x1B[${renderedLines}A\x1B[J`);
|
|
229
|
+
process.stderr.write("\x1B[?25h"); // show cursor
|
|
230
|
+
renderedLines = 0;
|
|
201
231
|
}
|
|
202
232
|
spinnerActive = false;
|
|
203
233
|
}
|
|
204
234
|
// ── Build observer for DAG events ──────────────────────────
|
|
205
235
|
const runStart = Date.now();
|
|
206
|
-
const
|
|
236
|
+
const progressObserver = config.json
|
|
207
237
|
? undefined
|
|
208
238
|
: (event) => {
|
|
209
239
|
switch (event.type) {
|
|
210
240
|
case "workflow:start":
|
|
211
|
-
// Already printed the banner
|
|
212
241
|
break;
|
|
213
242
|
case "node:enter":
|
|
214
243
|
stepIndex++;
|
|
215
|
-
|
|
216
|
-
|
|
244
|
+
startSpinner(event.node);
|
|
245
|
+
break;
|
|
246
|
+
case "node:progress":
|
|
247
|
+
if (spinnerActive) {
|
|
248
|
+
recentActivity.push(event.message);
|
|
249
|
+
if (recentActivity.length > MAX_ACTIVITY)
|
|
250
|
+
recentActivity.shift();
|
|
217
251
|
}
|
|
218
252
|
break;
|
|
219
253
|
case "tool:call":
|
|
220
|
-
|
|
221
|
-
spinnerStatus = `${event.tool}(...)`;
|
|
222
|
-
}
|
|
254
|
+
// tool:call is now superseded by the richer node:progress events
|
|
223
255
|
break;
|
|
224
256
|
case "node:exit": {
|
|
225
257
|
stopSpinner();
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
for (const detail of details) {
|
|
239
|
-
console.log(` ${c.subtle("\u21B3")} ${c.subtle(detail)}`);
|
|
240
|
-
}
|
|
258
|
+
const elapsed = formatElapsed(Date.now() - stepStart);
|
|
259
|
+
const icon = event.result.status === "success"
|
|
260
|
+
? c.ok("\u2713")
|
|
261
|
+
: event.result.status === "skipped"
|
|
262
|
+
? c.subtle("\u2212")
|
|
263
|
+
: c.fail("\u2717");
|
|
264
|
+
const reason = event.result.status !== "success" ? event.result.data?.error : undefined;
|
|
265
|
+
const counter = `[${stepIndex}/${totalNodes}]`;
|
|
266
|
+
console.log(formatStepLine(icon, counter, event.node, elapsed, reason));
|
|
267
|
+
const details = getStepDetails(event.node, event.result.data);
|
|
268
|
+
for (const detail of details) {
|
|
269
|
+
console.log(` ${c.subtle("\u21B3")} ${c.subtle(detail)}`);
|
|
241
270
|
}
|
|
242
271
|
break;
|
|
243
272
|
}
|
|
244
273
|
case "route":
|
|
245
|
-
// Optionally log routing decisions
|
|
246
274
|
break;
|
|
247
275
|
case "workflow:end":
|
|
248
|
-
// Output is handled after execute() returns
|
|
249
276
|
break;
|
|
250
277
|
}
|
|
251
278
|
};
|
|
279
|
+
const observer = composeObservers(progressObserver, config.stream ? createStreamObserver() : undefined);
|
|
252
280
|
// ── Build workflow input from config ──────────────────────
|
|
253
281
|
// TODO: The triage workflow input structure may need further refinement
|
|
254
282
|
// once the workflow nodes have stabilized. For now, pass config fields
|
|
@@ -328,7 +356,7 @@ implementCmd.action(async (issueId, options) => {
|
|
|
328
356
|
});
|
|
329
357
|
console.log(chalk.cyan(`\n sweny implement ${issueId}\n`));
|
|
330
358
|
const isTTY = process.stderr.isTTY ?? false;
|
|
331
|
-
const
|
|
359
|
+
const implProgressObserver = (event) => {
|
|
332
360
|
switch (event.type) {
|
|
333
361
|
case "workflow:start":
|
|
334
362
|
process.stderr.write(`\n \u25B2 ${chalk.bold(event.workflow)}\n\n`);
|
|
@@ -355,6 +383,7 @@ implementCmd.action(async (issueId, options) => {
|
|
|
355
383
|
break;
|
|
356
384
|
}
|
|
357
385
|
};
|
|
386
|
+
const observer = composeObservers(implProgressObserver, Boolean(options.stream) ? createStreamObserver() : undefined);
|
|
358
387
|
// Build workflow input for implement
|
|
359
388
|
const workflowInput = {
|
|
360
389
|
issueIdentifier: issueId,
|
|
@@ -446,7 +475,7 @@ export async function workflowRunAction(file, options) {
|
|
|
446
475
|
});
|
|
447
476
|
// Track per-node entry time to compute elapsed on exit
|
|
448
477
|
const nodeEnterTimes = new Map();
|
|
449
|
-
const
|
|
478
|
+
const wfProgressObserver = isJson
|
|
450
479
|
? undefined
|
|
451
480
|
: (event) => {
|
|
452
481
|
switch (event.type) {
|
|
@@ -467,7 +496,6 @@ export async function workflowRunAction(file, options) {
|
|
|
467
496
|
const elapsedMs = Date.now() - enterTime;
|
|
468
497
|
const elapsed = chalk.dim(elapsedMs < 1000 ? `${elapsedMs}ms` : `${Math.round(elapsedMs / 100) / 10}s`);
|
|
469
498
|
if (isTTY) {
|
|
470
|
-
// Overwrite the pending "○ nodeId…" line with the final status
|
|
471
499
|
process.stderr.write(`\x1B[1A\x1B[2K ${icon} ${event.node} ${elapsed}\n`);
|
|
472
500
|
}
|
|
473
501
|
else {
|
|
@@ -480,6 +508,7 @@ export async function workflowRunAction(file, options) {
|
|
|
480
508
|
break;
|
|
481
509
|
}
|
|
482
510
|
};
|
|
511
|
+
const observer = composeObservers(wfProgressObserver, options.stream ? createStreamObserver() : undefined);
|
|
483
512
|
// Build workflow input from config
|
|
484
513
|
const workflowInput = {
|
|
485
514
|
timeRange: config.timeRange,
|
|
@@ -587,6 +616,7 @@ workflowCmd
|
|
|
587
616
|
.description("Run a workflow from a YAML or JSON file")
|
|
588
617
|
.option("--dry-run", "Validate workflow without running")
|
|
589
618
|
.option("--json", "Output result as JSON on stdout; suppress progress output")
|
|
619
|
+
.option("--stream", "Stream NDJSON events to stdout (for Studio / automation)")
|
|
590
620
|
.action(workflowRunAction);
|
|
591
621
|
workflowCmd
|
|
592
622
|
.command("export <name>")
|
package/dist/executor.js
CHANGED
|
@@ -52,6 +52,9 @@ export async function execute(workflow, input, options) {
|
|
|
52
52
|
context,
|
|
53
53
|
tools: trackedTools,
|
|
54
54
|
outputSchema: node.output,
|
|
55
|
+
onProgress: (message) => {
|
|
56
|
+
safeObserve(observer, { type: "node:progress", node: currentId, message }, logger);
|
|
57
|
+
},
|
|
55
58
|
});
|
|
56
59
|
results.set(currentId, result);
|
|
57
60
|
safeObserve(observer, { type: "node:exit", node: currentId, result }, logger);
|
package/dist/skills/github.js
CHANGED
|
@@ -92,6 +92,23 @@ export const github = {
|
|
|
92
92
|
body: JSON.stringify({ title: input.title, body: input.body, labels: input.labels }),
|
|
93
93
|
}),
|
|
94
94
|
},
|
|
95
|
+
{
|
|
96
|
+
name: "github_add_comment",
|
|
97
|
+
description: "Add a comment to a GitHub issue or pull request",
|
|
98
|
+
input_schema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
repo: { type: "string", description: "owner/repo" },
|
|
102
|
+
issue_number: { type: "number", description: "Issue or PR number" },
|
|
103
|
+
body: { type: "string", description: "Comment body (markdown)" },
|
|
104
|
+
},
|
|
105
|
+
required: ["repo", "issue_number", "body"],
|
|
106
|
+
},
|
|
107
|
+
handler: async (input, ctx) => gh(`/repos/${input.repo}/issues/${input.issue_number}/comments`, ctx, {
|
|
108
|
+
method: "POST",
|
|
109
|
+
body: JSON.stringify({ body: input.body }),
|
|
110
|
+
}),
|
|
111
|
+
},
|
|
95
112
|
{
|
|
96
113
|
name: "github_create_pr",
|
|
97
114
|
description: "Create a pull request",
|
package/dist/skills/linear.js
CHANGED
|
@@ -66,6 +66,19 @@ export const linear = {
|
|
|
66
66
|
}
|
|
67
67
|
}`, { query: input.query, first: input.limit ?? 10 }, ctx),
|
|
68
68
|
},
|
|
69
|
+
{
|
|
70
|
+
name: "linear_add_comment",
|
|
71
|
+
description: "Add a comment to a Linear issue",
|
|
72
|
+
input_schema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
issueId: { type: "string", description: "Linear issue ID" },
|
|
76
|
+
body: { type: "string", description: "Comment body (markdown)" },
|
|
77
|
+
},
|
|
78
|
+
required: ["issueId", "body"],
|
|
79
|
+
},
|
|
80
|
+
handler: async (input, ctx) => linearGql(`mutation($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { id body } } }`, { input: { issueId: input.issueId, body: input.body } }, ctx),
|
|
81
|
+
},
|
|
69
82
|
{
|
|
70
83
|
name: "linear_update_issue",
|
|
71
84
|
description: "Update an existing Linear issue",
|
package/dist/types.d.ts
CHANGED
|
@@ -91,6 +91,10 @@ export type ExecutionEvent = {
|
|
|
91
91
|
type: "node:exit";
|
|
92
92
|
node: string;
|
|
93
93
|
result: NodeResult;
|
|
94
|
+
} | {
|
|
95
|
+
type: "node:progress";
|
|
96
|
+
node: string;
|
|
97
|
+
message: string;
|
|
94
98
|
} | {
|
|
95
99
|
type: "route";
|
|
96
100
|
from: string;
|
|
@@ -108,6 +112,8 @@ export interface Claude {
|
|
|
108
112
|
context: Record<string, unknown>;
|
|
109
113
|
tools: Tool[];
|
|
110
114
|
outputSchema?: JSONSchema;
|
|
115
|
+
/** Called with status messages while Claude is working (tool name, etc.) */
|
|
116
|
+
onProgress?: (message: string) => void;
|
|
111
117
|
}): Promise<NodeResult>;
|
|
112
118
|
/** Evaluate a routing condition — pick one of N choices */
|
|
113
119
|
evaluate(opts: {
|
package/dist/workflows/triage.js
CHANGED
|
@@ -41,8 +41,8 @@ Be thorough — the investigation step depends on complete context. Use every to
|
|
|
41
41
|
4. Determine affected services and users.
|
|
42
42
|
5. Recommend a fix approach.
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
skills: ["github"],
|
|
44
|
+
**Novelty check (REQUIRED):** Search the issue tracker for existing open issues that cover the same root cause. Use github_search_issues and/or linear_search_issues with relevant keywords. If you find an existing issue that matches, set is_duplicate=true and duplicate_of to the issue identifier (e.g. "#42" or "ENG-123").`,
|
|
45
|
+
skills: ["github", "linear"],
|
|
46
46
|
output: {
|
|
47
47
|
type: "object",
|
|
48
48
|
properties: {
|
|
@@ -58,15 +58,18 @@ If this is a known issue that already has a ticket, mark it as duplicate.`,
|
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
60
|
create_issue: {
|
|
61
|
-
name: "Create Issue",
|
|
62
|
-
instruction: `
|
|
61
|
+
name: "Create or Update Issue",
|
|
62
|
+
instruction: `Before creating anything, check whether this root cause is already tracked:
|
|
63
63
|
|
|
64
|
-
1.
|
|
65
|
-
2.
|
|
66
|
-
3.
|
|
67
|
-
|
|
64
|
+
1. Search for existing open issues using github_search_issues and/or linear_search_issues with keywords from the root cause, affected service, and error message.
|
|
65
|
+
2. **If a matching issue exists**: Add a comment to it (using github_add_comment or linear_add_comment) noting this re-occurrence with the current timestamp and any new context. Do NOT create a new issue. Return the existing issue's identifier and URL.
|
|
66
|
+
3. **If no matching issue exists**: Create a new issue with:
|
|
67
|
+
- A clear, actionable title
|
|
68
|
+
- Root cause, severity, affected services, reproduction steps, and recommended fix
|
|
69
|
+
- Appropriate labels (bug, severity level, affected service)
|
|
70
|
+
- Links to relevant commits, PRs, or existing issues
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
Use whichever tracker is available to you.`,
|
|
70
73
|
skills: ["linear", "github"],
|
|
71
74
|
},
|
|
72
75
|
notify: {
|