@sweny-ai/core 0.1.77 → 0.1.78
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/browser.d.ts +11 -5
- package/dist/browser.js +11 -11
- package/dist/claude.d.ts +16 -0
- package/dist/claude.js +98 -9
- package/dist/cli/config.d.ts +22 -0
- package/dist/cli/config.js +56 -9
- package/dist/cli/main.d.ts +1 -1
- package/dist/cli/main.js +56 -57
- package/dist/cli/marketplace.js +35 -9
- package/dist/cli/publish.d.ts +10 -0
- package/dist/cli/publish.js +22 -14
- package/dist/loader.d.ts +40 -0
- package/dist/loader.js +73 -0
- package/dist/mcp-catalog.d.ts +47 -0
- package/dist/mcp-catalog.js +304 -0
- package/dist/mcp.d.ts +6 -9
- package/dist/mcp.js +24 -314
- package/dist/schema.d.ts +183 -151
- package/dist/schema.js +49 -7
- package/dist/skills/custom-loader.d.ts +40 -0
- package/dist/skills/custom-loader.js +107 -32
- package/dist/skills/index.d.ts +13 -1
- package/dist/skills/index.js +12 -3
- package/dist/studio.js +4 -1
- package/dist/templates.d.ts +29 -8
- package/dist/templates.js +30 -20
- package/dist/types.d.ts +12 -0
- package/dist/verify.js +42 -4
- package/package.json +8 -4
- package/dist/__tests__/cascade.test.d.ts +0 -11
- package/dist/__tests__/cascade.test.js +0 -452
- package/dist/__tests__/claude.test.d.ts +0 -1
- package/dist/__tests__/claude.test.js +0 -427
- package/dist/__tests__/custom-loader.test.d.ts +0 -1
- package/dist/__tests__/custom-loader.test.js +0 -116
- package/dist/__tests__/executor.test.d.ts +0 -1
- package/dist/__tests__/executor.test.js +0 -1635
- package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/datadog.integration.test.js +0 -23
- package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
- package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/github.integration.test.js +0 -37
- package/dist/__tests__/integration/harness.d.ts +0 -24
- package/dist/__tests__/integration/harness.js +0 -34
- package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/linear.integration.test.js +0 -15
- package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sentry.integration.test.js +0 -20
- package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/slack.integration.test.js +0 -22
- package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
- package/dist/__tests__/integration/sources.integration.test.js +0 -123
- package/dist/__tests__/publish.test.d.ts +0 -1
- package/dist/__tests__/publish.test.js +0 -180
- package/dist/__tests__/requires.test.d.ts +0 -1
- package/dist/__tests__/requires.test.js +0 -82
- package/dist/__tests__/retry.test.d.ts +0 -1
- package/dist/__tests__/retry.test.js +0 -213
- package/dist/__tests__/schema.test.d.ts +0 -1
- package/dist/__tests__/schema.test.js +0 -600
- package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
- package/dist/__tests__/skill-config-integration.test.js +0 -204
- package/dist/__tests__/skills-index.test.d.ts +0 -1
- package/dist/__tests__/skills-index.test.js +0 -119
- package/dist/__tests__/skills.test.d.ts +0 -1
- package/dist/__tests__/skills.test.js +0 -565
- package/dist/__tests__/spec-conformance.test.d.ts +0 -18
- package/dist/__tests__/spec-conformance.test.js +0 -1084
- package/dist/__tests__/studio.test.d.ts +0 -1
- package/dist/__tests__/studio.test.js +0 -403
- package/dist/__tests__/templates.test.d.ts +0 -1
- package/dist/__tests__/templates.test.js +0 -49
- package/dist/__tests__/testing.test.d.ts +0 -1
- package/dist/__tests__/testing.test.js +0 -242
- package/dist/__tests__/verify.test.d.ts +0 -1
- package/dist/__tests__/verify.test.js +0 -517
- package/dist/__tests__/version-consistency.test.d.ts +0 -1
- package/dist/__tests__/version-consistency.test.js +0 -312
- package/dist/__tests__/workflow-builder.test.d.ts +0 -1
- package/dist/__tests__/workflow-builder.test.js +0 -110
- package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
- package/dist/__tests__/workflow-yaml.test.js +0 -201
- package/dist/cli/__tests__/diagram.test.d.ts +0 -1
- package/dist/cli/__tests__/diagram.test.js +0 -225
- package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
- package/dist/cli/__tests__/report-cloud.test.js +0 -165
- package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
- package/dist/cli/__tests__/upgrade.test.js +0 -317
- package/dist/cli/__tests__/version-check.test.d.ts +0 -1
- package/dist/cli/__tests__/version-check.test.js +0 -209
- package/dist/cli/config.test.d.ts +0 -1
- package/dist/cli/config.test.js +0 -200
- package/dist/cli/credentials.test.d.ts +0 -1
- package/dist/cli/credentials.test.js +0 -172
- package/dist/cli/e2e.test.d.ts +0 -1
- package/dist/cli/e2e.test.js +0 -690
- package/dist/cli/marketplace.test.d.ts +0 -1
- package/dist/cli/marketplace.test.js +0 -355
- package/dist/cli/new.test.d.ts +0 -1
- package/dist/cli/new.test.js +0 -1043
- package/dist/cli/output.test.d.ts +0 -1
- package/dist/cli/output.test.js +0 -180
- package/dist/cli/publish.test.d.ts +0 -1
- package/dist/cli/publish.test.js +0 -610
- package/dist/cli/renderer.test.d.ts +0 -1
- package/dist/cli/renderer.test.js +0 -302
- package/dist/executor.test.d.ts +0 -1
- package/dist/executor.test.js +0 -618
- package/dist/mcp.test.d.ts +0 -1
- package/dist/mcp.test.js +0 -735
- package/dist/mermaid.test.d.ts +0 -1
- package/dist/mermaid.test.js +0 -256
- package/dist/sources.test.d.ts +0 -1
- package/dist/sources.test.js +0 -265
- package/dist/sources.validation.test.d.ts +0 -12
- package/dist/sources.validation.test.js +0 -380
- package/dist/workflow-builder.test.d.ts +0 -1
- package/dist/workflow-builder.test.js +0 -123
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import * as fs from "node:fs";
|
|
3
|
-
import * as os from "node:os";
|
|
4
|
-
import * as path from "node:path";
|
|
5
|
-
import { runWorkflowDiagram, resolveUseBlock } from "../diagram.js";
|
|
6
|
-
const sampleWorkflow = {
|
|
7
|
-
id: "sample",
|
|
8
|
-
name: "Sample",
|
|
9
|
-
description: "sample",
|
|
10
|
-
entry: "a",
|
|
11
|
-
nodes: {
|
|
12
|
-
a: { name: "A", instruction: "do a", skills: [] },
|
|
13
|
-
b: { name: "B", instruction: "do b", skills: [] },
|
|
14
|
-
},
|
|
15
|
-
edges: [{ from: "a", to: "b" }],
|
|
16
|
-
};
|
|
17
|
-
// Minimal in-memory writable stream that accumulates text. Avoids the
|
|
18
|
-
// overhead of real Node streams for these pure-output assertions.
|
|
19
|
-
function captureStream() {
|
|
20
|
-
const chunks = [];
|
|
21
|
-
const stream = {
|
|
22
|
-
write: (chunk) => {
|
|
23
|
-
chunks.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
|
24
|
-
return true;
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
return {
|
|
28
|
-
stream,
|
|
29
|
-
get text() {
|
|
30
|
-
return chunks.join("");
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
describe("resolveUseBlock", () => {
|
|
35
|
-
// Centralized behavior test — the renderer is a tiny switch around this
|
|
36
|
-
// so keeping the rules here as one table makes the policy easy to audit.
|
|
37
|
-
it("returns false by default (stdout, no -o, no flags)", () => {
|
|
38
|
-
expect(resolveUseBlock({})).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
it("returns true when --block is explicit", () => {
|
|
41
|
-
expect(resolveUseBlock({ block: true })).toBe(true);
|
|
42
|
-
});
|
|
43
|
-
it("returns false when --no-block is explicit, even for .md", () => {
|
|
44
|
-
expect(resolveUseBlock({ block: false, output: "diagram.md" })).toBe(false);
|
|
45
|
-
});
|
|
46
|
-
it("infers true for .md output", () => {
|
|
47
|
-
expect(resolveUseBlock({ output: "diagram.md" })).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
it("infers true for .markdown output", () => {
|
|
50
|
-
expect(resolveUseBlock({ output: "diagram.markdown" })).toBe(true);
|
|
51
|
-
});
|
|
52
|
-
it("infers false for .mmd output", () => {
|
|
53
|
-
expect(resolveUseBlock({ output: "diagram.mmd" })).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
it("infers false for .mermaid output", () => {
|
|
56
|
-
expect(resolveUseBlock({ output: "diagram.mermaid" })).toBe(false);
|
|
57
|
-
});
|
|
58
|
-
it("infers false for any other extension (.txt, no ext, etc.)", () => {
|
|
59
|
-
expect(resolveUseBlock({ output: "diagram.txt" })).toBe(false);
|
|
60
|
-
expect(resolveUseBlock({ output: "diagram" })).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
it("is case-insensitive on extension", () => {
|
|
63
|
-
expect(resolveUseBlock({ output: "Diagram.MD" })).toBe(true);
|
|
64
|
-
expect(resolveUseBlock({ output: "Diagram.MMD" })).toBe(false);
|
|
65
|
-
});
|
|
66
|
-
it("lets explicit --block override .mmd extension", () => {
|
|
67
|
-
expect(resolveUseBlock({ block: true, output: "diagram.mmd" })).toBe(true);
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
describe("runWorkflowDiagram", () => {
|
|
71
|
-
let tmpdir;
|
|
72
|
-
const loadWorkflowFile = vi.fn((_f) => sampleWorkflow);
|
|
73
|
-
const exit = vi.fn();
|
|
74
|
-
beforeEach(() => {
|
|
75
|
-
tmpdir = fs.mkdtempSync(path.join(os.tmpdir(), "sweny-diagram-test-"));
|
|
76
|
-
loadWorkflowFile.mockClear();
|
|
77
|
-
exit.mockClear();
|
|
78
|
-
});
|
|
79
|
-
afterEach(() => {
|
|
80
|
-
fs.rmSync(tmpdir, { recursive: true, force: true });
|
|
81
|
-
});
|
|
82
|
-
describe("stdout — default behavior", () => {
|
|
83
|
-
it("writes RAW mermaid (no fence, no title frontmatter) by default", () => {
|
|
84
|
-
// This is the Mermaid-CLI / Live-Editor-compatible output. No ```,
|
|
85
|
-
// no `---\ntitle: …\n---` — just `graph TB` and the nodes.
|
|
86
|
-
const stdout = captureStream();
|
|
87
|
-
const stderr = captureStream();
|
|
88
|
-
runWorkflowDiagram("any.yml", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: stderr.stream, exit });
|
|
89
|
-
expect(stdout.text).not.toMatch(/```mermaid/);
|
|
90
|
-
expect(stdout.text).not.toMatch(/^---\ntitle:/);
|
|
91
|
-
expect(stdout.text).toMatch(/^graph TB/);
|
|
92
|
-
expect(exit).not.toHaveBeenCalled();
|
|
93
|
-
});
|
|
94
|
-
it("wraps in a ```mermaid fence when --block is explicit", () => {
|
|
95
|
-
const stdout = captureStream();
|
|
96
|
-
runWorkflowDiagram("any.yml", { block: true }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
97
|
-
expect(stdout.text).toMatch(/^```mermaid\n/);
|
|
98
|
-
expect(stdout.text).toMatch(/\n```\n$/);
|
|
99
|
-
});
|
|
100
|
-
it("honors --direction LR", () => {
|
|
101
|
-
const stdout = captureStream();
|
|
102
|
-
runWorkflowDiagram("any.yml", { direction: "LR" }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
103
|
-
expect(stdout.text).toMatch(/graph LR/);
|
|
104
|
-
});
|
|
105
|
-
it("emits --title as frontmatter only when explicitly set", () => {
|
|
106
|
-
const stdout = captureStream();
|
|
107
|
-
runWorkflowDiagram("any.yml", { title: "Custom Title" }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
108
|
-
expect(stdout.text).toMatch(/^---\ntitle: Custom Title\n---\n/);
|
|
109
|
-
expect(stdout.text).toContain("graph TB");
|
|
110
|
-
});
|
|
111
|
-
it("does NOT default title to the workflow name", () => {
|
|
112
|
-
// Regression guard: previously we auto-filled `title` from workflow.name,
|
|
113
|
-
// which broke mmdc / mermaid-live because they don't accept title frontmatter.
|
|
114
|
-
const stdout = captureStream();
|
|
115
|
-
runWorkflowDiagram("any.yml", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
116
|
-
expect(stdout.text).not.toMatch(/title:/);
|
|
117
|
-
expect(stdout.text).not.toContain("Sample"); // workflow.name
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
describe("-o / --output file", () => {
|
|
121
|
-
it("writes raw mermaid to .mmd (default)", () => {
|
|
122
|
-
const out = path.join(tmpdir, "diagram.mmd");
|
|
123
|
-
const stderr = captureStream();
|
|
124
|
-
runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: stderr.stream, exit });
|
|
125
|
-
const contents = fs.readFileSync(out, "utf8");
|
|
126
|
-
expect(contents).not.toMatch(/```mermaid/);
|
|
127
|
-
expect(contents).not.toMatch(/title:/);
|
|
128
|
-
expect(contents).toMatch(/^graph TB/);
|
|
129
|
-
expect(stderr.text).toMatch(/✓ Wrote diagram to/);
|
|
130
|
-
});
|
|
131
|
-
it("writes raw mermaid to .mermaid (Mermaid CLI's canonical extension)", () => {
|
|
132
|
-
const out = path.join(tmpdir, "diagram.mermaid");
|
|
133
|
-
runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
|
|
134
|
-
const contents = fs.readFileSync(out, "utf8");
|
|
135
|
-
expect(contents).not.toMatch(/```mermaid/);
|
|
136
|
-
expect(contents).toMatch(/^graph TB/);
|
|
137
|
-
});
|
|
138
|
-
it("writes fenced markdown to .md", () => {
|
|
139
|
-
const out = path.join(tmpdir, "diagram.md");
|
|
140
|
-
runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
|
|
141
|
-
const contents = fs.readFileSync(out, "utf8");
|
|
142
|
-
expect(contents).toMatch(/^```mermaid\n/);
|
|
143
|
-
expect(contents).toMatch(/\n```\n$/);
|
|
144
|
-
});
|
|
145
|
-
it("writes raw mermaid for unknown extensions (no longer falls back to fenced)", () => {
|
|
146
|
-
// Behavior change: previously .txt and friends got fenced as the
|
|
147
|
-
// "safe default". Now raw mermaid is the safe default because that's
|
|
148
|
-
// what every renderer actually accepts.
|
|
149
|
-
const out = path.join(tmpdir, "diagram.txt");
|
|
150
|
-
runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
|
|
151
|
-
const contents = fs.readFileSync(out, "utf8");
|
|
152
|
-
expect(contents).not.toMatch(/```mermaid/);
|
|
153
|
-
expect(contents).toMatch(/graph TB/);
|
|
154
|
-
});
|
|
155
|
-
it("creates parent directories as needed (-o nested/subdir/file.mmd)", () => {
|
|
156
|
-
const out = path.join(tmpdir, "a", "b", "c", "diagram.mmd");
|
|
157
|
-
runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
|
|
158
|
-
expect(fs.existsSync(out)).toBe(true);
|
|
159
|
-
expect(fs.readFileSync(out, "utf8")).toMatch(/graph TB/);
|
|
160
|
-
});
|
|
161
|
-
it("does not write anything to stdout when -o is set", () => {
|
|
162
|
-
const stdout = captureStream();
|
|
163
|
-
const out = path.join(tmpdir, "diagram.mmd");
|
|
164
|
-
runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
165
|
-
expect(stdout.text).toBe("");
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
describe("explicit --block / --no-block override extension inference", () => {
|
|
169
|
-
it("explicit --block wins over .mmd (writes fenced to .mmd)", () => {
|
|
170
|
-
const out = path.join(tmpdir, "diagram.mmd");
|
|
171
|
-
runWorkflowDiagram("any.yml", { output: out, block: true }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
|
|
172
|
-
expect(fs.readFileSync(out, "utf8")).toMatch(/```mermaid/);
|
|
173
|
-
});
|
|
174
|
-
it("explicit --no-block wins over .md (writes raw to .md)", () => {
|
|
175
|
-
const out = path.join(tmpdir, "diagram.md");
|
|
176
|
-
runWorkflowDiagram("any.yml", { output: out, block: false }, { loadWorkflowFile, stdout: captureStream().stream, stderr: captureStream().stream, exit });
|
|
177
|
-
expect(fs.readFileSync(out, "utf8")).not.toMatch(/```mermaid/);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
describe("builtin workflow names", () => {
|
|
181
|
-
it("resolves 'triage' without calling loadWorkflowFile", () => {
|
|
182
|
-
const stdout = captureStream();
|
|
183
|
-
runWorkflowDiagram("triage", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
184
|
-
expect(loadWorkflowFile).not.toHaveBeenCalled();
|
|
185
|
-
expect(stdout.text).toMatch(/graph TB/);
|
|
186
|
-
// Confirm built-in renders raw by default too.
|
|
187
|
-
expect(stdout.text).not.toMatch(/```mermaid/);
|
|
188
|
-
});
|
|
189
|
-
it("resolves 'implement' without calling loadWorkflowFile", () => {
|
|
190
|
-
const stdout = captureStream();
|
|
191
|
-
runWorkflowDiagram("implement", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
192
|
-
expect(loadWorkflowFile).not.toHaveBeenCalled();
|
|
193
|
-
expect(stdout.text.length).toBeGreaterThan(0);
|
|
194
|
-
});
|
|
195
|
-
it("resolves 'seed-content' without calling loadWorkflowFile", () => {
|
|
196
|
-
const stdout = captureStream();
|
|
197
|
-
runWorkflowDiagram("seed-content", {}, { loadWorkflowFile, stdout: stdout.stream, stderr: captureStream().stream, exit });
|
|
198
|
-
expect(loadWorkflowFile).not.toHaveBeenCalled();
|
|
199
|
-
expect(stdout.text.length).toBeGreaterThan(0);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
describe("error handling", () => {
|
|
203
|
-
it("exits with code 1 and prints an error when loadWorkflowFile throws", () => {
|
|
204
|
-
const stderr = captureStream();
|
|
205
|
-
const throwingLoader = vi.fn(() => {
|
|
206
|
-
throw new Error("file not found: missing.yml");
|
|
207
|
-
});
|
|
208
|
-
runWorkflowDiagram("missing.yml", {}, { loadWorkflowFile: throwingLoader, stdout: captureStream().stream, stderr: stderr.stream, exit });
|
|
209
|
-
expect(exit).toHaveBeenCalledWith(1);
|
|
210
|
-
expect(stderr.text).toMatch(/file not found/);
|
|
211
|
-
});
|
|
212
|
-
it("exits with code 1 when the output path is unwritable", () => {
|
|
213
|
-
// Point -o at a path where the parent is a file (not a dir). mkdirSync
|
|
214
|
-
// with recursive:true will throw EEXIST, writeFileSync then fails. We
|
|
215
|
-
// specifically want to assert the error path doesn't crash the process.
|
|
216
|
-
const blocker = path.join(tmpdir, "blocker");
|
|
217
|
-
fs.writeFileSync(blocker, "im-a-file");
|
|
218
|
-
const out = path.join(blocker, "diagram.mmd");
|
|
219
|
-
const stderr = captureStream();
|
|
220
|
-
runWorkflowDiagram("any.yml", { output: out }, { loadWorkflowFile, stdout: captureStream().stream, stderr: stderr.stream, exit });
|
|
221
|
-
expect(exit).toHaveBeenCalledWith(1);
|
|
222
|
-
expect(stderr.text).toMatch(/Error:/);
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
describe("reportToCloud", () => {
|
|
3
|
-
let reportToCloud;
|
|
4
|
-
const fetchMock = vi.fn();
|
|
5
|
-
beforeEach(async () => {
|
|
6
|
-
fetchMock.mockReset();
|
|
7
|
-
fetchMock.mockResolvedValue(new Response(JSON.stringify({ run_url: "https://cloud.sweny.ai/runs/abc" }), { status: 200 }));
|
|
8
|
-
vi.spyOn(globalThis, "fetch").mockImplementation(fetchMock);
|
|
9
|
-
// Dynamic import to handle any module-level side effects
|
|
10
|
-
const mod = await import("../cloud-report.js");
|
|
11
|
-
reportToCloud = mod.reportToCloud;
|
|
12
|
-
});
|
|
13
|
-
afterEach(() => {
|
|
14
|
-
vi.restoreAllMocks();
|
|
15
|
-
});
|
|
16
|
-
function makeConfig(overrides = {}) {
|
|
17
|
-
return {
|
|
18
|
-
cloudToken: "",
|
|
19
|
-
githubToken: "",
|
|
20
|
-
repository: "acme/widget",
|
|
21
|
-
codingAgentProvider: "claude",
|
|
22
|
-
anthropicApiKey: "",
|
|
23
|
-
claudeOauthToken: "",
|
|
24
|
-
openaiApiKey: "",
|
|
25
|
-
geminiApiKey: "",
|
|
26
|
-
observabilityProviders: [],
|
|
27
|
-
observabilityCredentials: {},
|
|
28
|
-
issueTrackerProvider: "",
|
|
29
|
-
linearApiKey: "",
|
|
30
|
-
linearTeamId: "",
|
|
31
|
-
linearBugLabelId: "",
|
|
32
|
-
linearTriageLabelId: "",
|
|
33
|
-
linearStateBacklog: "",
|
|
34
|
-
linearStateInProgress: "",
|
|
35
|
-
linearStatePeerReview: "",
|
|
36
|
-
timeRange: "",
|
|
37
|
-
severityFocus: "",
|
|
38
|
-
serviceFilter: "",
|
|
39
|
-
investigationDepth: "",
|
|
40
|
-
maxInvestigateTurns: 50,
|
|
41
|
-
maxImplementTurns: 40,
|
|
42
|
-
baseBranch: "main",
|
|
43
|
-
prLabels: [],
|
|
44
|
-
issueLabels: [],
|
|
45
|
-
dryRun: false,
|
|
46
|
-
reviewMode: "review",
|
|
47
|
-
noveltyMode: false,
|
|
48
|
-
issueOverride: "",
|
|
49
|
-
additionalInstructions: "",
|
|
50
|
-
serviceMapPath: "",
|
|
51
|
-
botToken: "",
|
|
52
|
-
sourceControlProvider: "",
|
|
53
|
-
jiraBaseUrl: "",
|
|
54
|
-
jiraEmail: "",
|
|
55
|
-
jiraApiToken: "",
|
|
56
|
-
gitlabToken: "",
|
|
57
|
-
gitlabProjectId: "",
|
|
58
|
-
gitlabBaseUrl: "",
|
|
59
|
-
notificationProvider: "",
|
|
60
|
-
notificationWebhookUrl: "",
|
|
61
|
-
sendgridApiKey: "",
|
|
62
|
-
emailFrom: "",
|
|
63
|
-
emailTo: "",
|
|
64
|
-
webhookSigningSecret: "",
|
|
65
|
-
repositoryOwner: "",
|
|
66
|
-
json: false,
|
|
67
|
-
stream: false,
|
|
68
|
-
bell: false,
|
|
69
|
-
cacheDir: "",
|
|
70
|
-
cacheTtl: 0,
|
|
71
|
-
noCache: false,
|
|
72
|
-
outputDir: "",
|
|
73
|
-
mcpServers: {},
|
|
74
|
-
workspaceTools: [],
|
|
75
|
-
rules: [],
|
|
76
|
-
context: [],
|
|
77
|
-
offline: false,
|
|
78
|
-
fetchAuth: {},
|
|
79
|
-
...overrides,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function makeResults() {
|
|
83
|
-
return new Map([
|
|
84
|
-
[
|
|
85
|
-
"investigate",
|
|
86
|
-
{
|
|
87
|
-
status: "success",
|
|
88
|
-
data: { findings: [{ id: "F1" }], recommendation: "implement" },
|
|
89
|
-
toolCalls: [],
|
|
90
|
-
},
|
|
91
|
-
],
|
|
92
|
-
[
|
|
93
|
-
"create_pr",
|
|
94
|
-
{
|
|
95
|
-
status: "success",
|
|
96
|
-
data: { prUrl: "https://github.com/acme/widget/pull/7", prNumber: 7 },
|
|
97
|
-
toolCalls: [],
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
]);
|
|
101
|
-
}
|
|
102
|
-
it("does NOT call fetch when cloudToken is empty", async () => {
|
|
103
|
-
await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "" }), "triage");
|
|
104
|
-
expect(fetchMock).not.toHaveBeenCalled();
|
|
105
|
-
});
|
|
106
|
-
it("does NOT call fetch when repository is missing", async () => {
|
|
107
|
-
const orig = process.env.GITHUB_REPOSITORY;
|
|
108
|
-
delete process.env.GITHUB_REPOSITORY;
|
|
109
|
-
try {
|
|
110
|
-
await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_x", repository: "" }), "triage");
|
|
111
|
-
expect(fetchMock).not.toHaveBeenCalled();
|
|
112
|
-
}
|
|
113
|
-
finally {
|
|
114
|
-
if (orig !== undefined)
|
|
115
|
-
process.env.GITHUB_REPOSITORY = orig;
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
it("calls fetch with Bearer cloudToken when token is set", async () => {
|
|
119
|
-
await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
|
|
120
|
-
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
121
|
-
const [, init] = fetchMock.mock.calls[0];
|
|
122
|
-
expect(init.headers.Authorization).toBe("Bearer sweny_pk_abc");
|
|
123
|
-
});
|
|
124
|
-
it("does NOT send GITHUB_TOKEN in Authorization header", async () => {
|
|
125
|
-
await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc", githubToken: "ghs_evil" }), "triage");
|
|
126
|
-
const [, init] = fetchMock.mock.calls[0];
|
|
127
|
-
expect(init.headers.Authorization).not.toContain("ghs_evil");
|
|
128
|
-
expect(init.headers.Authorization).not.toMatch(/^token /);
|
|
129
|
-
});
|
|
130
|
-
it("posts to SWENY_CLOUD_URL override when set", async () => {
|
|
131
|
-
process.env.SWENY_CLOUD_URL = "https://cloud.example.test";
|
|
132
|
-
try {
|
|
133
|
-
await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
|
|
134
|
-
const [url] = fetchMock.mock.calls[0];
|
|
135
|
-
expect(url).toBe("https://cloud.example.test/api/report");
|
|
136
|
-
}
|
|
137
|
-
finally {
|
|
138
|
-
delete process.env.SWENY_CLOUD_URL;
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
it("silently swallows network failures", async () => {
|
|
142
|
-
fetchMock.mockRejectedValue(new Error("ECONNREFUSED"));
|
|
143
|
-
await expect(reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage")).resolves.toBeUndefined();
|
|
144
|
-
});
|
|
145
|
-
it("prints the run URL when response includes one", async () => {
|
|
146
|
-
const logSpy = vi.spyOn(console, "log").mockImplementation(() => { });
|
|
147
|
-
await reportToCloud(makeResults(), 1000, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
|
|
148
|
-
const output = logSpy.mock.calls.flat().join(" ");
|
|
149
|
-
expect(output).toContain("cloud.sweny.ai/runs/abc");
|
|
150
|
-
logSpy.mockRestore();
|
|
151
|
-
});
|
|
152
|
-
it("payload contains owner, repo, workflow, duration_ms, findings, nodes", async () => {
|
|
153
|
-
await reportToCloud(makeResults(), 1234, makeConfig({ cloudToken: "sweny_pk_abc" }), "triage");
|
|
154
|
-
const [, init] = fetchMock.mock.calls[0];
|
|
155
|
-
const body = JSON.parse(init.body);
|
|
156
|
-
expect(body).toMatchObject({
|
|
157
|
-
owner: "acme",
|
|
158
|
-
repo: "widget",
|
|
159
|
-
workflow: "triage",
|
|
160
|
-
duration_ms: 1234,
|
|
161
|
-
});
|
|
162
|
-
expect(Array.isArray(body.findings)).toBe(true);
|
|
163
|
-
expect(Array.isArray(body.nodes)).toBe(true);
|
|
164
|
-
});
|
|
165
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|