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