@sweny-ai/core 0.1.169 → 0.1.171
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/agent-env.d.ts +2 -1
- package/dist/agent-env.js +9 -3
- package/dist/browser.d.ts +2 -0
- package/dist/browser.js +3 -0
- package/dist/budget.js +5 -1
- package/dist/cli/comment-output.d.ts +4 -2
- package/dist/cli/comment-output.js +16 -8
- package/dist/cli/main.js +42 -26
- package/dist/cli/new.js +3 -0
- package/dist/cli/output.d.ts +3 -2
- package/dist/cli/output.js +39 -37
- package/dist/cli/progress.d.ts +33 -0
- package/dist/cli/progress.js +69 -0
- package/dist/cli/renderer.d.ts +4 -0
- package/dist/cli/renderer.js +20 -13
- package/dist/cli/run-output.d.ts +13 -6
- package/dist/cli/run-output.js +36 -26
- package/dist/cli/runs.js +17 -11
- package/dist/cli/style.d.ts +26 -0
- package/dist/cli/style.js +48 -0
- package/dist/cli/terminal.d.ts +47 -0
- package/dist/cli/terminal.js +123 -0
- package/dist/cli/ticket.d.ts +89 -0
- package/dist/cli/ticket.js +218 -0
- package/dist/cli/try.d.ts +15 -8
- package/dist/cli/try.js +84 -65
- package/dist/executor.d.ts +8 -1
- package/dist/executor.js +21 -2
- package/dist/git-credentials.d.ts +106 -0
- package/dist/git-credentials.js +322 -0
- package/dist/harness/acp.js +3 -0
- package/dist/harness/claude-code.d.ts +7 -0
- package/dist/harness/claude-code.js +44 -3
- package/dist/harness/codex.js +3 -0
- package/dist/harness/pi.js +3 -0
- package/dist/harness/policy.d.ts +5 -0
- package/dist/harness/policy.js +14 -0
- package/dist/harness/sandbox-wrapper.d.ts +9 -2
- package/dist/harness/sandbox-wrapper.js +14 -4
- package/dist/harness/types.d.ts +10 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mermaid.d.ts +10 -5
- package/dist/mermaid.js +11 -13
- package/dist/safe-outputs.js +5 -0
- package/dist/skills/git-push.d.ts +62 -0
- package/dist/skills/git-push.js +132 -0
- package/dist/skills/github.d.ts +17 -0
- package/dist/skills/github.js +23 -0
- package/dist/testing.d.ts +16 -12
- package/dist/testing.js +45 -20
- package/dist/theme.d.ts +185 -0
- package/dist/theme.js +183 -0
- package/dist/types.d.ts +2 -0
- package/dist/workflows/browser.js +2 -2
- package/dist/workflows/implement.yml +2 -1
- package/dist/workflows/triage.yml +2 -1
- package/package.json +6 -1
package/dist/agent-env.d.ts
CHANGED
|
@@ -232,7 +232,8 @@ export declare function noPushGitConfig(dir: string): Array<[string, string]>;
|
|
|
232
232
|
* them. A deliberate agent on an unsandboxed run that finds a credential on
|
|
233
233
|
* disk (an ssh key without a passphrase, a keychain entry, a token persisted
|
|
234
234
|
* in `.git/config` by a checkout) can still push. Run sandboxed (the sandbox
|
|
235
|
-
* hides credential files such as `~/.ssh
|
|
235
|
+
* hides credential files such as `~/.ssh`, and a checkout's persisted token,
|
|
236
|
+
* see git-credentials.ts) and without push credentials in CI
|
|
236
237
|
* for a guarantee; under a strict harness policy an unsandboxed staged write
|
|
237
238
|
* node is refused (`stagedWrite` in policy.ts). `enabled` false returns `env`
|
|
238
239
|
* unchanged. Pure apart from creating {@link noPushDir} once.
|
package/dist/agent-env.js
CHANGED
|
@@ -646,7 +646,8 @@ export function noPushGitConfig(dir) {
|
|
|
646
646
|
* them. A deliberate agent on an unsandboxed run that finds a credential on
|
|
647
647
|
* disk (an ssh key without a passphrase, a keychain entry, a token persisted
|
|
648
648
|
* in `.git/config` by a checkout) can still push. Run sandboxed (the sandbox
|
|
649
|
-
* hides credential files such as `~/.ssh
|
|
649
|
+
* hides credential files such as `~/.ssh`, and a checkout's persisted token,
|
|
650
|
+
* see git-credentials.ts) and without push credentials in CI
|
|
650
651
|
* for a guarantee; under a strict harness policy an unsandboxed staged write
|
|
651
652
|
* node is refused (`stagedWrite` in policy.ts). `enabled` false returns `env`
|
|
652
653
|
* unchanged. Pure apart from creating {@link noPushDir} once.
|
|
@@ -670,10 +671,15 @@ export function withPushBlocked(env, enabled) {
|
|
|
670
671
|
n++;
|
|
671
672
|
}
|
|
672
673
|
out.GIT_CONFIG_COUNT = String(n);
|
|
673
|
-
const
|
|
674
|
+
const wrapper = shQuote(path.join(dir, "ssh"));
|
|
675
|
+
// Applied twice, GIT_SSH_COMMAND is already our wrapper: keep the operator's command it recorded,
|
|
676
|
+
// or the wrapper would exec itself forever on a fetch.
|
|
677
|
+
const ownSsh = env.GIT_SSH_COMMAND === wrapper
|
|
678
|
+
? env.SWENY_NO_PUSH_SSH
|
|
679
|
+
: (env.GIT_SSH_COMMAND ?? (env.GIT_SSH ? shQuote(env.GIT_SSH) : undefined));
|
|
674
680
|
if (ownSsh)
|
|
675
681
|
out.SWENY_NO_PUSH_SSH = ownSsh;
|
|
676
|
-
out.GIT_SSH_COMMAND =
|
|
682
|
+
out.GIT_SSH_COMMAND = wrapper;
|
|
677
683
|
out.GIT_SSH_VARIANT = "ssh";
|
|
678
684
|
out.GIT_ASKPASS = path.join(dir, "askpass");
|
|
679
685
|
out.GIT_TERMINAL_PROMPT = "0";
|
package/dist/browser.d.ts
CHANGED
|
@@ -24,3 +24,5 @@ export { isWhenExpression, whenLabel, parseExpression, evaluateExpression, check
|
|
|
24
24
|
export type { ExprNode, ExpressionScope, ExpressionResult } from "./when.js";
|
|
25
25
|
export { workflowToFlow, flowToWorkflow, applyExecutionEvent, exportAsTypescript, getSkillCatalog } from "./studio.js";
|
|
26
26
|
export type { FlowNode, SkillNodeData, FlowEdge } from "./studio.js";
|
|
27
|
+
export { SWENY_TAGLINE, PALETTE, ROLE_COLORS, SURFACE_COLORS, GLYPHS, SPINNER_FRAMES, SPINNER_INTERVAL_MS, TICKET_BOX, MERMAID_CLASS_DEFS, MERMAID_EDGE_STYLES, } from "./theme.js";
|
|
28
|
+
export type { ColorRole, GlyphKey, GlyphSet, TicketBox, DiagramNodeStatus } from "./theme.js";
|
package/dist/browser.js
CHANGED
|
@@ -27,3 +27,6 @@ export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workf
|
|
|
27
27
|
export { isWhenExpression, whenLabel, parseExpression, evaluateExpression, checkExpression, ExpressionSyntaxError, } from "./when.js";
|
|
28
28
|
// Studio adapter
|
|
29
29
|
export { workflowToFlow, flowToWorkflow, applyExecutionEvent, exportAsTypescript, getSkillCatalog } from "./studio.js";
|
|
30
|
+
// Visual tokens (#479): palette, color roles, glyphs, diagram classDefs. No
|
|
31
|
+
// imports, no chalk: Studio and the docs site share them with the CLI.
|
|
32
|
+
export { SWENY_TAGLINE, PALETTE, ROLE_COLORS, SURFACE_COLORS, GLYPHS, SPINNER_FRAMES, SPINNER_INTERVAL_MS, TICKET_BOX, MERMAID_CLASS_DEFS, MERMAID_EDGE_STYLES, } from "./theme.js";
|
package/dist/budget.js
CHANGED
|
@@ -38,7 +38,11 @@ export function hasLimits(l) {
|
|
|
38
38
|
export function spendOf(u) {
|
|
39
39
|
if (!u)
|
|
40
40
|
return {};
|
|
41
|
-
|
|
41
|
+
// Count only finite fields: a NaN beside a real count must not turn the sum into NaN,
|
|
42
|
+
// which no limit compares as crossed (the run would pass its budget without stopping).
|
|
43
|
+
const inTokens = isNum(u.inputTokens) ? u.inputTokens : undefined;
|
|
44
|
+
const outTokens = isNum(u.outputTokens) ? u.outputTokens : undefined;
|
|
45
|
+
const tokens = inTokens !== undefined || outTokens !== undefined ? (inTokens ?? 0) + (outTokens ?? 0) : undefined;
|
|
42
46
|
return { ...(tokens !== undefined ? { tokens } : {}), ...(isNum(u.costUsd) ? { costUsd: u.costUsd } : {}) };
|
|
43
47
|
}
|
|
44
48
|
function over(spent, limit, strictlyOver) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PR billboard: the markdown a CI runner posts as one sticky comment on a
|
|
3
|
-
* pull request. Pure and runner-agnostic; the CLI writes it with
|
|
3
|
+
* pull request. Leads with the receipt ticket, then the run's DAG. Pure and runner-agnostic; the CLI writes it with
|
|
4
4
|
* `--comment-file`, and any runner (the GitHub Action, GitLab, a script) posts it.
|
|
5
5
|
*
|
|
6
6
|
* METADATA ONLY. Node names, statuses, counts, and durations. Never prompts,
|
|
@@ -13,6 +13,8 @@ export interface RunCommentOptions {
|
|
|
13
13
|
trace?: ExecutionTrace;
|
|
14
14
|
/** Per-node wall-clock duration in ms, keyed by node id. Missing entries render as "-". */
|
|
15
15
|
durationsMs?: Record<string, number>;
|
|
16
|
+
/** Run id; the ticket shows its short hash. */
|
|
17
|
+
runId?: string;
|
|
16
18
|
}
|
|
17
19
|
/** Hidden marker the poster searches for to update the comment in place. */
|
|
18
20
|
export declare function runCommentMarker(workflowId: string): string;
|
|
@@ -22,7 +24,7 @@ export declare function formatRunComment(workflow: Workflow, results: Map<string
|
|
|
22
24
|
* and no error text (thrown messages can embed model prose). Replaces any stale
|
|
23
25
|
* success comment for the same workflow.
|
|
24
26
|
*/
|
|
25
|
-
export declare function formatCrashComment(workflow: Workflow, summary: RunSummary): string;
|
|
27
|
+
export declare function formatCrashComment(workflow: Workflow, summary: RunSummary, runId?: string): string;
|
|
26
28
|
/** Write the comment markdown to `file`. Never throws; returns true only when written. */
|
|
27
29
|
export declare function writeRunComment(file: string, workflow: Workflow, results: Map<string, NodeResult>, summary: RunSummary, opts?: RunCommentOptions & {
|
|
28
30
|
crashed?: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PR billboard: the markdown a CI runner posts as one sticky comment on a
|
|
3
|
-
* pull request. Pure and runner-agnostic; the CLI writes it with
|
|
3
|
+
* pull request. Leads with the receipt ticket, then the run's DAG. Pure and runner-agnostic; the CLI writes it with
|
|
4
4
|
* `--comment-file`, and any runner (the GitHub Action, GitLab, a script) posts it.
|
|
5
5
|
*
|
|
6
6
|
* METADATA ONLY. Node names, statuses, counts, and durations. Never prompts,
|
|
@@ -9,8 +9,14 @@
|
|
|
9
9
|
import fs from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { toMermaidBlock } from "../mermaid.js";
|
|
12
|
-
import {
|
|
12
|
+
import { formatReceiptDuration, nodeStates } from "./run-output.js";
|
|
13
|
+
import { formatTicketText } from "./ticket.js";
|
|
14
|
+
import { glyphs } from "./terminal.js";
|
|
13
15
|
export const RUN_COMMENT_FOOTER = "Run with [SWEny](https://github.com/swenyai/sweny): `npx @sweny-ai/core new`";
|
|
16
|
+
/** The receipt ticket as a fenced block, the same card a TTY run ends on. */
|
|
17
|
+
function ticketBlock(workflow, summary, runId, crashed) {
|
|
18
|
+
return ["```text", formatTicketText({ summary, workflow: workflow.id, runId, crashed }), "```"].join("\n");
|
|
19
|
+
}
|
|
14
20
|
/** Hidden marker the poster searches for to update the comment in place. */
|
|
15
21
|
export function runCommentMarker(workflowId) {
|
|
16
22
|
const safe = workflowId.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/-{2,}/g, "-") || "workflow";
|
|
@@ -33,9 +39,9 @@ export function formatRunComment(workflow, results, summary, opts = {}) {
|
|
|
33
39
|
runCommentMarker(workflow.id),
|
|
34
40
|
`## ${summary.ok ? "✅" : "❌"} ${cell(workflow.name)}`,
|
|
35
41
|
"",
|
|
36
|
-
|
|
42
|
+
ticketBlock(workflow, summary, opts.runId),
|
|
37
43
|
"",
|
|
38
|
-
toMermaidBlock(workflow, { state, trace: opts.trace
|
|
44
|
+
toMermaidBlock(workflow, { state, trace: opts.trace }),
|
|
39
45
|
"",
|
|
40
46
|
"<details>",
|
|
41
47
|
"<summary>Nodes</summary>",
|
|
@@ -55,12 +61,12 @@ export function formatRunComment(workflow, results, summary, opts = {}) {
|
|
|
55
61
|
* and no error text (thrown messages can embed model prose). Replaces any stale
|
|
56
62
|
* success comment for the same workflow.
|
|
57
63
|
*/
|
|
58
|
-
export function formatCrashComment(workflow, summary) {
|
|
64
|
+
export function formatCrashComment(workflow, summary, runId) {
|
|
59
65
|
return [
|
|
60
66
|
runCommentMarker(workflow.id),
|
|
61
67
|
`## ❌ ${cell(workflow.name)}`,
|
|
62
68
|
"",
|
|
63
|
-
|
|
69
|
+
ticketBlock(workflow, summary, runId, true),
|
|
64
70
|
"",
|
|
65
71
|
"The run stopped before finishing. See the job log for details.",
|
|
66
72
|
"",
|
|
@@ -72,11 +78,13 @@ export function formatCrashComment(workflow, summary) {
|
|
|
72
78
|
export function writeRunComment(file, workflow, results, summary, opts = {}) {
|
|
73
79
|
try {
|
|
74
80
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
75
|
-
fs.writeFileSync(file, opts.crashed
|
|
81
|
+
fs.writeFileSync(file, opts.crashed
|
|
82
|
+
? formatCrashComment(workflow, summary, opts.runId)
|
|
83
|
+
: formatRunComment(workflow, results, summary, opts));
|
|
76
84
|
return true;
|
|
77
85
|
}
|
|
78
86
|
catch (err) {
|
|
79
|
-
process.stderr.write(`
|
|
87
|
+
process.stderr.write(` ${glyphs().warning} could not write comment file: ${err instanceof Error ? err.message : err}\n`);
|
|
80
88
|
return false;
|
|
81
89
|
}
|
|
82
90
|
}
|
package/dist/cli/main.js
CHANGED
|
@@ -39,8 +39,12 @@ import { JournalLockedError, JournalMismatchError, RunJournal } from "../journal
|
|
|
39
39
|
import { journalDisabled, prepareResume, RESUME_SHARED_RUN_FLAGS, WORKFLOW_RESUME_OPTIONS, } from "./resume.js";
|
|
40
40
|
import { runE2eRun, runWithWallClockBudget, DEFAULT_WORKFLOW_TIMEOUT_MS } from "./e2e.js";
|
|
41
41
|
import { createVerboseToolObserver } from "./verbose-observer.js";
|
|
42
|
-
import { createRunLogger,
|
|
42
|
+
import { createRunLogger, summarizeRun, writeStepSummary, WORKFLOW_RUN_DESCRIPTION, WORKFLOW_RUN_OPTIONS, } from "./run-output.js";
|
|
43
43
|
import { writeRunComment } from "./comment-output.js";
|
|
44
|
+
import { writeReceipt } from "./ticket.js";
|
|
45
|
+
import { createNodeProgress, toolCallsText } from "./progress.js";
|
|
46
|
+
import { applyNoColor, createPaint } from "./style.js";
|
|
47
|
+
import { colorEnabled, glyphsFor, richOutput, spinnerFramesFor, supportsUnicode } from "./terminal.js";
|
|
44
48
|
import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs, validateWarnings, parsePositiveInt, } from "./config.js";
|
|
45
49
|
import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, writeResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
|
|
46
50
|
import { checkProviderConnectivity, detectClaudeCodeLogin, discoverWorkflowSkillIds, validateCheckInputs, } from "./check.js";
|
|
@@ -83,6 +87,8 @@ if (process.argv[2] !== "try") {
|
|
|
83
87
|
// Agent sandbox / env-passthrough keys from .sweny.yml -> SWENY_* env (#360).
|
|
84
88
|
applyAgentFileConfig(loadConfigFile());
|
|
85
89
|
}
|
|
90
|
+
// NO_COLOR (https://no-color.org) turns off every color, including direct chalk calls.
|
|
91
|
+
applyNoColor();
|
|
86
92
|
const program = new Command()
|
|
87
93
|
.name("sweny")
|
|
88
94
|
.description("Workflows for coding agents. One set of rules, a receipt for every run.")
|
|
@@ -275,7 +281,7 @@ triageCmd.action(async (options) => {
|
|
|
275
281
|
mcpServers: config.mcpServers,
|
|
276
282
|
});
|
|
277
283
|
// ── Progress display state ─────────────────────────────────
|
|
278
|
-
const FRAMES =
|
|
284
|
+
const FRAMES = spinnerFramesFor(supportsUnicode());
|
|
279
285
|
const isTTY = !config.json && (process.stderr.isTTY ?? false);
|
|
280
286
|
const MAX_ACTIVITY = 3;
|
|
281
287
|
let spinnerInterval;
|
|
@@ -297,7 +303,7 @@ triageCmd.action(async (options) => {
|
|
|
297
303
|
/** Render the multi-line progress block (spinner + activity lines). */
|
|
298
304
|
function renderProgressBlock() {
|
|
299
305
|
const cols = process.stderr.columns || 80;
|
|
300
|
-
const frame =
|
|
306
|
+
const frame = c.brand(FRAMES[frameIdx++ % FRAMES.length]);
|
|
301
307
|
const counter = c.subtle(`[${stepIndex}/${totalNodes}]`);
|
|
302
308
|
const elapsed = c.subtle(formatElapsed(Date.now() - stepStart));
|
|
303
309
|
const headerLine = ` ${frame} ${counter} ${stepLabel} ${elapsed}`;
|
|
@@ -594,7 +600,7 @@ implementCmd.action(async (issueId, options) => {
|
|
|
594
600
|
const implProgressObserver = (event) => {
|
|
595
601
|
switch (event.type) {
|
|
596
602
|
case "workflow:start":
|
|
597
|
-
process.stderr.write(`\n \u25B2 ${chalk.bold(event.workflow)}\n\n`);
|
|
603
|
+
process.stderr.write(`\n ${c.brand("\u25B2")} ${chalk.bold(event.workflow)}\n\n`);
|
|
598
604
|
break;
|
|
599
605
|
case "node:enter":
|
|
600
606
|
process.stderr.write(` ${c.subtle("\u25CB")} ${chalk.dim(event.node)}\u2026\n`);
|
|
@@ -852,32 +858,40 @@ resume) {
|
|
|
852
858
|
}, typeof options.harnessPolicy === "string" ? options.harnessPolicy : undefined);
|
|
853
859
|
// Track per-node entry time to compute elapsed on exit
|
|
854
860
|
const nodeEnterTimes = new Map();
|
|
861
|
+
// Node progress (#479): a spinner on the running node when stderr is live,
|
|
862
|
+
// then one settled line per node. Verbose and stream output interleave with
|
|
863
|
+
// it, so those runs get plain lines.
|
|
864
|
+
const unicode = supportsUnicode();
|
|
865
|
+
const g = glyphsFor(unicode);
|
|
866
|
+
const progressPaint = createPaint(!isJson && colorEnabled(process.stderr));
|
|
867
|
+
const progress = createNodeProgress({
|
|
868
|
+
write: (s) => void process.stderr.write(s),
|
|
869
|
+
live: !isJson && richOutput(process.stderr) && !options.verbose && !options.stream,
|
|
870
|
+
unicode,
|
|
871
|
+
paint: progressPaint,
|
|
872
|
+
announce: true,
|
|
873
|
+
});
|
|
855
874
|
const wfProgressObserver = isJson
|
|
856
875
|
? undefined
|
|
857
876
|
: (event) => {
|
|
858
877
|
switch (event.type) {
|
|
859
878
|
case "workflow:start":
|
|
860
|
-
process.stderr.write(`\n
|
|
879
|
+
process.stderr.write(`\n ${progressPaint.brand(g.brand)} ${progressPaint.strong(event.workflow)}\n\n`);
|
|
861
880
|
break;
|
|
862
881
|
case "node:enter":
|
|
863
882
|
nodeEnterTimes.set(event.node, Date.now());
|
|
864
|
-
|
|
883
|
+
progress.enter(event.node);
|
|
884
|
+
break;
|
|
885
|
+
case "node:progress":
|
|
886
|
+
progress.tick(event.node);
|
|
865
887
|
break;
|
|
866
888
|
case "node:exit": {
|
|
867
|
-
const icon = event.result.status === "success"
|
|
868
|
-
? c.ok("\u2713")
|
|
869
|
-
: event.result.status === "skipped"
|
|
870
|
-
? c.subtle("\u2212")
|
|
871
|
-
: c.fail("\u2717");
|
|
872
889
|
const enterTime = nodeEnterTimes.get(event.node) ?? Date.now();
|
|
873
890
|
const elapsedMs = Date.now() - enterTime;
|
|
874
|
-
const elapsed =
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
else {
|
|
879
|
-
process.stderr.write(` ${icon} ${event.node} ${elapsed}\n`);
|
|
880
|
-
}
|
|
891
|
+
const elapsed = elapsedMs < 1000 ? `${elapsedMs}ms` : `${Math.round(elapsedMs / 100) / 10}s`;
|
|
892
|
+
const calls = event.result.toolCalls.length;
|
|
893
|
+
const detail = [elapsed, ...(calls > 0 ? [toolCallsText(calls)] : [])].join(` ${g.sep} `);
|
|
894
|
+
progress.exit(event.node, event.result.status, detail);
|
|
881
895
|
runLogger.flush();
|
|
882
896
|
break;
|
|
883
897
|
}
|
|
@@ -1059,6 +1073,7 @@ resume) {
|
|
|
1059
1073
|
writeRunComment(options.commentFile, workflow, results, summarizeRun(results, wfDurationMs, false, trace), {
|
|
1060
1074
|
trace,
|
|
1061
1075
|
durationsMs: Object.fromEntries(nodeTimer.durations),
|
|
1076
|
+
runId,
|
|
1062
1077
|
});
|
|
1063
1078
|
}
|
|
1064
1079
|
if (isJson) {
|
|
@@ -1098,14 +1113,15 @@ resume) {
|
|
|
1098
1113
|
else
|
|
1099
1114
|
console.log(`${block}\n`);
|
|
1100
1115
|
}
|
|
1116
|
+
// The receipt: a ticket on a TTY, one plain line in CI and pipes.
|
|
1101
1117
|
if (wfHasFailed) {
|
|
1102
|
-
|
|
1118
|
+
await writeReceipt(receipt, { workflow: workflow.id, runId, stream: process.stderr });
|
|
1103
1119
|
if (journal?.active)
|
|
1104
1120
|
console.error(c.subtle(` resume with: sweny workflow resume ${runId}\n`));
|
|
1105
1121
|
process.exit(1);
|
|
1106
1122
|
return;
|
|
1107
1123
|
}
|
|
1108
|
-
|
|
1124
|
+
await writeReceipt(receipt, { workflow: workflow.id, runId, stream: process.stdout });
|
|
1109
1125
|
process.exit(0);
|
|
1110
1126
|
}
|
|
1111
1127
|
catch (err) {
|
|
@@ -1115,18 +1131,18 @@ resume) {
|
|
|
1115
1131
|
process.exit(1);
|
|
1116
1132
|
return;
|
|
1117
1133
|
}
|
|
1134
|
+
progress.stop();
|
|
1118
1135
|
console.error(formatCrashError(err));
|
|
1119
1136
|
runLogger.flush();
|
|
1120
1137
|
journal?.end("crashed");
|
|
1121
1138
|
if (journal)
|
|
1122
1139
|
console.error(c.subtle(` resume with: sweny workflow resume ${runId}`));
|
|
1123
1140
|
recordHistory(nodeTimer.lastResults, undefined, true);
|
|
1124
|
-
|
|
1141
|
+
const crashSummary = summarizeRun(new Map(), Date.now() - runStart, true);
|
|
1142
|
+
await writeReceipt(crashSummary, { workflow: workflow.id, runId, crashed: true, stream: process.stderr });
|
|
1125
1143
|
// A crash must not leave a stale success comment behind.
|
|
1126
1144
|
if (options.commentFile) {
|
|
1127
|
-
writeRunComment(options.commentFile, workflow, new Map(),
|
|
1128
|
-
crashed: true,
|
|
1129
|
-
});
|
|
1145
|
+
writeRunComment(options.commentFile, workflow, new Map(), crashSummary, { crashed: true, runId });
|
|
1130
1146
|
}
|
|
1131
1147
|
// Finalize the cloud run as failed (covers thrown errors, incl.
|
|
1132
1148
|
// RouteEvaluationError). Without this a crashed workflow run stays
|
|
@@ -1272,7 +1288,7 @@ workflowCmd
|
|
|
1272
1288
|
.command("diagram <file>")
|
|
1273
1289
|
.description("Render a workflow as a Mermaid diagram (raw .mmd by default; .md output auto-fences)")
|
|
1274
1290
|
.option("--direction <dir>", "Graph direction: TB (top-bottom) or LR (left-right)", "TB")
|
|
1275
|
-
.option("--title <title>", "Inject a title header (off by default
|
|
1291
|
+
.option("--title <title>", "Inject a title header (off by default: raw Mermaid has no title)")
|
|
1276
1292
|
.option("--block", "Wrap in ```mermaid fenced code block (forces fencing in any output)")
|
|
1277
1293
|
.option("--no-block", "Force raw Mermaid even when writing to a .md file")
|
|
1278
1294
|
.option("-o, --output <path>", "Write to a file instead of stdout (.mmd/.mermaid raw; .md fenced)")
|
|
@@ -1483,7 +1499,7 @@ program
|
|
|
1483
1499
|
shell: process.platform === "win32",
|
|
1484
1500
|
});
|
|
1485
1501
|
if (res.error) {
|
|
1486
|
-
process.stderr.write(chalk.red(` Error: couldn't run ${cmd}
|
|
1502
|
+
process.stderr.write(chalk.red(` Error: couldn't run ${cmd}: ${res.error.message}`) + "\n");
|
|
1487
1503
|
return 127;
|
|
1488
1504
|
}
|
|
1489
1505
|
return typeof res.status === "number" ? res.status : 1;
|
package/dist/cli/new.js
CHANGED
|
@@ -385,6 +385,9 @@ export function buildActionWorkflow(credentials, cronExpression) {
|
|
|
385
385
|
lines.push(" runs-on: ubuntu-latest");
|
|
386
386
|
lines.push(" steps:");
|
|
387
387
|
lines.push(" - uses: actions/checkout@v4");
|
|
388
|
+
// #473: no token on disk for the agents to read; sweny pushes the PR branch itself.
|
|
389
|
+
lines.push(" with:");
|
|
390
|
+
lines.push(" persist-credentials: false");
|
|
388
391
|
lines.push(" - uses: swenyai/sweny@v5");
|
|
389
392
|
lines.push(" with:");
|
|
390
393
|
lines.push(" workflow: triage");
|
package/dist/cli/output.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export declare const c: {
|
|
|
9
9
|
report: import("chalk").ChalkInstance;
|
|
10
10
|
ok: import("chalk").ChalkInstance;
|
|
11
11
|
fail: import("chalk").ChalkInstance;
|
|
12
|
+
warn: import("chalk").ChalkInstance;
|
|
12
13
|
subtle: import("chalk").ChalkInstance;
|
|
13
14
|
link: import("chalk").ChalkInstance;
|
|
14
15
|
};
|
|
@@ -43,9 +44,9 @@ export declare function writeResultJson(results: Map<string, NodeResult>): Promi
|
|
|
43
44
|
* - Node execution details (collapsible) with tool-call summaries
|
|
44
45
|
*/
|
|
45
46
|
export interface FormatMarkdownOptions {
|
|
46
|
-
/** Workflow definition
|
|
47
|
+
/** Workflow definition: required for the Mermaid diagram. */
|
|
47
48
|
workflow?: Workflow;
|
|
48
|
-
/** Execution trace from `execute()
|
|
49
|
+
/** Execution trace from `execute()`: colors the diagram and drives the path/routing sections. */
|
|
49
50
|
trace?: ExecutionTrace;
|
|
50
51
|
}
|
|
51
52
|
export declare function formatDagResultMarkdown(results: Map<string, NodeResult>, durationMs: number, config?: CliConfig, options?: FormatMarkdownOptions): string;
|
package/dist/cli/output.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { toMermaidBlock } from "../mermaid.js";
|
|
3
|
-
|
|
3
|
+
import { PALETTE, ROLE_COLORS } from "../theme.js";
|
|
4
|
+
// ── Color palette (tokens from theme.ts; one color per meaning) ─
|
|
4
5
|
export const c = {
|
|
5
|
-
brand: chalk.hex(
|
|
6
|
-
brandDim: chalk.hex(
|
|
7
|
-
learn: chalk.hex(
|
|
8
|
-
act: chalk.hex(
|
|
9
|
-
report: chalk.hex(
|
|
10
|
-
ok: chalk.hex(
|
|
11
|
-
fail: chalk.hex(
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
brand: chalk.hex(ROLE_COLORS.brand),
|
|
7
|
+
brandDim: chalk.hex(ROLE_COLORS.brandDeep),
|
|
8
|
+
learn: chalk.hex(ROLE_COLORS.info),
|
|
9
|
+
act: chalk.hex(ROLE_COLORS.warning),
|
|
10
|
+
report: chalk.hex(PALETTE.slate400),
|
|
11
|
+
ok: chalk.hex(ROLE_COLORS.success),
|
|
12
|
+
fail: chalk.hex(ROLE_COLORS.error),
|
|
13
|
+
warn: chalk.hex(ROLE_COLORS.warning),
|
|
14
|
+
subtle: chalk.hex(ROLE_COLORS.muted),
|
|
15
|
+
link: chalk.hex(ROLE_COLORS.info).underline,
|
|
14
16
|
};
|
|
15
17
|
export function phaseColor(phase) {
|
|
16
18
|
return phase === "learn" ? c.learn : phase === "act" ? c.act : phase === "report" ? c.report : chalk.white;
|
|
@@ -84,7 +86,7 @@ export function formatBanner(config, version) {
|
|
|
84
86
|
const title = `${c.brand("\u25B2")} ${chalk.bold(c.brand("SWEny"))} ${chalk.bold("Triage")}`;
|
|
85
87
|
const ver = c.subtle(`v${version}`);
|
|
86
88
|
const titlePad = BOX_WIDTH - 4 - visLen(title) - visLen(ver);
|
|
87
|
-
const mode = config.dryRun ?
|
|
89
|
+
const mode = config.dryRun ? c.warn("dry run") : c.ok("live");
|
|
88
90
|
const header = [title + " ".repeat(Math.max(1, titlePad)) + ver];
|
|
89
91
|
const rows = [
|
|
90
92
|
`${c.subtle("Repository")}${" ".repeat(6)}${chalk.white(config.repository)}`,
|
|
@@ -134,7 +136,7 @@ export function getStepDetails(name, data) {
|
|
|
134
136
|
case "novelty-gate": {
|
|
135
137
|
const action = data.action;
|
|
136
138
|
if (action === "dry-run")
|
|
137
|
-
details.push("Dry run
|
|
139
|
+
details.push("Dry run: analysis only");
|
|
138
140
|
else if (action === "skip")
|
|
139
141
|
details.push("No novel issues found");
|
|
140
142
|
else if (action === "+1")
|
|
@@ -182,7 +184,7 @@ export function getStepDetails(name, data) {
|
|
|
182
184
|
export function formatStepLine(icon, counter, name, elapsed, reason) {
|
|
183
185
|
const label = `${counter} ${name}`;
|
|
184
186
|
const pad = Math.max(1, 40 - visLen(label));
|
|
185
|
-
const suffix = reason ? c.subtle(`
|
|
187
|
+
const suffix = reason ? c.subtle(` ${reason}`) : "";
|
|
186
188
|
return ` ${icon} ${c.subtle(counter)} ${name}${" ".repeat(pad)}${c.subtle(elapsed)}${suffix}`;
|
|
187
189
|
}
|
|
188
190
|
// ── DAG result summary ──────────────────────────────────────────
|
|
@@ -202,7 +204,7 @@ export function formatDagResultHuman(results, durationMs, config) {
|
|
|
202
204
|
if (createPrResult?.data?.prUrl) {
|
|
203
205
|
return formatDagSuccessResult(results, duration);
|
|
204
206
|
}
|
|
205
|
-
// Dry run
|
|
207
|
+
// Dry run: show findings summary, no side effects taken
|
|
206
208
|
if (config?.dryRun) {
|
|
207
209
|
return formatDagDryRunResult(results, duration);
|
|
208
210
|
}
|
|
@@ -283,7 +285,7 @@ function formatDagDryRunResult(results, duration) {
|
|
|
283
285
|
if (rec)
|
|
284
286
|
body.push(`${c.subtle("Next")}${" ".repeat(6)}${String(rec)}`);
|
|
285
287
|
body.push("");
|
|
286
|
-
body.push(c.subtle("No side effects
|
|
288
|
+
body.push(c.subtle("No side effects: dry run mode"));
|
|
287
289
|
return ["", boxTop(), ...boxSection(header), boxDivider(), ...boxSection(body), boxBottom(), ""].join("\n");
|
|
288
290
|
}
|
|
289
291
|
function formatDagFailureResult(nodeId, result, duration) {
|
|
@@ -342,19 +344,19 @@ export function formatValidationErrors(errors) {
|
|
|
342
344
|
export function extractCredentialHint(err) {
|
|
343
345
|
const msg = err instanceof Error ? err.message : String(err);
|
|
344
346
|
if (/401|unauthorized|authentication/i.test(msg) && /anthropic/i.test(msg)) {
|
|
345
|
-
return "Check your ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN
|
|
347
|
+
return "Check your ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN. Get a key at https://console.anthropic.com";
|
|
346
348
|
}
|
|
347
349
|
if (/401|403|unauthorized/i.test(msg) && /datadog/i.test(msg)) {
|
|
348
|
-
return "Check your DD_API_KEY and DD_APP_KEY
|
|
350
|
+
return "Check your DD_API_KEY and DD_APP_KEY. Find them at https://app.datadoghq.com/organization-settings/api-keys";
|
|
349
351
|
}
|
|
350
352
|
if (/401|403|unauthorized/i.test(msg) && /linear/i.test(msg)) {
|
|
351
|
-
return "Check your LINEAR_API_KEY
|
|
353
|
+
return "Check your LINEAR_API_KEY. Find it at https://linear.app/settings/api";
|
|
352
354
|
}
|
|
353
355
|
if (/401|403|unauthorized/i.test(msg) && /github/i.test(msg)) {
|
|
354
|
-
return "Check your GITHUB_TOKEN
|
|
356
|
+
return "Check your GITHUB_TOKEN. Create a Personal Access Token at https://github.com/settings/tokens";
|
|
355
357
|
}
|
|
356
358
|
if (/ENOTFOUND|ETIMEDOUT|network/i.test(msg)) {
|
|
357
|
-
return "Network error
|
|
359
|
+
return "Network error: check your internet connection and provider endpoint URL.";
|
|
358
360
|
}
|
|
359
361
|
return null;
|
|
360
362
|
}
|
|
@@ -380,14 +382,14 @@ export function formatCheckResults(results) {
|
|
|
380
382
|
const body = results.map((r) => {
|
|
381
383
|
const icon = r.status === "ok" ? c.ok("\u2713") : r.status === "fail" ? c.fail("\u2717") : c.subtle("\u2212");
|
|
382
384
|
const name = chalk.white(r.name);
|
|
383
|
-
const detail = r.status === "ok" ? c.subtle(r.detail) : r.status === "fail" ?
|
|
385
|
+
const detail = r.status === "ok" ? c.subtle(r.detail) : r.status === "fail" ? c.fail(r.detail) : c.subtle(r.detail);
|
|
384
386
|
return `${icon} ${name}\n ${detail}`;
|
|
385
387
|
});
|
|
386
388
|
const hasFailure = results.some((r) => r.status === "fail");
|
|
387
389
|
const summary = hasFailure
|
|
388
|
-
? c.fail("One or more checks failed
|
|
390
|
+
? c.fail("One or more checks failed. Fix the issues above before running sweny triage.")
|
|
389
391
|
: results.every((r) => r.status === "skip")
|
|
390
|
-
? c.subtle("All providers set to file mode
|
|
392
|
+
? c.subtle("All providers set to file mode: no network checks performed.")
|
|
391
393
|
: c.ok("All checks passed.");
|
|
392
394
|
return [
|
|
393
395
|
"",
|
|
@@ -442,19 +444,19 @@ export function formatDagResultMarkdown(results, durationMs, config, options = {
|
|
|
442
444
|
lines.push(`## ❌ SWEny Triage Failed`);
|
|
443
445
|
}
|
|
444
446
|
else if (prData?.prUrl) {
|
|
445
|
-
lines.push(`## ✅ SWEny Triage
|
|
447
|
+
lines.push(`## ✅ SWEny Triage: PR opened`);
|
|
446
448
|
}
|
|
447
449
|
else if (issueData?.issueIdentifier || issueData?.issueUrl) {
|
|
448
|
-
lines.push(`## ✅ SWEny Triage
|
|
450
|
+
lines.push(`## ✅ SWEny Triage: Issue created`);
|
|
449
451
|
}
|
|
450
452
|
else if (isDryRun) {
|
|
451
|
-
lines.push(`## 🔍 SWEny Triage
|
|
453
|
+
lines.push(`## 🔍 SWEny Triage: Dry run complete`);
|
|
452
454
|
}
|
|
453
455
|
else if (novelCount === 0) {
|
|
454
|
-
lines.push(`## ✅ SWEny Triage
|
|
456
|
+
lines.push(`## ✅ SWEny Triage: No new incidents`);
|
|
455
457
|
}
|
|
456
458
|
else {
|
|
457
|
-
lines.push(`## ℹ️ SWEny Triage
|
|
459
|
+
lines.push(`## ℹ️ SWEny Triage: No action taken`);
|
|
458
460
|
}
|
|
459
461
|
lines.push("");
|
|
460
462
|
// ── Workflow diagram (Mermaid) ────────────────────────────────
|
|
@@ -532,9 +534,9 @@ export function formatDagResultMarkdown(results, durationMs, config, options = {
|
|
|
532
534
|
lines.push("| --- | --- | --- | --- | --- |");
|
|
533
535
|
for (let i = 0; i < findings.length; i++) {
|
|
534
536
|
const f = findings[i];
|
|
535
|
-
const sev = String(f.severity ?? "
|
|
536
|
-
const title = String(f.title ?? "
|
|
537
|
-
const complexity = String(f.fix_complexity ?? "
|
|
537
|
+
const sev = String(f.severity ?? "-");
|
|
538
|
+
const title = String(f.title ?? "-").replace(/\|/g, "\\|");
|
|
539
|
+
const complexity = String(f.fix_complexity ?? "-");
|
|
538
540
|
const status = f.is_duplicate ? `dup of ${f.duplicate_of ?? "existing"}` : "novel";
|
|
539
541
|
lines.push(`| ${i + 1} | ${sev} | ${title} | ${complexity} | ${status} |`);
|
|
540
542
|
}
|
|
@@ -549,7 +551,7 @@ export function formatDagResultMarkdown(results, durationMs, config, options = {
|
|
|
549
551
|
const title = issueData.issueTitle ? String(issueData.issueTitle) : "";
|
|
550
552
|
const url = issueData.issueUrl ? String(issueData.issueUrl) : "";
|
|
551
553
|
const link = url ? `[${id || url}](${url})` : id;
|
|
552
|
-
lines.push(`- **Issue created:** ${link}${title ?
|
|
554
|
+
lines.push(`- **Issue created:** ${link}${title ? `: ${title}` : ""}`);
|
|
553
555
|
}
|
|
554
556
|
if (prData?.prUrl) {
|
|
555
557
|
const num = prData.prNumber ? `#${String(prData.prNumber)}` : "";
|
|
@@ -565,7 +567,7 @@ export function formatDagResultMarkdown(results, durationMs, config, options = {
|
|
|
565
567
|
lines.push("");
|
|
566
568
|
}
|
|
567
569
|
if (isDryRun) {
|
|
568
|
-
lines.push("_Dry run mode
|
|
570
|
+
lines.push("_Dry run mode: no side effects were taken._");
|
|
569
571
|
lines.push("");
|
|
570
572
|
}
|
|
571
573
|
// ── Node execution details (collapsible) ──────────────────────
|
|
@@ -578,17 +580,17 @@ function appendNodeDetails(lines, results) {
|
|
|
578
580
|
lines.push("<details><summary>Node execution details</summary>");
|
|
579
581
|
lines.push("");
|
|
580
582
|
for (const [nodeId, result] of results) {
|
|
581
|
-
const icon = result.status === "success" ? "✓" : result.status === "failed" ? "✗" : "
|
|
583
|
+
const icon = result.status === "success" ? "✓" : result.status === "failed" ? "✗" : "−";
|
|
582
584
|
const toolCount = result.toolCalls?.length ?? 0;
|
|
583
|
-
lines.push(`#### ${icon} \`${nodeId}\`
|
|
585
|
+
lines.push(`#### ${icon} \`${nodeId}\` · ${toolCount} tool call${toolCount === 1 ? "" : "s"}`);
|
|
584
586
|
lines.push("");
|
|
585
587
|
if (toolCount > 0 && result.toolCalls) {
|
|
586
588
|
lines.push("| Tool | Input (truncated) |");
|
|
587
589
|
lines.push("| --- | --- |");
|
|
588
590
|
for (const tc of result.toolCalls.slice(0, 20)) {
|
|
589
|
-
const name = tc.tool ?? "
|
|
591
|
+
const name = tc.tool ?? "-";
|
|
590
592
|
const input = summarizeToolInput(tc.input).replace(/\|/g, "\\|");
|
|
591
|
-
lines.push(`| \`${name}\` | ${input || "
|
|
593
|
+
lines.push(`| \`${name}\` | ${input || "-"} |`);
|
|
592
594
|
}
|
|
593
595
|
if (toolCount > 20) {
|
|
594
596
|
lines.push(`| … | _${toolCount - 20} more tool calls_ |`);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node progress lines for `sweny workflow run` and `sweny try`: a spinner on
|
|
3
|
+
* the running node when the output is live, then one settled line per node.
|
|
4
|
+
*
|
|
5
|
+
* ⠙ survey 7 tool calls (live, redrawn in place)
|
|
6
|
+
* ✓ survey 18s · 12 tool calls (settled)
|
|
7
|
+
*
|
|
8
|
+
* Not live (pipes, CI, --verbose): no redraws, no spinner, one line per event.
|
|
9
|
+
*/
|
|
10
|
+
import type { Paint } from "./style.js";
|
|
11
|
+
export interface NodeProgressOptions {
|
|
12
|
+
write: (s: string) => void;
|
|
13
|
+
/** Redraw the running node in place with a spinner. */
|
|
14
|
+
live: boolean;
|
|
15
|
+
unicode: boolean;
|
|
16
|
+
paint: Paint;
|
|
17
|
+
/** Pad node ids to this width so the details line up. */
|
|
18
|
+
idWidth?: number;
|
|
19
|
+
/** When not live, print a `○ node…` line as each node starts. Default false. */
|
|
20
|
+
announce?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export type SettledStatus = "success" | "failed" | "skipped";
|
|
23
|
+
export interface NodeProgress {
|
|
24
|
+
enter(node: string): void;
|
|
25
|
+
/** One unit of work (a tool call) on the running node. */
|
|
26
|
+
tick(node: string): void;
|
|
27
|
+
/** Settle the node: its glyph, its id, and a muted detail (`18s · 12 tool calls`). */
|
|
28
|
+
exit(node: string, status: SettledStatus, detail?: string): void;
|
|
29
|
+
/** Stop any spinner without settling (a crash). Clears the live line. */
|
|
30
|
+
stop(): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function toolCallsText(n: number): string;
|
|
33
|
+
export declare function createNodeProgress(o: NodeProgressOptions): NodeProgress;
|