@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.
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 +42 -4
  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,23 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { available } from "./harness.js";
3
- import { datadog } from "../../skills/datadog.js";
4
- const ctx = () => ({
5
- config: {
6
- DD_API_KEY: process.env.DD_API_KEY,
7
- DD_APP_KEY: process.env.DD_APP_KEY,
8
- DD_SITE: process.env.DD_SITE ?? "",
9
- },
10
- logger: console,
11
- });
12
- describe.runIf(available.datadog)("datadog integration", () => {
13
- it("lists monitors", async () => {
14
- const tool = datadog.tools.find((t) => t.name === "datadog_list_monitors");
15
- const result = await tool.handler({}, ctx());
16
- expect(Array.isArray(result)).toBe(true);
17
- }, 15_000);
18
- it("searches logs", async () => {
19
- const tool = datadog.tools.find((t) => t.name === "datadog_search_logs");
20
- const result = await tool.handler({ query: "*", from: "now-5m", limit: 5 }, ctx());
21
- expect(result).toHaveProperty("data");
22
- }, 15_000);
23
- });
@@ -1,75 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { available, logAvailability } from "./harness.js";
3
- import { execute } from "../../executor.js";
4
- import { ClaudeClient } from "../../claude.js";
5
- import { createSkillMap } from "../../skills/index.js";
6
- logAvailability();
7
- // A minimal 2-node workflow that only uses Claude (no external tools)
8
- const analysisWorkflow = {
9
- id: "e2e-analysis",
10
- name: "E2E Analysis",
11
- description: "Two-node workflow using real Claude",
12
- entry: "analyze",
13
- nodes: {
14
- analyze: {
15
- name: "Analyze Input",
16
- instruction: "Analyze the input data and determine if it represents a critical issue. Return JSON with fields: severity (high/medium/low) and summary (one sentence).",
17
- skills: [],
18
- output: {
19
- type: "object",
20
- properties: {
21
- severity: { type: "string", enum: ["high", "medium", "low"] },
22
- summary: { type: "string" },
23
- },
24
- required: ["severity", "summary"],
25
- },
26
- },
27
- report: {
28
- name: "Generate Report",
29
- instruction: "Based on the analysis, write a brief incident report. Return JSON with fields: title (string) and recommendation (string).",
30
- skills: [],
31
- output: {
32
- type: "object",
33
- properties: {
34
- title: { type: "string" },
35
- recommendation: { type: "string" },
36
- },
37
- required: ["title", "recommendation"],
38
- },
39
- },
40
- },
41
- edges: [{ from: "analyze", to: "report" }],
42
- };
43
- describe.runIf(available.claude)("e2e workflow with real Claude", () => {
44
- it("executes a 2-node analysis workflow", async () => {
45
- const claude = new ClaudeClient({
46
- model: "claude-haiku-4-5-20251001", // Use Haiku for speed/cost in tests
47
- maxTurns: 3,
48
- });
49
- const events = [];
50
- const { results } = await execute(analysisWorkflow, {
51
- alert: "CPU usage at 98% for 15 minutes on production server web-03",
52
- service: "api-gateway",
53
- timestamp: "2025-03-25T08:00:00Z",
54
- }, {
55
- skills: createSkillMap([]),
56
- claude,
57
- observer: (e) => events.push(e),
58
- config: {},
59
- });
60
- // Both nodes executed
61
- expect(results.size).toBe(2);
62
- expect(results.get("analyze")?.status).toBe("success");
63
- expect(results.get("report")?.status).toBe("success");
64
- // Analysis should have severity
65
- const analysis = results.get("analyze").data;
66
- expect(analysis).toBeDefined();
67
- // Report should have title
68
- const report = results.get("report").data;
69
- expect(report).toBeDefined();
70
- // Events stream should be complete
71
- expect(events[0].type).toBe("workflow:start");
72
- expect(events[events.length - 1].type).toBe("workflow:end");
73
- expect(events.filter((e) => e.type === "node:enter")).toHaveLength(2);
74
- }, 60_000); // 60s timeout for real API calls
75
- });
@@ -1,37 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { available, logAvailability } from "./harness.js";
3
- import { github } from "../../skills/github.js";
4
- logAvailability();
5
- const ctx = () => ({
6
- config: { GITHUB_TOKEN: process.env.GITHUB_TOKEN },
7
- logger: console,
8
- });
9
- describe.runIf(available.github)("github integration", () => {
10
- it("searches code in a public repo", async () => {
11
- const tool = github.tools.find((t) => t.name === "github_search_code");
12
- const result = await tool.handler({ query: "README", repo: "octocat/Hello-World" }, ctx());
13
- expect(result).toHaveProperty("items");
14
- }, 15_000);
15
- it("gets an issue from a public repo", async () => {
16
- const tool = github.tools.find((t) => t.name === "github_get_issue");
17
- const result = await tool.handler({ repo: "octocat/Hello-World", number: 1 }, ctx());
18
- expect(result).toHaveProperty("title");
19
- expect(result).toHaveProperty("state");
20
- }, 15_000);
21
- it("lists recent commits", async () => {
22
- const tool = github.tools.find((t) => t.name === "github_list_recent_commits");
23
- const result = await tool.handler({ repo: "octocat/Hello-World", branch: "master", per_page: 3 }, ctx());
24
- expect(Array.isArray(result)).toBe(true);
25
- expect(result.length).toBeGreaterThan(0);
26
- }, 15_000);
27
- it("searches issues", async () => {
28
- const tool = github.tools.find((t) => t.name === "github_search_issues");
29
- const result = await tool.handler({ query: "is:issue", repo: "octocat/Hello-World" }, ctx());
30
- expect(result).toHaveProperty("items");
31
- }, 15_000);
32
- it("gets a file from a public repo", async () => {
33
- const tool = github.tools.find((t) => t.name === "github_get_file");
34
- const result = await tool.handler({ repo: "octocat/Hello-World", path: "README" }, ctx());
35
- expect(result).toHaveProperty("content");
36
- }, 15_000);
37
- });
@@ -1,24 +0,0 @@
1
- /**
2
- * Integration Test Harness
3
- *
4
- * Detects which external services are available via environment variables.
5
- * Integration tests use `describe.runIf(available.xxx)` to skip cleanly
6
- * when credentials aren't present.
7
- *
8
- * Usage:
9
- * npx vitest run --config vitest.integration.config.ts
10
- *
11
- * Or with env vars:
12
- * GITHUB_TOKEN=ghp_... npx vitest run --config vitest.integration.config.ts
13
- */
14
- export declare const available: {
15
- github: boolean;
16
- linear: boolean;
17
- sentry: boolean;
18
- datadog: boolean;
19
- slack: boolean;
20
- discord: boolean;
21
- claude: boolean;
22
- };
23
- /** Print which services are available (for CI logs) */
24
- export declare function logAvailability(): void;
@@ -1,34 +0,0 @@
1
- /**
2
- * Integration Test Harness
3
- *
4
- * Detects which external services are available via environment variables.
5
- * Integration tests use `describe.runIf(available.xxx)` to skip cleanly
6
- * when credentials aren't present.
7
- *
8
- * Usage:
9
- * npx vitest run --config vitest.integration.config.ts
10
- *
11
- * Or with env vars:
12
- * GITHUB_TOKEN=ghp_... npx vitest run --config vitest.integration.config.ts
13
- */
14
- export const available = {
15
- github: !!process.env.GITHUB_TOKEN,
16
- linear: !!process.env.LINEAR_API_KEY,
17
- sentry: !!(process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG),
18
- datadog: !!(process.env.DD_API_KEY && process.env.DD_APP_KEY),
19
- slack: !!(process.env.SLACK_WEBHOOK_URL || process.env.SLACK_BOT_TOKEN),
20
- discord: !!process.env.DISCORD_WEBHOOK_URL,
21
- claude: !!process.env.ANTHROPIC_API_KEY,
22
- };
23
- /** Print which services are available (for CI logs) */
24
- export function logAvailability() {
25
- const entries = Object.entries(available);
26
- const on = entries.filter(([, v]) => v).map(([k]) => k);
27
- const off = entries.filter(([, v]) => !v).map(([k]) => k);
28
- console.log(`\n🔌 Integration test services:`);
29
- if (on.length > 0)
30
- console.log(` ✅ Available: ${on.join(", ")}`);
31
- if (off.length > 0)
32
- console.log(` ⏭️ Skipped: ${off.join(", ")}`);
33
- console.log();
34
- }
@@ -1,15 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { available } from "./harness.js";
3
- import { linear } from "../../skills/linear.js";
4
- const ctx = () => ({
5
- config: { LINEAR_API_KEY: process.env.LINEAR_API_KEY },
6
- logger: console,
7
- });
8
- describe.runIf(available.linear)("linear integration", () => {
9
- it("searches issues", async () => {
10
- const tool = linear.tools.find((t) => t.name === "linear_search_issues");
11
- const result = await tool.handler({ query: "bug", limit: 3 }, ctx());
12
- expect(result).toHaveProperty("searchIssues");
13
- expect(result.searchIssues).toHaveProperty("nodes");
14
- }, 15_000);
15
- });
@@ -1,20 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { available } from "./harness.js";
3
- import { sentry } from "../../skills/sentry.js";
4
- const ctx = () => ({
5
- config: {
6
- SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
7
- SENTRY_ORG: process.env.SENTRY_ORG,
8
- SENTRY_BASE_URL: process.env.SENTRY_BASE_URL ?? "",
9
- },
10
- logger: console,
11
- });
12
- describe.runIf(available.sentry)("sentry integration", () => {
13
- // These tests require at least one project in the org
14
- it("list_issues returns an array", async () => {
15
- const tool = sentry.tools.find((t) => t.name === "sentry_list_issues");
16
- // Use the first project found, or skip if none
17
- const result = await tool.handler({ project: process.env.SENTRY_PROJECT ?? "javascript" }, ctx());
18
- expect(Array.isArray(result)).toBe(true);
19
- }, 15_000);
20
- });
@@ -1,22 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { available } from "./harness.js";
3
- import { slack } from "../../skills/slack.js";
4
- const ctx = () => ({
5
- config: {
6
- SLACK_WEBHOOK_URL: process.env.SLACK_WEBHOOK_URL ?? "",
7
- SLACK_BOT_TOKEN: process.env.SLACK_BOT_TOKEN ?? "",
8
- },
9
- logger: console,
10
- });
11
- // Only run Slack integration tests when explicitly opted in
12
- // to avoid spamming channels during routine test runs
13
- const slackEnabled = available.slack && process.env.SLACK_INTEGRATION_TEST === "1";
14
- describe.runIf(slackEnabled)("slack integration", () => {
15
- it("sends a test message via webhook", async () => {
16
- if (!process.env.SLACK_WEBHOOK_URL)
17
- return;
18
- const tool = slack.tools.find((t) => t.name === "slack_send_message");
19
- const result = await tool.handler({ text: `🧪 SWEny integration test — ${new Date().toISOString()}` }, ctx());
20
- expect(result).toHaveProperty("ok");
21
- }, 15_000);
22
- });
@@ -1,123 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from "vitest";
2
- import { mkdirSync, writeFileSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import * as path from "node:path";
5
- import { randomBytes } from "node:crypto";
6
- import { execute } from "../../executor.js";
7
- import { MockClaude } from "../../testing.js";
8
- import { createSkillMap } from "../../skills/index.js";
9
- function freshDir(name) {
10
- const dir = path.join(tmpdir(), `sweny-sources-int-${name}-${Date.now()}-${randomBytes(4).toString("hex")}`);
11
- rmSync(dir, { recursive: true, force: true });
12
- mkdirSync(dir, { recursive: true });
13
- return dir;
14
- }
15
- describe("integration: Source resolution across a workflow", () => {
16
- beforeEach(() => {
17
- vi.restoreAllMocks();
18
- });
19
- it("resolves inline, file, and url instructions across three nodes", async () => {
20
- const dir = freshDir("three-kinds");
21
- writeFileSync(path.join(dir, "b.md"), "Step B instruction from file.");
22
- vi.spyOn(globalThis, "fetch").mockImplementation(async () => new Response("Step C instruction from URL.", { status: 200 }));
23
- const workflow = {
24
- id: "three-kinds",
25
- name: "Three Kinds",
26
- description: "",
27
- entry: "a",
28
- nodes: {
29
- a: { name: "A", instruction: "Step A inline", skills: [] },
30
- b: { name: "B", instruction: { file: path.join(dir, "b.md") }, skills: [] },
31
- c: { name: "C", instruction: { url: "https://example.com/c.md" }, skills: [] },
32
- },
33
- edges: [
34
- { from: "a", to: "b" },
35
- { from: "b", to: "c" },
36
- ],
37
- };
38
- const skills = createSkillMap([]);
39
- const result = await execute(workflow, {}, {
40
- skills,
41
- claude: new MockClaude({
42
- responses: {
43
- a: { data: {} },
44
- b: { data: {} },
45
- c: { data: {} },
46
- },
47
- }),
48
- cwd: dir,
49
- });
50
- // All three nodes should have executed
51
- expect(result.results.size).toBe(3);
52
- // Trace should have all sources
53
- expect(result.trace.sources["nodes.a.instruction"].kind).toBe("inline");
54
- expect(result.trace.sources["nodes.a.instruction"].content).toBe("Step A inline");
55
- expect(result.trace.sources["nodes.b.instruction"].kind).toBe("file");
56
- expect(result.trace.sources["nodes.b.instruction"].content).toBe("Step B instruction from file.");
57
- expect(result.trace.sources["nodes.c.instruction"].kind).toBe("url");
58
- expect(result.trace.sources["nodes.c.instruction"].content).toBe("Step C instruction from URL.");
59
- expect(result.trace.sources["nodes.c.instruction"].fetchedAt).toBeDefined();
60
- // Every source has a hash
61
- for (const rs of Object.values(result.trace.sources)) {
62
- expect(rs.hash).toMatch(/^[0-9a-f]{16}$/);
63
- }
64
- });
65
- it("fails fast with SOURCE_OFFLINE_REQUIRES_FETCH when offline and URL present", async () => {
66
- const workflow = {
67
- id: "offline-fail",
68
- name: "Offline fail",
69
- description: "",
70
- entry: "a",
71
- nodes: {
72
- a: { name: "A", instruction: { url: "https://example.com/x" }, skills: [] },
73
- },
74
- edges: [],
75
- };
76
- await expect(execute(workflow, {}, {
77
- skills: createSkillMap([]),
78
- claude: new MockClaude({ responses: { a: { data: {} } } }),
79
- offline: true,
80
- })).rejects.toThrow(/SOURCE_OFFLINE_REQUIRES_FETCH/);
81
- });
82
- it("fails fast with SOURCE_FILE_NOT_FOUND and includes field path", async () => {
83
- const dir = freshDir("missing");
84
- const workflow = {
85
- id: "file-missing",
86
- name: "File missing",
87
- description: "",
88
- entry: "bad",
89
- nodes: {
90
- bad: { name: "Bad", instruction: { file: "./does-not-exist.md" }, skills: [] },
91
- },
92
- edges: [],
93
- };
94
- await expect(execute(workflow, {}, {
95
- skills: createSkillMap([]),
96
- claude: new MockClaude({ responses: { bad: { data: {} } } }),
97
- cwd: dir,
98
- })).rejects.toThrow(/SOURCE_FILE_NOT_FOUND.*nodes\.bad\.instruction/);
99
- });
100
- it("emits workflow:start before sources:resolved (per spec)", async () => {
101
- const events = [];
102
- const workflow = {
103
- id: "evt-test",
104
- name: "Event test",
105
- description: "",
106
- entry: "only",
107
- nodes: { only: { name: "Only", instruction: "Go.", skills: [] } },
108
- edges: [],
109
- };
110
- await execute(workflow, {}, {
111
- skills: createSkillMap([]),
112
- claude: new MockClaude({ responses: { only: { data: {} } } }),
113
- observer: (e) => events.push(e),
114
- });
115
- const srcEvt = events.find((e) => e.type === "sources:resolved");
116
- expect(srcEvt).toBeDefined();
117
- expect(srcEvt.sources["nodes.only.instruction"].content).toBe("Go.");
118
- // workflow:start should come before sources:resolved (per spec)
119
- const startIdx = events.findIndex((e) => e.type === "workflow:start");
120
- const srcIdx = events.findIndex((e) => e.type === "sources:resolved");
121
- expect(startIdx).toBeLessThan(srcIdx);
122
- });
123
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,180 +0,0 @@
1
- import { describe, it, expect, beforeAll, afterAll } 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 { validateWorkflowFile, validateSkillDir } from "../cli/publish.js";
6
- describe("validateWorkflowFile", () => {
7
- let tmpDir;
8
- beforeAll(() => {
9
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sweny-publish-test-"));
10
- });
11
- afterAll(() => {
12
- fs.rmSync(tmpDir, { recursive: true, force: true });
13
- });
14
- it("validates a correct workflow file", () => {
15
- const file = path.join(tmpDir, "good.yml");
16
- fs.writeFileSync(file, `id: test-workflow
17
- name: Test
18
- description: A test workflow
19
- entry: start
20
- nodes:
21
- start:
22
- name: Start
23
- instruction: Do something.
24
- skills: [github]
25
- edges: []
26
- `);
27
- const result = validateWorkflowFile(file);
28
- expect(result.valid).toBe(true);
29
- expect(result.id).toBe("test-workflow");
30
- expect(result.name).toBe("Test");
31
- expect(result.nodeCount).toBe(1);
32
- expect(result.edgeCount).toBe(0);
33
- expect(result.errors).toEqual([]);
34
- });
35
- it("rejects a non-existent file", () => {
36
- const result = validateWorkflowFile(path.join(tmpDir, "nope.yml"));
37
- expect(result.valid).toBe(false);
38
- expect(result.errors[0]).toBe("File not found");
39
- });
40
- it("rejects invalid YAML", () => {
41
- const file = path.join(tmpDir, "bad.yml");
42
- fs.writeFileSync(file, "{{invalid yaml");
43
- const result = validateWorkflowFile(file);
44
- expect(result.valid).toBe(false);
45
- expect(result.errors[0]).toMatch(/Invalid YAML/);
46
- });
47
- it("rejects a workflow missing required fields", () => {
48
- const file = path.join(tmpDir, "incomplete.yml");
49
- fs.writeFileSync(file, "id: test\nname: Test\n");
50
- const result = validateWorkflowFile(file);
51
- expect(result.valid).toBe(false);
52
- expect(result.errors.length).toBeGreaterThan(0);
53
- });
54
- it("rejects a workflow with unknown skill references", () => {
55
- const file = path.join(tmpDir, "unknown-skill.yml");
56
- fs.writeFileSync(file, `id: unknown-skill-test
57
- name: Unknown Skill Test
58
- description: Test
59
- entry: start
60
- nodes:
61
- start:
62
- name: Start
63
- instruction: Do something.
64
- skills: [unicorn]
65
- edges: []
66
- `);
67
- const result = validateWorkflowFile(file);
68
- expect(result.valid).toBe(false);
69
- expect(result.errors.some((e) => e.includes("unicorn"))).toBe(true);
70
- });
71
- it("returns config warnings for skills with required env vars", () => {
72
- const file = path.join(tmpDir, "with-github.yml");
73
- fs.writeFileSync(file, `id: github-test
74
- name: GitHub Test
75
- description: Test
76
- entry: start
77
- nodes:
78
- start:
79
- name: Start
80
- instruction: Do something.
81
- skills: [github]
82
- edges: []
83
- `);
84
- const result = validateWorkflowFile(file);
85
- expect(result.valid).toBe(true);
86
- expect(result.warnings.some((w) => w.includes("GITHUB_TOKEN"))).toBe(true);
87
- });
88
- });
89
- describe("validateSkillDir", () => {
90
- let tmpDir;
91
- beforeAll(() => {
92
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "sweny-skill-test-"));
93
- });
94
- afterAll(() => {
95
- fs.rmSync(tmpDir, { recursive: true, force: true });
96
- });
97
- it("validates a correct instruction skill", () => {
98
- const skillDir = path.join(tmpDir, "my-skill");
99
- fs.mkdirSync(skillDir, { recursive: true });
100
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
101
- name: my-skill
102
- description: A test skill
103
- ---
104
-
105
- When reviewing code, check for security issues first.
106
- `);
107
- const result = validateSkillDir(skillDir);
108
- expect(result.valid).toBe(true);
109
- expect(result.id).toBe("my-skill");
110
- expect(result.hasInstruction).toBe(true);
111
- expect(result.hasMcp).toBe(false);
112
- });
113
- it("validates a correct MCP skill", () => {
114
- const skillDir = path.join(tmpDir, "mcp-skill");
115
- fs.mkdirSync(skillDir, { recursive: true });
116
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
117
- name: mcp-skill
118
- description: A test MCP skill
119
- mcp:
120
- command: npx
121
- args: ["-y", "@company/server"]
122
- ---
123
- `);
124
- const result = validateSkillDir(skillDir);
125
- expect(result.valid).toBe(true);
126
- expect(result.hasMcp).toBe(true);
127
- });
128
- it("rejects a directory without SKILL.md", () => {
129
- const emptyDir = path.join(tmpDir, "empty");
130
- fs.mkdirSync(emptyDir, { recursive: true });
131
- const result = validateSkillDir(emptyDir);
132
- expect(result.valid).toBe(false);
133
- expect(result.errors[0]).toBe("SKILL.md not found in directory");
134
- });
135
- it("rejects a skill with no frontmatter", () => {
136
- const skillDir = path.join(tmpDir, "no-fm");
137
- fs.mkdirSync(skillDir, { recursive: true });
138
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), "Just some text");
139
- const result = validateSkillDir(skillDir);
140
- expect(result.valid).toBe(false);
141
- expect(result.errors[0]).toBe("No YAML frontmatter found");
142
- });
143
- it("rejects an invalid skill ID", () => {
144
- const skillDir = path.join(tmpDir, "Bad-Name");
145
- fs.mkdirSync(skillDir, { recursive: true });
146
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
147
- name: Bad-Name
148
- ---
149
-
150
- Some instruction.
151
- `);
152
- const result = validateSkillDir(skillDir);
153
- expect(result.valid).toBe(false);
154
- expect(result.errors[0]).toMatch(/Invalid skill ID/);
155
- });
156
- it("rejects a skill with mismatched directory name", () => {
157
- const skillDir = path.join(tmpDir, "wrong-dir");
158
- fs.mkdirSync(skillDir, { recursive: true });
159
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
160
- name: correct-name
161
- ---
162
-
163
- Some instruction.
164
- `);
165
- const result = validateSkillDir(skillDir);
166
- expect(result.valid).toBe(false);
167
- expect(result.errors).toContain(`Directory name "wrong-dir" doesn't match skill name "correct-name"`);
168
- });
169
- it("rejects a skill with no instruction and no MCP", () => {
170
- const skillDir = path.join(tmpDir, "empty-skill");
171
- fs.mkdirSync(skillDir, { recursive: true });
172
- fs.writeFileSync(path.join(skillDir, "SKILL.md"), `---
173
- name: empty-skill
174
- ---
175
- `);
176
- const result = validateSkillDir(skillDir);
177
- expect(result.valid).toBe(false);
178
- expect(result.errors).toContain("Skill must have an instruction body or an mcp config");
179
- });
180
- });
@@ -1 +0,0 @@
1
- export {};