@sweny-ai/core 0.1.77 → 0.1.79

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.
Files changed (119) hide show
  1. package/dist/browser.d.ts +11 -5
  2. package/dist/browser.js +11 -11
  3. package/dist/claude.d.ts +16 -0
  4. package/dist/claude.js +98 -9
  5. package/dist/cli/config.d.ts +22 -0
  6. package/dist/cli/config.js +56 -9
  7. package/dist/cli/main.d.ts +1 -1
  8. package/dist/cli/main.js +56 -57
  9. package/dist/cli/marketplace.js +35 -9
  10. package/dist/cli/publish.d.ts +10 -0
  11. package/dist/cli/publish.js +22 -14
  12. package/dist/loader.d.ts +40 -0
  13. package/dist/loader.js +73 -0
  14. package/dist/mcp-catalog.d.ts +47 -0
  15. package/dist/mcp-catalog.js +304 -0
  16. package/dist/mcp.d.ts +6 -9
  17. package/dist/mcp.js +24 -314
  18. package/dist/schema.d.ts +183 -151
  19. package/dist/schema.js +49 -7
  20. package/dist/skills/custom-loader.d.ts +40 -0
  21. package/dist/skills/custom-loader.js +107 -32
  22. package/dist/skills/index.d.ts +13 -1
  23. package/dist/skills/index.js +12 -3
  24. package/dist/studio.js +4 -1
  25. package/dist/templates.d.ts +29 -8
  26. package/dist/templates.js +30 -20
  27. package/dist/types.d.ts +12 -0
  28. package/dist/verify.js +96 -7
  29. package/package.json +8 -4
  30. package/dist/__tests__/cascade.test.d.ts +0 -11
  31. package/dist/__tests__/cascade.test.js +0 -452
  32. package/dist/__tests__/claude.test.d.ts +0 -1
  33. package/dist/__tests__/claude.test.js +0 -427
  34. package/dist/__tests__/custom-loader.test.d.ts +0 -1
  35. package/dist/__tests__/custom-loader.test.js +0 -116
  36. package/dist/__tests__/executor.test.d.ts +0 -1
  37. package/dist/__tests__/executor.test.js +0 -1635
  38. package/dist/__tests__/integration/datadog.integration.test.d.ts +0 -1
  39. package/dist/__tests__/integration/datadog.integration.test.js +0 -23
  40. package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +0 -1
  41. package/dist/__tests__/integration/e2e-workflow.integration.test.js +0 -75
  42. package/dist/__tests__/integration/github.integration.test.d.ts +0 -1
  43. package/dist/__tests__/integration/github.integration.test.js +0 -37
  44. package/dist/__tests__/integration/harness.d.ts +0 -24
  45. package/dist/__tests__/integration/harness.js +0 -34
  46. package/dist/__tests__/integration/linear.integration.test.d.ts +0 -1
  47. package/dist/__tests__/integration/linear.integration.test.js +0 -15
  48. package/dist/__tests__/integration/sentry.integration.test.d.ts +0 -1
  49. package/dist/__tests__/integration/sentry.integration.test.js +0 -20
  50. package/dist/__tests__/integration/slack.integration.test.d.ts +0 -1
  51. package/dist/__tests__/integration/slack.integration.test.js +0 -22
  52. package/dist/__tests__/integration/sources.integration.test.d.ts +0 -1
  53. package/dist/__tests__/integration/sources.integration.test.js +0 -123
  54. package/dist/__tests__/publish.test.d.ts +0 -1
  55. package/dist/__tests__/publish.test.js +0 -180
  56. package/dist/__tests__/requires.test.d.ts +0 -1
  57. package/dist/__tests__/requires.test.js +0 -82
  58. package/dist/__tests__/retry.test.d.ts +0 -1
  59. package/dist/__tests__/retry.test.js +0 -213
  60. package/dist/__tests__/schema.test.d.ts +0 -1
  61. package/dist/__tests__/schema.test.js +0 -600
  62. package/dist/__tests__/skill-config-integration.test.d.ts +0 -1
  63. package/dist/__tests__/skill-config-integration.test.js +0 -204
  64. package/dist/__tests__/skills-index.test.d.ts +0 -1
  65. package/dist/__tests__/skills-index.test.js +0 -119
  66. package/dist/__tests__/skills.test.d.ts +0 -1
  67. package/dist/__tests__/skills.test.js +0 -565
  68. package/dist/__tests__/spec-conformance.test.d.ts +0 -18
  69. package/dist/__tests__/spec-conformance.test.js +0 -1084
  70. package/dist/__tests__/studio.test.d.ts +0 -1
  71. package/dist/__tests__/studio.test.js +0 -403
  72. package/dist/__tests__/templates.test.d.ts +0 -1
  73. package/dist/__tests__/templates.test.js +0 -49
  74. package/dist/__tests__/testing.test.d.ts +0 -1
  75. package/dist/__tests__/testing.test.js +0 -242
  76. package/dist/__tests__/verify.test.d.ts +0 -1
  77. package/dist/__tests__/verify.test.js +0 -517
  78. package/dist/__tests__/version-consistency.test.d.ts +0 -1
  79. package/dist/__tests__/version-consistency.test.js +0 -312
  80. package/dist/__tests__/workflow-builder.test.d.ts +0 -1
  81. package/dist/__tests__/workflow-builder.test.js +0 -110
  82. package/dist/__tests__/workflow-yaml.test.d.ts +0 -1
  83. package/dist/__tests__/workflow-yaml.test.js +0 -201
  84. package/dist/cli/__tests__/diagram.test.d.ts +0 -1
  85. package/dist/cli/__tests__/diagram.test.js +0 -225
  86. package/dist/cli/__tests__/report-cloud.test.d.ts +0 -1
  87. package/dist/cli/__tests__/report-cloud.test.js +0 -165
  88. package/dist/cli/__tests__/upgrade.test.d.ts +0 -1
  89. package/dist/cli/__tests__/upgrade.test.js +0 -317
  90. package/dist/cli/__tests__/version-check.test.d.ts +0 -1
  91. package/dist/cli/__tests__/version-check.test.js +0 -209
  92. package/dist/cli/config.test.d.ts +0 -1
  93. package/dist/cli/config.test.js +0 -200
  94. package/dist/cli/credentials.test.d.ts +0 -1
  95. package/dist/cli/credentials.test.js +0 -172
  96. package/dist/cli/e2e.test.d.ts +0 -1
  97. package/dist/cli/e2e.test.js +0 -690
  98. package/dist/cli/marketplace.test.d.ts +0 -1
  99. package/dist/cli/marketplace.test.js +0 -355
  100. package/dist/cli/new.test.d.ts +0 -1
  101. package/dist/cli/new.test.js +0 -1043
  102. package/dist/cli/output.test.d.ts +0 -1
  103. package/dist/cli/output.test.js +0 -180
  104. package/dist/cli/publish.test.d.ts +0 -1
  105. package/dist/cli/publish.test.js +0 -610
  106. package/dist/cli/renderer.test.d.ts +0 -1
  107. package/dist/cli/renderer.test.js +0 -302
  108. package/dist/executor.test.d.ts +0 -1
  109. package/dist/executor.test.js +0 -618
  110. package/dist/mcp.test.d.ts +0 -1
  111. package/dist/mcp.test.js +0 -735
  112. package/dist/mermaid.test.d.ts +0 -1
  113. package/dist/mermaid.test.js +0 -256
  114. package/dist/sources.test.d.ts +0 -1
  115. package/dist/sources.test.js +0 -265
  116. package/dist/sources.validation.test.d.ts +0 -12
  117. package/dist/sources.validation.test.js +0 -380
  118. package/dist/workflow-builder.test.d.ts +0 -1
  119. 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 {};