@sweny-ai/core 0.1.170 → 0.1.172
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/check.js +12 -12
- package/dist/cli/comment-output.d.ts +4 -2
- package/dist/cli/comment-output.js +16 -8
- package/dist/cli/config.js +24 -24
- package/dist/cli/e2e.js +17 -17
- package/dist/cli/main.js +42 -26
- package/dist/cli/marketplace.js +3 -3
- package/dist/cli/new.js +11 -8
- 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/publish.js +2 -2
- package/dist/cli/record.d.ts +66 -0
- package/dist/cli/record.js +344 -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/setup.js +7 -7
- 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 +19 -8
- package/dist/cli/try.js +97 -65
- package/dist/cli/upgrade.js +1 -1
- package/dist/executor.d.ts +8 -1
- package/dist/executor.js +20 -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/theme.d.ts +185 -0
- package/dist/theme.js +183 -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/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/marketplace.js
CHANGED
|
@@ -22,7 +22,7 @@ export async function fetchMarketplaceWorkflow(id) {
|
|
|
22
22
|
res = await fetch(url);
|
|
23
23
|
}
|
|
24
24
|
catch {
|
|
25
|
-
const err = new Error(`Could not reach github.com
|
|
25
|
+
const err = new Error(`Could not reach github.com - check your connection`);
|
|
26
26
|
err.kind = "network";
|
|
27
27
|
throw err;
|
|
28
28
|
}
|
|
@@ -147,8 +147,8 @@ export async function adaptWorkflowInteractive(workflow, options) {
|
|
|
147
147
|
const action = await p.select({
|
|
148
148
|
message: "Looks good?",
|
|
149
149
|
options: [
|
|
150
|
-
{ value: "accept", label: "Yes
|
|
151
|
-
{ value: "refine", label: "Refine
|
|
150
|
+
{ value: "accept", label: "Yes - use this workflow" },
|
|
151
|
+
{ value: "refine", label: "Refine - describe what to change" },
|
|
152
152
|
{ value: "cancel", label: "Cancel" },
|
|
153
153
|
],
|
|
154
154
|
});
|
package/dist/cli/new.js
CHANGED
|
@@ -328,7 +328,7 @@ export function inferObservability(skills) {
|
|
|
328
328
|
*/
|
|
329
329
|
export function buildSwenyYml(sourceControl, observability, issueTracker) {
|
|
330
330
|
const lines = [];
|
|
331
|
-
lines.push("# .sweny.yml
|
|
331
|
+
lines.push("# .sweny.yml - SWEny project configuration");
|
|
332
332
|
lines.push("# Secrets (API keys, tokens) go in .env (gitignored).");
|
|
333
333
|
lines.push("# Docs: https://docs.sweny.ai/cli");
|
|
334
334
|
lines.push("");
|
|
@@ -345,7 +345,7 @@ export function buildSwenyYml(sourceControl, observability, issueTracker) {
|
|
|
345
345
|
*/
|
|
346
346
|
export function buildEnvTemplate(credentials) {
|
|
347
347
|
const lines = [];
|
|
348
|
-
lines.push("# .env
|
|
348
|
+
lines.push("# .env - SWEny credentials (DO NOT COMMIT)");
|
|
349
349
|
lines.push("# Fill in each value, then run: sweny check");
|
|
350
350
|
lines.push("");
|
|
351
351
|
for (const cred of credentials) {
|
|
@@ -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");
|
|
@@ -743,7 +746,7 @@ export async function runNew(options) {
|
|
|
743
746
|
summaryLines.push("");
|
|
744
747
|
}
|
|
745
748
|
if (hasExistingConfig) {
|
|
746
|
-
summaryLines.push(chalk.dim("(.sweny.yml already exists
|
|
749
|
+
summaryLines.push(chalk.dim("(.sweny.yml already exists - keeping your existing config)"));
|
|
747
750
|
summaryLines.push("");
|
|
748
751
|
}
|
|
749
752
|
summaryLines.push(chalk.bold(hasExistingConfig ? "Workflow uses:" : "Inferred from workflow:"));
|
|
@@ -770,7 +773,7 @@ export async function runNew(options) {
|
|
|
770
773
|
p.log.success("Created .sweny.yml");
|
|
771
774
|
}
|
|
772
775
|
else {
|
|
773
|
-
p.log.info(".sweny.yml already exists
|
|
776
|
+
p.log.info(".sweny.yml already exists - keeping existing config");
|
|
774
777
|
}
|
|
775
778
|
// 2. Append missing .env keys
|
|
776
779
|
const envExistedBefore = fs.existsSync(path.join(cwd, ".env"));
|
|
@@ -783,7 +786,7 @@ export async function runNew(options) {
|
|
|
783
786
|
p.log.success(`Appended ${addedCount} new key(s) to .env`);
|
|
784
787
|
}
|
|
785
788
|
else {
|
|
786
|
-
p.log.info(".env already contains all required keys
|
|
789
|
+
p.log.info(".env already contains all required keys - skipped");
|
|
787
790
|
}
|
|
788
791
|
// The pack's Actions trigger (#474): never overwritten, skipped with --no-ci.
|
|
789
792
|
const installTrigger = () => {
|
|
@@ -812,7 +815,7 @@ export async function runNew(options) {
|
|
|
812
815
|
if (p.isCancel(overwrite))
|
|
813
816
|
cancel();
|
|
814
817
|
if (!overwrite) {
|
|
815
|
-
p.log.info("Workflow file preserved
|
|
818
|
+
p.log.info("Workflow file preserved - no changes");
|
|
816
819
|
p.outro("Done.");
|
|
817
820
|
return;
|
|
818
821
|
}
|
|
@@ -900,8 +903,8 @@ async function runCustomWorkflowBuilder(skills) {
|
|
|
900
903
|
const action = await p.select({
|
|
901
904
|
message: "Looks good?",
|
|
902
905
|
options: [
|
|
903
|
-
{ value: "accept", label: "Yes
|
|
904
|
-
{ value: "refine", label: "Refine
|
|
906
|
+
{ value: "accept", label: "Yes - use this workflow" },
|
|
907
|
+
{ value: "refine", label: "Refine - describe what to change" },
|
|
905
908
|
{ value: "cancel", label: "Cancel" },
|
|
906
909
|
],
|
|
907
910
|
});
|
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;
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { SPINNER_INTERVAL_MS } from "../theme.js";
|
|
11
|
+
import { glyphsFor, spinnerFramesFor } from "./terminal.js";
|
|
12
|
+
export function toolCallsText(n) {
|
|
13
|
+
return `${n} tool ${n === 1 ? "call" : "calls"}`;
|
|
14
|
+
}
|
|
15
|
+
export function createNodeProgress(o) {
|
|
16
|
+
const g = glyphsFor(o.unicode);
|
|
17
|
+
const frames = spinnerFramesFor(o.unicode);
|
|
18
|
+
const { paint } = o;
|
|
19
|
+
const pad = (id) => id.padEnd(o.idWidth ?? 0);
|
|
20
|
+
let timer;
|
|
21
|
+
let current;
|
|
22
|
+
let frame = 0;
|
|
23
|
+
const draw = () => {
|
|
24
|
+
if (!current)
|
|
25
|
+
return;
|
|
26
|
+
const calls = current.calls > 0 ? ` ${paint.muted(toolCallsText(current.calls))}` : "";
|
|
27
|
+
o.write(`\r\x1B[2K ${paint.brand(frames[frame++ % frames.length])} ${current.node}${calls}`);
|
|
28
|
+
};
|
|
29
|
+
const clear = () => {
|
|
30
|
+
if (timer)
|
|
31
|
+
clearInterval(timer);
|
|
32
|
+
timer = undefined;
|
|
33
|
+
if (current && o.live)
|
|
34
|
+
o.write("\r\x1B[2K");
|
|
35
|
+
current = undefined;
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
enter(node) {
|
|
39
|
+
clear();
|
|
40
|
+
if (o.live) {
|
|
41
|
+
current = { node, calls: 0 };
|
|
42
|
+
frame = 0;
|
|
43
|
+
draw();
|
|
44
|
+
timer = setInterval(draw, SPINNER_INTERVAL_MS);
|
|
45
|
+
timer.unref?.();
|
|
46
|
+
}
|
|
47
|
+
else if (o.announce) {
|
|
48
|
+
o.write(` ${paint.muted(g.pending)} ${paint.muted(node)}${g.ellipsis}\n`);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
tick(node) {
|
|
52
|
+
if (current && current.node === node)
|
|
53
|
+
current.calls++;
|
|
54
|
+
},
|
|
55
|
+
exit(node, status, detail) {
|
|
56
|
+
clear();
|
|
57
|
+
const glyph = status === "success"
|
|
58
|
+
? paint.success(g.success)
|
|
59
|
+
: status === "failed"
|
|
60
|
+
? paint.error(g.failure)
|
|
61
|
+
: paint.muted(g.skipped);
|
|
62
|
+
const tail = detail ? ` ${paint.muted(detail)}` : "";
|
|
63
|
+
o.write(` ${glyph} ${detail ? pad(node) : node}${tail}\n`);
|
|
64
|
+
},
|
|
65
|
+
stop() {
|
|
66
|
+
clear();
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
package/dist/cli/publish.js
CHANGED
|
@@ -216,7 +216,7 @@ async function publishWorkflow() {
|
|
|
216
216
|
p.log.error(`Validation failed:\n${validation.errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
217
217
|
return null;
|
|
218
218
|
}
|
|
219
|
-
p.log.success(`Validated: ${chalk.cyan(validation.name)}
|
|
219
|
+
p.log.success(`Validated: ${chalk.cyan(validation.name)} - ${validation.nodeCount} nodes, ${validation.edgeCount} edges`);
|
|
220
220
|
if (validation.warnings.length > 0) {
|
|
221
221
|
for (const warn of validation.warnings) {
|
|
222
222
|
p.log.warn(warn);
|
|
@@ -312,7 +312,7 @@ async function selectOutputMode(type, id, sourcePath, meta) {
|
|
|
312
312
|
const hasGh = ghAvailable();
|
|
313
313
|
const options = [
|
|
314
314
|
...(hasGh
|
|
315
|
-
? [{ value: "pr", label: "Open GitHub PR", hint: "recommended
|
|
315
|
+
? [{ value: "pr", label: "Open GitHub PR", hint: "recommended - forks marketplace repo and opens a PR" }]
|
|
316
316
|
: []),
|
|
317
317
|
{ value: "save", label: "Save submission file", hint: "saves to ./sweny-publish/" },
|
|
318
318
|
];
|