@sweny-ai/core 0.1.63 → 0.1.64
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/main.js +4 -4
- package/dist/cli/marketplace.d.ts +103 -0
- package/dist/cli/marketplace.js +290 -0
- package/dist/cli/marketplace.test.d.ts +1 -0
- package/dist/cli/marketplace.test.js +355 -0
- package/dist/cli/new.d.ts +35 -2
- package/dist/cli/new.js +156 -51
- package/dist/cli/new.test.js +215 -2
- package/dist/executor.js +1 -1
- package/dist/source-resolver.d.ts +22 -0
- package/dist/source-resolver.js +174 -0
- package/dist/sources.d.ts +9 -6
- package/dist/sources.js +13 -146
- package/dist/sources.test.js +2 -1
- package/dist/sources.validation.test.js +2 -1
- package/dist/templates.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import * as os from "node:os";
|
|
5
|
+
import { fetchMarketplaceWorkflow, fetchMarketplaceIndex, computeProviderMismatch, buildAdaptPrompt, adaptWorkflowInteractive, installMarketplaceWorkflow, MARKETPLACE_RAW_BASE, } from "./marketplace.js";
|
|
6
|
+
vi.mock("@clack/prompts", () => ({
|
|
7
|
+
select: vi.fn(),
|
|
8
|
+
text: vi.fn(),
|
|
9
|
+
spinner: () => ({ start: vi.fn(), stop: vi.fn() }),
|
|
10
|
+
log: { message: vi.fn(), error: vi.fn() },
|
|
11
|
+
isCancel: (v) => v === Symbol.for("cancel"),
|
|
12
|
+
}));
|
|
13
|
+
vi.mock("../workflow-builder.js", () => ({
|
|
14
|
+
refineWorkflow: vi.fn(),
|
|
15
|
+
}));
|
|
16
|
+
describe("fetchMarketplaceWorkflow", () => {
|
|
17
|
+
let fetchSpy;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
|
|
20
|
+
return new Response("id: pr-review\nname: PR Review\n", { status: 200 });
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
afterEach(() => fetchSpy.mockRestore());
|
|
24
|
+
it("fetches workflow YAML from raw GitHub", async () => {
|
|
25
|
+
const result = await fetchMarketplaceWorkflow("pr-review");
|
|
26
|
+
expect(result.id).toBe("pr-review");
|
|
27
|
+
expect(result.yaml).toContain("id: pr-review");
|
|
28
|
+
expect(fetchSpy).toHaveBeenCalledWith(`${MARKETPLACE_RAW_BASE}/workflows/pr-review.yml`);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
describe("fetchMarketplaceWorkflow errors", () => {
|
|
32
|
+
let fetchSpy;
|
|
33
|
+
afterEach(() => fetchSpy?.mockRestore());
|
|
34
|
+
it("throws not-found on 404", async () => {
|
|
35
|
+
fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response("", { status: 404 }));
|
|
36
|
+
await expect(fetchMarketplaceWorkflow("missing")).rejects.toMatchObject({
|
|
37
|
+
kind: "not-found",
|
|
38
|
+
message: expect.stringContaining("missing"),
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it("throws rate-limit on 403 with X-RateLimit-Remaining: 0", async () => {
|
|
42
|
+
fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response("", {
|
|
43
|
+
status: 403,
|
|
44
|
+
headers: { "X-RateLimit-Remaining": "0", "X-RateLimit-Reset": "1234567890" },
|
|
45
|
+
}));
|
|
46
|
+
await expect(fetchMarketplaceWorkflow("pr-review")).rejects.toMatchObject({
|
|
47
|
+
kind: "rate-limit",
|
|
48
|
+
retryAfter: 1234567890,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
it("throws network error when fetch rejects", async () => {
|
|
52
|
+
fetchSpy = vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
|
|
53
|
+
throw new TypeError("fetch failed");
|
|
54
|
+
});
|
|
55
|
+
await expect(fetchMarketplaceWorkflow("pr-review")).rejects.toMatchObject({
|
|
56
|
+
kind: "network",
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("fetchMarketplaceIndex", () => {
|
|
61
|
+
afterEach(() => vi.restoreAllMocks());
|
|
62
|
+
it("fetches and parses index.json", async () => {
|
|
63
|
+
const body = JSON.stringify([
|
|
64
|
+
{ id: "pr-review", name: "PR Review", description: "Reviews PRs", skills: ["github"] },
|
|
65
|
+
{ id: "issue-triage", name: "Issue Triage", description: "Triages issues", skills: ["github"] },
|
|
66
|
+
]);
|
|
67
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(body, { status: 200 }));
|
|
68
|
+
const entries = await fetchMarketplaceIndex();
|
|
69
|
+
expect(entries).toHaveLength(2);
|
|
70
|
+
expect(entries[0]).toMatchObject({ id: "pr-review", name: "PR Review" });
|
|
71
|
+
});
|
|
72
|
+
it("throws not-found when index.json is missing", async () => {
|
|
73
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response("", { status: 404 }));
|
|
74
|
+
await expect(fetchMarketplaceIndex()).rejects.toMatchObject({ kind: "not-found" });
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
const testSkills = [
|
|
78
|
+
{ id: "linear", name: "Linear", description: "", category: "tasks", config: {}, tools: [] },
|
|
79
|
+
{
|
|
80
|
+
id: "github-issues",
|
|
81
|
+
name: "GitHub Issues",
|
|
82
|
+
description: "",
|
|
83
|
+
category: "tasks",
|
|
84
|
+
config: {},
|
|
85
|
+
tools: [],
|
|
86
|
+
},
|
|
87
|
+
{ id: "github", name: "GitHub", description: "", category: "git", config: {}, tools: [] },
|
|
88
|
+
{
|
|
89
|
+
id: "datadog",
|
|
90
|
+
name: "Datadog",
|
|
91
|
+
description: "",
|
|
92
|
+
category: "observability",
|
|
93
|
+
config: {},
|
|
94
|
+
tools: [],
|
|
95
|
+
},
|
|
96
|
+
{ id: "sentry", name: "Sentry", description: "", category: "observability", config: {}, tools: [] },
|
|
97
|
+
];
|
|
98
|
+
describe("computeProviderMismatch", () => {
|
|
99
|
+
it("returns empty array when all workflow skills match config", () => {
|
|
100
|
+
const mismatches = computeProviderMismatch(["github", "github-issues"], { "source-control-provider": "github", "issue-tracker-provider": "github-issues" }, testSkills);
|
|
101
|
+
expect(mismatches).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
it("detects tasks mismatch (workflow=linear, config=github-issues)", () => {
|
|
104
|
+
const mismatches = computeProviderMismatch(["linear"], { "issue-tracker-provider": "github-issues" }, testSkills);
|
|
105
|
+
expect(mismatches).toEqual([
|
|
106
|
+
{
|
|
107
|
+
category: "tasks",
|
|
108
|
+
configKey: "issue-tracker-provider",
|
|
109
|
+
workflowSkill: "linear",
|
|
110
|
+
userProvider: "github-issues",
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
it("detects observability mismatch and ignores general-category skills", () => {
|
|
115
|
+
const mismatches = computeProviderMismatch(["datadog"], { "observability-provider": "sentry" }, testSkills);
|
|
116
|
+
expect(mismatches).toEqual([
|
|
117
|
+
{
|
|
118
|
+
category: "observability",
|
|
119
|
+
configKey: "observability-provider",
|
|
120
|
+
workflowSkill: "datadog",
|
|
121
|
+
userProvider: "sentry",
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
});
|
|
125
|
+
it("skips mismatch when the user has no provider set for that role", () => {
|
|
126
|
+
const mismatches = computeProviderMismatch(["linear"], {}, testSkills);
|
|
127
|
+
expect(mismatches).toEqual([]);
|
|
128
|
+
});
|
|
129
|
+
it("ignores workflow skills not in the available skill registry", () => {
|
|
130
|
+
const mismatches = computeProviderMismatch(["unknown-skill"], { "issue-tracker-provider": "github-issues" }, testSkills);
|
|
131
|
+
expect(mismatches).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
describe("buildAdaptPrompt", () => {
|
|
135
|
+
it("describes single mismatch", () => {
|
|
136
|
+
const prompt = buildAdaptPrompt([
|
|
137
|
+
{
|
|
138
|
+
category: "tasks",
|
|
139
|
+
configKey: "issue-tracker-provider",
|
|
140
|
+
workflowSkill: "linear",
|
|
141
|
+
userProvider: "github-issues",
|
|
142
|
+
},
|
|
143
|
+
]);
|
|
144
|
+
expect(prompt).toContain("linear");
|
|
145
|
+
expect(prompt).toContain("github-issues");
|
|
146
|
+
expect(prompt).toContain("issue-tracker");
|
|
147
|
+
expect(prompt.toLowerCase()).toContain("rewrite");
|
|
148
|
+
});
|
|
149
|
+
it("describes multiple mismatches", () => {
|
|
150
|
+
const prompt = buildAdaptPrompt([
|
|
151
|
+
{
|
|
152
|
+
category: "tasks",
|
|
153
|
+
configKey: "issue-tracker-provider",
|
|
154
|
+
workflowSkill: "linear",
|
|
155
|
+
userProvider: "github-issues",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
category: "observability",
|
|
159
|
+
configKey: "observability-provider",
|
|
160
|
+
workflowSkill: "datadog",
|
|
161
|
+
userProvider: "sentry",
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
expect(prompt).toContain("linear");
|
|
165
|
+
expect(prompt).toContain("datadog");
|
|
166
|
+
expect(prompt).toContain("sentry");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
describe("adaptWorkflowInteractive", () => {
|
|
170
|
+
beforeEach(() => vi.clearAllMocks());
|
|
171
|
+
const sampleWorkflow = {
|
|
172
|
+
id: "pr-review",
|
|
173
|
+
name: "PR Review",
|
|
174
|
+
description: "",
|
|
175
|
+
entry: "start",
|
|
176
|
+
nodes: { start: { name: "Start", instruction: "x", skills: [] } },
|
|
177
|
+
edges: [],
|
|
178
|
+
};
|
|
179
|
+
it("returns the workflow when user accepts on first render", async () => {
|
|
180
|
+
const p = await import("@clack/prompts");
|
|
181
|
+
p.select.mockResolvedValueOnce("accept");
|
|
182
|
+
const claudeStub = { run: vi.fn() };
|
|
183
|
+
const result = await adaptWorkflowInteractive(sampleWorkflow, {
|
|
184
|
+
claude: claudeStub,
|
|
185
|
+
skills: [],
|
|
186
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
187
|
+
});
|
|
188
|
+
expect(result).toEqual(sampleWorkflow);
|
|
189
|
+
});
|
|
190
|
+
it("returns null when user cancels", async () => {
|
|
191
|
+
const p = await import("@clack/prompts");
|
|
192
|
+
p.select.mockResolvedValueOnce("cancel");
|
|
193
|
+
const claudeStub = { run: vi.fn() };
|
|
194
|
+
const result = await adaptWorkflowInteractive(sampleWorkflow, {
|
|
195
|
+
claude: claudeStub,
|
|
196
|
+
skills: [],
|
|
197
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
198
|
+
});
|
|
199
|
+
expect(result).toBeNull();
|
|
200
|
+
});
|
|
201
|
+
it("calls refineWorkflow when user picks 'refine'", async () => {
|
|
202
|
+
const p = await import("@clack/prompts");
|
|
203
|
+
const wb = await import("../workflow-builder.js");
|
|
204
|
+
p.select.mockResolvedValueOnce("refine").mockResolvedValueOnce("accept");
|
|
205
|
+
p.text.mockResolvedValueOnce("make it better");
|
|
206
|
+
wb.refineWorkflow.mockResolvedValueOnce(sampleWorkflow);
|
|
207
|
+
const claudeStub = { run: vi.fn() };
|
|
208
|
+
const result = await adaptWorkflowInteractive(sampleWorkflow, {
|
|
209
|
+
claude: claudeStub,
|
|
210
|
+
skills: [],
|
|
211
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
212
|
+
});
|
|
213
|
+
expect(wb.refineWorkflow).toHaveBeenCalledWith(sampleWorkflow, "make it better", expect.any(Object));
|
|
214
|
+
expect(result).toEqual(sampleWorkflow);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe("installMarketplaceWorkflow", () => {
|
|
218
|
+
const tmpDirs = [];
|
|
219
|
+
afterEach(() => {
|
|
220
|
+
for (const d of tmpDirs)
|
|
221
|
+
fs.rmSync(d, { recursive: true, force: true });
|
|
222
|
+
tmpDirs.length = 0;
|
|
223
|
+
vi.restoreAllMocks();
|
|
224
|
+
});
|
|
225
|
+
function tmp() {
|
|
226
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "sweny-install-"));
|
|
227
|
+
tmpDirs.push(dir);
|
|
228
|
+
return dir;
|
|
229
|
+
}
|
|
230
|
+
const sampleYaml = "id: pr-review\nname: PR Review\ndescription: Review PRs\nentry: start\n" +
|
|
231
|
+
"nodes:\n start:\n name: Start\n instruction: hi\n skills: [github]\nedges: []\n";
|
|
232
|
+
it("installs workflow as-is when no mismatch and no .sweny.yml", async () => {
|
|
233
|
+
const cwd = tmp();
|
|
234
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(sampleYaml, { status: 200 }));
|
|
235
|
+
const result = await installMarketplaceWorkflow("pr-review", {
|
|
236
|
+
cwd,
|
|
237
|
+
availableSkills: testSkills,
|
|
238
|
+
claude: null,
|
|
239
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
240
|
+
});
|
|
241
|
+
expect(result.installed).toBe(true);
|
|
242
|
+
expect(result.adapted).toBe(false);
|
|
243
|
+
expect(fs.existsSync(path.join(cwd, ".sweny.yml"))).toBe(true);
|
|
244
|
+
expect(fs.existsSync(path.join(cwd, ".sweny", "workflows", "pr-review.yml"))).toBe(true);
|
|
245
|
+
});
|
|
246
|
+
it("warns and installs as-is when mismatch detected but no agent", async () => {
|
|
247
|
+
const cwd = tmp();
|
|
248
|
+
fs.writeFileSync(path.join(cwd, ".sweny.yml"), "issue-tracker-provider: github-issues\n");
|
|
249
|
+
const yamlWithLinear = sampleYaml.replace("[github]", "[linear]");
|
|
250
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(yamlWithLinear, { status: 200 }));
|
|
251
|
+
const warnings = [];
|
|
252
|
+
const result = await installMarketplaceWorkflow("pr-review", {
|
|
253
|
+
cwd,
|
|
254
|
+
availableSkills: testSkills,
|
|
255
|
+
claude: null,
|
|
256
|
+
logger: {
|
|
257
|
+
debug: () => { },
|
|
258
|
+
info: () => { },
|
|
259
|
+
warn: (m) => warnings.push(m),
|
|
260
|
+
error: () => { },
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
expect(result.installed).toBe(true);
|
|
264
|
+
expect(result.adapted).toBe(false);
|
|
265
|
+
expect(result.mismatches).toHaveLength(1);
|
|
266
|
+
expect(warnings.some((w) => w.includes("linear"))).toBe(true);
|
|
267
|
+
});
|
|
268
|
+
it("throws with not-found kind when workflow missing", async () => {
|
|
269
|
+
const cwd = tmp();
|
|
270
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response("", { status: 404 }));
|
|
271
|
+
await expect(installMarketplaceWorkflow("nope", {
|
|
272
|
+
cwd,
|
|
273
|
+
availableSkills: testSkills,
|
|
274
|
+
claude: null,
|
|
275
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
276
|
+
})).rejects.toMatchObject({ kind: "not-found" });
|
|
277
|
+
});
|
|
278
|
+
// ── I1: pre-computed provider inference ────────────────────────────────
|
|
279
|
+
it("uses inferredSourceControl instead of skill-based inference for .sweny.yml", async () => {
|
|
280
|
+
// Workflow references github skill, but caller says the remote is gitlab.
|
|
281
|
+
// The written .sweny.yml must say gitlab, not github.
|
|
282
|
+
const cwd = tmp();
|
|
283
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(sampleYaml, { status: 200 }));
|
|
284
|
+
await installMarketplaceWorkflow("pr-review", {
|
|
285
|
+
cwd,
|
|
286
|
+
availableSkills: testSkills,
|
|
287
|
+
claude: null,
|
|
288
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
289
|
+
inferredSourceControl: "gitlab",
|
|
290
|
+
inferredIssueTracker: "github-issues",
|
|
291
|
+
inferredObservability: null,
|
|
292
|
+
});
|
|
293
|
+
const swenyYml = fs.readFileSync(path.join(cwd, ".sweny.yml"), "utf-8");
|
|
294
|
+
expect(swenyYml).toContain("source-control-provider: gitlab");
|
|
295
|
+
// skill-based fallback would have written "github" — verify it did NOT
|
|
296
|
+
expect(swenyYml).not.toContain("source-control-provider: github");
|
|
297
|
+
});
|
|
298
|
+
it("falls back to skill-based inference when inferredSourceControl is absent", async () => {
|
|
299
|
+
// Workflow references github skill, no pre-computed values → should infer github
|
|
300
|
+
const cwd = tmp();
|
|
301
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(sampleYaml, { status: 200 }));
|
|
302
|
+
await installMarketplaceWorkflow("pr-review", {
|
|
303
|
+
cwd,
|
|
304
|
+
availableSkills: testSkills,
|
|
305
|
+
claude: null,
|
|
306
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
307
|
+
});
|
|
308
|
+
const swenyYml = fs.readFileSync(path.join(cwd, ".sweny.yml"), "utf-8");
|
|
309
|
+
expect(swenyYml).toContain("source-control-provider: github");
|
|
310
|
+
});
|
|
311
|
+
// ── I2: overwrite protection ────────────────────────────────────────────
|
|
312
|
+
it("returns alreadyExists=true without overwriting when overwrite is false (default)", async () => {
|
|
313
|
+
const cwd = tmp();
|
|
314
|
+
// Pre-create the workflow file with distinct content
|
|
315
|
+
const wfDir = path.join(cwd, ".sweny", "workflows");
|
|
316
|
+
fs.mkdirSync(wfDir, { recursive: true });
|
|
317
|
+
const wfPath = path.join(wfDir, "pr-review.yml");
|
|
318
|
+
fs.writeFileSync(wfPath, "# original content\n", "utf-8");
|
|
319
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(sampleYaml, { status: 200 }));
|
|
320
|
+
const result = await installMarketplaceWorkflow("pr-review", {
|
|
321
|
+
cwd,
|
|
322
|
+
availableSkills: testSkills,
|
|
323
|
+
claude: null,
|
|
324
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
325
|
+
// overwrite: false is the default — omitting it intentionally
|
|
326
|
+
});
|
|
327
|
+
expect(result.installed).toBe(false);
|
|
328
|
+
expect(result.alreadyExists).toBe(true);
|
|
329
|
+
expect(result.workflowPath).toBe(wfPath);
|
|
330
|
+
// File must be unchanged
|
|
331
|
+
expect(fs.readFileSync(wfPath, "utf-8")).toBe("# original content\n");
|
|
332
|
+
});
|
|
333
|
+
it("overwrites existing workflow file when overwrite is true", async () => {
|
|
334
|
+
const cwd = tmp();
|
|
335
|
+
// Pre-create the workflow file with stale content
|
|
336
|
+
const wfDir = path.join(cwd, ".sweny", "workflows");
|
|
337
|
+
fs.mkdirSync(wfDir, { recursive: true });
|
|
338
|
+
const wfPath = path.join(wfDir, "pr-review.yml");
|
|
339
|
+
fs.writeFileSync(wfPath, "# stale content\n", "utf-8");
|
|
340
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response(sampleYaml, { status: 200 }));
|
|
341
|
+
const result = await installMarketplaceWorkflow("pr-review", {
|
|
342
|
+
cwd,
|
|
343
|
+
availableSkills: testSkills,
|
|
344
|
+
claude: null,
|
|
345
|
+
logger: { debug: () => { }, info: () => { }, warn: () => { }, error: () => { } },
|
|
346
|
+
overwrite: true,
|
|
347
|
+
});
|
|
348
|
+
expect(result.installed).toBe(true);
|
|
349
|
+
expect(result.alreadyExists).toBeUndefined();
|
|
350
|
+
// File must contain the new content
|
|
351
|
+
const written = fs.readFileSync(wfPath, "utf-8");
|
|
352
|
+
expect(written).toContain("PR Review");
|
|
353
|
+
expect(written).not.toContain("stale content");
|
|
354
|
+
});
|
|
355
|
+
});
|
package/dist/cli/new.d.ts
CHANGED
|
@@ -76,6 +76,23 @@ export declare function collectCredentials(selections: InitSelections): Credenti
|
|
|
76
76
|
* Returns provider + normalised remote (e.g. "github.com/owner/repo"), or null.
|
|
77
77
|
*/
|
|
78
78
|
export declare function detectGitRemote(cwd: string): GitRemoteInfo | null;
|
|
79
|
+
/**
|
|
80
|
+
* Infer source-control-provider from git remote detection.
|
|
81
|
+
*/
|
|
82
|
+
export declare function inferSourceControl(gitInfo: GitRemoteInfo | null): string;
|
|
83
|
+
/**
|
|
84
|
+
* Infer issue-tracker-provider from skills in the workflow.
|
|
85
|
+
*
|
|
86
|
+
* Explicit issue-tracker skills win. Otherwise we default to `github-issues`
|
|
87
|
+
* regardless of source-control provider: we don't ship a gitlab issues
|
|
88
|
+
* provider (yet), and `github-issues` is a sensible "use the same platform
|
|
89
|
+
* as source control" default for the supported case.
|
|
90
|
+
*/
|
|
91
|
+
export declare function inferIssueTracker(skills: string[], _sourceControl: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Infer observability-provider from skills in the workflow.
|
|
94
|
+
*/
|
|
95
|
+
export declare function inferObservability(skills: string[]): string | null;
|
|
79
96
|
/**
|
|
80
97
|
* Generate .sweny.yml content from inferred providers.
|
|
81
98
|
*/
|
|
@@ -89,12 +106,25 @@ export declare function buildEnvTemplate(credentials: Credential[]): string;
|
|
|
89
106
|
* Only includes credentials WITHOUT defaults as secrets.
|
|
90
107
|
*/
|
|
91
108
|
export declare function buildActionWorkflow(credentials: Credential[], cronExpression: string): string;
|
|
109
|
+
/** Write .sweny.yml if missing. Returns true if created, false if file existed. */
|
|
110
|
+
export declare function writeSwenyYmlIfMissing(cwd: string, sourceControl: string, observability: string | null, issueTracker: string): boolean;
|
|
111
|
+
/** Append credential keys to .env that aren't already present. Returns count appended. */
|
|
112
|
+
export declare function appendMissingEnvKeys(cwd: string, credentials: Credential[]): number;
|
|
113
|
+
/** Write workflow YAML into .sweny/workflows/<id>.yml. Returns info about what happened. */
|
|
114
|
+
export declare function writeWorkflowFile(cwd: string, id: string, yaml: string, options?: {
|
|
115
|
+
overwrite?: boolean;
|
|
116
|
+
}): {
|
|
117
|
+
written: boolean;
|
|
118
|
+
exists?: boolean;
|
|
119
|
+
path: string;
|
|
120
|
+
};
|
|
92
121
|
/**
|
|
93
122
|
* Interactive workflow creation wizard — workflow-first approach.
|
|
94
123
|
*
|
|
95
124
|
* Flow:
|
|
96
125
|
* 1. Detect git remote (used to infer source-control provider)
|
|
97
|
-
* 2. Pick one of
|
|
126
|
+
* 2. Pick one of five branches:
|
|
127
|
+
* - `__marketplace` → browse published workflows at swenyai/workflows, then delegate to `installMarketplaceWorkflow`
|
|
98
128
|
* - A built-in `WORKFLOW_TEMPLATES` entry → write that workflow file
|
|
99
129
|
* - `__custom` → LLM generates a workflow from a user description
|
|
100
130
|
* - `__e2e` → delegate to the e2e browser-testing wizard in `./e2e.ts`
|
|
@@ -108,4 +138,7 @@ export declare function buildActionWorkflow(credentials: Credential[], cronExpre
|
|
|
108
138
|
*
|
|
109
139
|
* Safe to re-run in an existing project to add additional workflows.
|
|
110
140
|
*/
|
|
111
|
-
export declare function runNew(
|
|
141
|
+
export declare function runNew(options?: {
|
|
142
|
+
marketplaceId?: string;
|
|
143
|
+
skipIntro?: boolean;
|
|
144
|
+
}): Promise<void>;
|