@sweny-ai/core 0.1.131 → 0.1.133
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/cli/comment-output.d.ts +29 -0
- package/dist/cli/comment-output.js +82 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +15 -0
- package/dist/cli/run-output.d.ts +7 -0
- package/dist/cli/run-output.js +12 -3
- package/package.json +2 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
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
|
|
4
|
+
* `--comment-file`, and any runner (the GitHub Action, GitLab, a script) posts it.
|
|
5
|
+
*
|
|
6
|
+
* METADATA ONLY. Node names, statuses, counts, and durations. Never prompts,
|
|
7
|
+
* tool inputs, node outputs, errors, or model prose.
|
|
8
|
+
*/
|
|
9
|
+
import type { ExecutionTrace, NodeResult, Workflow } from "../types.js";
|
|
10
|
+
import { type RunSummary } from "./run-output.js";
|
|
11
|
+
export declare const RUN_COMMENT_FOOTER = "Run with [SWEny](https://sweny.ai): `npx @sweny-ai/core new`";
|
|
12
|
+
export interface RunCommentOptions {
|
|
13
|
+
trace?: ExecutionTrace;
|
|
14
|
+
/** Per-node wall-clock duration in ms, keyed by node id. Missing entries render as "-". */
|
|
15
|
+
durationsMs?: Record<string, number>;
|
|
16
|
+
}
|
|
17
|
+
/** Hidden marker the poster searches for to update the comment in place. */
|
|
18
|
+
export declare function runCommentMarker(workflowId: string): string;
|
|
19
|
+
export declare function formatRunComment(workflow: Workflow, results: Map<string, NodeResult>, summary: RunSummary, opts?: RunCommentOptions): string;
|
|
20
|
+
/**
|
|
21
|
+
* Minimal comment for a run that crashed before producing node results: no DAG
|
|
22
|
+
* and no error text (thrown messages can embed model prose). Replaces any stale
|
|
23
|
+
* success comment for the same workflow.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatCrashComment(workflow: Workflow, summary: RunSummary): string;
|
|
26
|
+
/** Write the comment markdown to `file`. Never throws; returns true only when written. */
|
|
27
|
+
export declare function writeRunComment(file: string, workflow: Workflow, results: Map<string, NodeResult>, summary: RunSummary, opts?: RunCommentOptions & {
|
|
28
|
+
crashed?: boolean;
|
|
29
|
+
}): boolean;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
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
|
|
4
|
+
* `--comment-file`, and any runner (the GitHub Action, GitLab, a script) posts it.
|
|
5
|
+
*
|
|
6
|
+
* METADATA ONLY. Node names, statuses, counts, and durations. Never prompts,
|
|
7
|
+
* tool inputs, node outputs, errors, or model prose.
|
|
8
|
+
*/
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { toMermaidBlock } from "../mermaid.js";
|
|
12
|
+
import { STEP_SUMMARY_CLASS_DEFS, formatReceipt, formatReceiptDuration, nodeStates, } from "./run-output.js";
|
|
13
|
+
export const RUN_COMMENT_FOOTER = "Run with [SWEny](https://sweny.ai): `npx @sweny-ai/core new`";
|
|
14
|
+
/** Hidden marker the poster searches for to update the comment in place. */
|
|
15
|
+
export function runCommentMarker(workflowId) {
|
|
16
|
+
const safe = workflowId.replace(/[^A-Za-z0-9._-]+/g, "_").replace(/-{2,}/g, "-") || "workflow";
|
|
17
|
+
return `<!-- sweny-run-comment:${safe} -->`;
|
|
18
|
+
}
|
|
19
|
+
/** Table-cell-safe text: no pipes, newlines, or backticks. */
|
|
20
|
+
function cell(text) {
|
|
21
|
+
return text.replace(/\s+/g, " ").replace(/\|/g, "\\|").replace(/`/g, "'").trim();
|
|
22
|
+
}
|
|
23
|
+
const STATUS_LABEL = { success: "✅ success", failed: "❌ failed", skipped: "⏭️ skipped" };
|
|
24
|
+
export function formatRunComment(workflow, results, summary, opts = {}) {
|
|
25
|
+
const state = nodeStates(results);
|
|
26
|
+
const rows = [...results].map(([id, r]) => {
|
|
27
|
+
const name = cell(workflow.nodes[id]?.name ?? id);
|
|
28
|
+
const ms = opts.durationsMs?.[id];
|
|
29
|
+
const dur = typeof ms === "number" && Number.isFinite(ms) ? formatReceiptDuration(ms) : "-";
|
|
30
|
+
return `| ${name} | ${STATUS_LABEL[r.status]} | ${dur} |`;
|
|
31
|
+
});
|
|
32
|
+
return [
|
|
33
|
+
runCommentMarker(workflow.id),
|
|
34
|
+
`## ${summary.ok ? "✅" : "❌"} ${cell(workflow.name)}`,
|
|
35
|
+
"",
|
|
36
|
+
`\`${formatReceipt(summary)}\``,
|
|
37
|
+
"",
|
|
38
|
+
toMermaidBlock(workflow, { state, trace: opts.trace, classDefs: STEP_SUMMARY_CLASS_DEFS }),
|
|
39
|
+
"",
|
|
40
|
+
"<details>",
|
|
41
|
+
"<summary>Nodes</summary>",
|
|
42
|
+
"",
|
|
43
|
+
"| Node | Status | Duration |",
|
|
44
|
+
"| --- | --- | --- |",
|
|
45
|
+
...rows,
|
|
46
|
+
"",
|
|
47
|
+
"</details>",
|
|
48
|
+
"",
|
|
49
|
+
`<sub>${RUN_COMMENT_FOOTER}</sub>`,
|
|
50
|
+
"",
|
|
51
|
+
].join("\n");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Minimal comment for a run that crashed before producing node results: no DAG
|
|
55
|
+
* and no error text (thrown messages can embed model prose). Replaces any stale
|
|
56
|
+
* success comment for the same workflow.
|
|
57
|
+
*/
|
|
58
|
+
export function formatCrashComment(workflow, summary) {
|
|
59
|
+
return [
|
|
60
|
+
runCommentMarker(workflow.id),
|
|
61
|
+
`## ❌ ${cell(workflow.name)}`,
|
|
62
|
+
"",
|
|
63
|
+
`\`${formatReceipt(summary)} · crashed\``,
|
|
64
|
+
"",
|
|
65
|
+
"The run stopped before finishing. See the job log for details.",
|
|
66
|
+
"",
|
|
67
|
+
`<sub>${RUN_COMMENT_FOOTER}</sub>`,
|
|
68
|
+
"",
|
|
69
|
+
].join("\n");
|
|
70
|
+
}
|
|
71
|
+
/** Write the comment markdown to `file`. Never throws; returns true only when written. */
|
|
72
|
+
export function writeRunComment(file, workflow, results, summary, opts = {}) {
|
|
73
|
+
try {
|
|
74
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
75
|
+
fs.writeFileSync(file, opts.crashed ? formatCrashComment(workflow, summary) : formatRunComment(workflow, results, summary, opts));
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
process.stderr.write(` ⚠ could not write comment file: ${err instanceof Error ? err.message : err}\n`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/cli/main.d.ts
CHANGED
package/dist/cli/main.js
CHANGED
|
@@ -32,6 +32,7 @@ import { registerRunsCommand } from "./runs.js";
|
|
|
32
32
|
import { runE2eRun, runWithWallClockBudget, DEFAULT_WORKFLOW_TIMEOUT_MS } from "./e2e.js";
|
|
33
33
|
import { createVerboseToolObserver } from "./verbose-observer.js";
|
|
34
34
|
import { createRunLogger, renderReceiptLine, summarizeRun, writeStepSummary, WORKFLOW_RUN_DESCRIPTION, WORKFLOW_RUN_OPTIONS, } from "./run-output.js";
|
|
35
|
+
import { writeRunComment } from "./comment-output.js";
|
|
35
36
|
import { registerTriageCommand, registerImplementCommand, parseCliInputs, validateInputs, validateWarnings, parsePositiveInt, } from "./config.js";
|
|
36
37
|
import { c, formatBanner, getStepDetails, formatStepLine, formatDagResultHuman, formatDagResultMarkdown, formatResultJson, formatValidationErrors, formatCrashError, formatCheckResults, } from "./output.js";
|
|
37
38
|
import { checkProviderConnectivity, detectClaudeCodeLogin, discoverWorkflowSkillIds, validateCheckInputs, } from "./check.js";
|
|
@@ -948,6 +949,14 @@ export async function workflowRunAction(file, options) {
|
|
|
948
949
|
catch {
|
|
949
950
|
// silent
|
|
950
951
|
}
|
|
952
|
+
// PR billboard markdown (metadata only). Written before any early exit so
|
|
953
|
+
// --json runs and failed runs still get a comment.
|
|
954
|
+
if (options.commentFile) {
|
|
955
|
+
writeRunComment(options.commentFile, workflow, results, summarizeRun(results, wfDurationMs), {
|
|
956
|
+
trace,
|
|
957
|
+
durationsMs: Object.fromEntries(nodeTimer.durations),
|
|
958
|
+
});
|
|
959
|
+
}
|
|
951
960
|
if (isJson) {
|
|
952
961
|
process.stdout.write(JSON.stringify(Object.fromEntries(results), null, 2) + "\n");
|
|
953
962
|
process.exit(wfHasFailed ? 1 : 0);
|
|
@@ -986,6 +995,12 @@ export async function workflowRunAction(file, options) {
|
|
|
986
995
|
runLogger.flush();
|
|
987
996
|
recordHistory(nodeTimer.lastResults, undefined, true);
|
|
988
997
|
console.error(` ${renderReceiptLine(summarizeRun(new Map(), Date.now() - runStart, true), isTTY)}\n`);
|
|
998
|
+
// A crash must not leave a stale success comment behind.
|
|
999
|
+
if (options.commentFile) {
|
|
1000
|
+
writeRunComment(options.commentFile, workflow, new Map(), summarizeRun(new Map(), Date.now() - runStart, true), {
|
|
1001
|
+
crashed: true,
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
989
1004
|
// Finalize the cloud run as failed (covers thrown errors, incl.
|
|
990
1005
|
// RouteEvaluationError). Without this a crashed workflow run stays
|
|
991
1006
|
// "running" in cloud forever.
|
package/dist/cli/run-output.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* model prose: only counts, durations, token totals, and cost.
|
|
7
7
|
*/
|
|
8
8
|
import { type ExecutionTrace, type Logger, type NodeResult, type Workflow } from "../types.js";
|
|
9
|
+
import { type NodeStatus } from "../mermaid.js";
|
|
9
10
|
export interface RunSummary {
|
|
10
11
|
ok: boolean;
|
|
11
12
|
nodesOk: number;
|
|
@@ -31,6 +32,12 @@ export declare function formatCost(usd: number): string;
|
|
|
31
32
|
export declare function formatReceipt(s: RunSummary): string;
|
|
32
33
|
/** Receipt for a terminal: colored only when `color` is true. */
|
|
33
34
|
export declare function renderReceiptLine(s: RunSummary, color: boolean): string;
|
|
35
|
+
/** Brand blue (blue-600 / blue-700), not indigo. Applied to successful nodes. */
|
|
36
|
+
export declare const STEP_SUMMARY_CLASS_DEFS: {
|
|
37
|
+
readonly success: "fill:#2563eb,stroke:#1d4ed8,color:#fff,stroke-width:2px";
|
|
38
|
+
};
|
|
39
|
+
/** Per-node Mermaid status from run results. */
|
|
40
|
+
export declare function nodeStates(results: Map<string, NodeResult>): Record<string, NodeStatus>;
|
|
34
41
|
/** Markdown for `$GITHUB_STEP_SUMMARY`: receipt + status-colored Mermaid DAG. */
|
|
35
42
|
export declare function formatStepSummary(workflow: Workflow, results: Map<string, NodeResult>, summary: RunSummary, trace?: ExecutionTrace): string;
|
|
36
43
|
/**
|
package/dist/cli/run-output.js
CHANGED
|
@@ -94,15 +94,20 @@ export function renderReceiptLine(s, color) {
|
|
|
94
94
|
}
|
|
95
95
|
// ── Step summary ────────────────────────────────────────────────
|
|
96
96
|
/** Brand blue (blue-600 / blue-700), not indigo. Applied to successful nodes. */
|
|
97
|
-
const STEP_SUMMARY_CLASS_DEFS = {
|
|
97
|
+
export const STEP_SUMMARY_CLASS_DEFS = {
|
|
98
98
|
success: "fill:#2563eb,stroke:#1d4ed8,color:#fff,stroke-width:2px",
|
|
99
99
|
};
|
|
100
|
-
/**
|
|
101
|
-
export function
|
|
100
|
+
/** Per-node Mermaid status from run results. */
|
|
101
|
+
export function nodeStates(results) {
|
|
102
102
|
const state = {};
|
|
103
103
|
for (const [id, r] of results) {
|
|
104
104
|
state[id] = r.status === "success" ? "success" : r.status === "failed" ? "failed" : "skipped";
|
|
105
105
|
}
|
|
106
|
+
return state;
|
|
107
|
+
}
|
|
108
|
+
/** Markdown for `$GITHUB_STEP_SUMMARY`: receipt + status-colored Mermaid DAG. */
|
|
109
|
+
export function formatStepSummary(workflow, results, summary, trace) {
|
|
110
|
+
const state = nodeStates(results);
|
|
106
111
|
return [
|
|
107
112
|
`## ${summary.ok ? "✅" : "❌"} ${workflow.name}`,
|
|
108
113
|
"",
|
|
@@ -185,5 +190,9 @@ export const WORKFLOW_RUN_OPTIONS = [
|
|
|
185
190
|
"Show raw log lines and each tool call's input and output inline (human-readable, truncated). Use --stream for full untruncated NDJSON.",
|
|
186
191
|
],
|
|
187
192
|
["--mermaid", "Output a Mermaid diagram with execution state after run"],
|
|
193
|
+
[
|
|
194
|
+
"--comment-file <path>",
|
|
195
|
+
"Write a PR-comment markdown (run receipt, status-colored DAG, per-node table; metadata only) to <path> so any CI can post it",
|
|
196
|
+
],
|
|
188
197
|
["--input <json>", "JSON string of input data to pass to the workflow"],
|
|
189
198
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sweny-ai/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.133",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"sweny": "./dist/cli/main.js"
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"ajv": "^8.20.0",
|
|
70
70
|
"esbuild": "^0.28.1",
|
|
71
71
|
"prettier": "^3.9.6",
|
|
72
|
-
"vitest": "^4.1.
|
|
72
|
+
"vitest": "^4.1.11"
|
|
73
73
|
},
|
|
74
74
|
"publishConfig": {
|
|
75
75
|
"access": "public"
|