@sweny-ai/core 0.1.51 → 0.1.52

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/schema.js CHANGED
@@ -25,13 +25,41 @@ export const toolZ = z.object({
25
25
  input_schema: jsonSchemaZ,
26
26
  });
27
27
  export const skillCategoryZ = z.enum(["git", "observability", "tasks", "notification", "general"]);
28
- export const skillZ = z.object({
28
+ export const mcpServerConfigZ = z
29
+ .object({
30
+ type: z.enum(["stdio", "http"]).optional(),
31
+ command: z.string().optional(),
32
+ args: z.array(z.string()).optional(),
33
+ url: z.string().optional(),
34
+ headers: z.record(z.string()).optional(),
35
+ env: z.record(z.string()).optional(),
36
+ })
37
+ .refine((c) => c.command || c.url, {
38
+ message: "MCP server must have either command (stdio) or url (HTTP)",
39
+ });
40
+ export const skillDefinitionZ = z
41
+ .object({
42
+ name: z.string().optional(),
43
+ description: z.string().optional(),
44
+ instruction: z.string().optional(),
45
+ mcp: mcpServerConfigZ.optional(),
46
+ })
47
+ .refine((s) => s.instruction || s.mcp, {
48
+ message: "Inline skill must provide instruction, mcp, or both",
49
+ });
50
+ export const skillZ = z
51
+ .object({
29
52
  id: z.string().min(1),
30
53
  name: z.string(),
31
54
  description: z.string(),
32
55
  category: skillCategoryZ,
33
- config: z.record(configFieldZ),
34
- tools: z.array(toolZ),
56
+ config: z.record(configFieldZ).default({}),
57
+ tools: z.array(toolZ).default([]),
58
+ instruction: z.string().optional(),
59
+ mcp: mcpServerConfigZ.optional(),
60
+ })
61
+ .refine((s) => s.tools.length > 0 || s.instruction || s.mcp, {
62
+ message: "Skill must provide at least one of: tools, instruction, or mcp",
35
63
  });
36
64
  export const nodeZ = z.object({
37
65
  name: z.string().min(1),
@@ -52,6 +80,7 @@ export const workflowZ = z.object({
52
80
  nodes: z.record(nodeZ),
53
81
  edges: z.array(edgeZ),
54
82
  entry: z.string().min(1),
83
+ skills: z.record(skillDefinitionZ).default({}),
55
84
  });
56
85
  /** Parse + validate a raw object as a Workflow. Throws on invalid input. */
57
86
  export function parseWorkflow(raw) {
@@ -168,11 +197,25 @@ export function validateWorkflow(workflow, knownSkills) {
168
197
  }
169
198
  }
170
199
  }
200
+ // Inline skill definitions must have instruction or mcp
201
+ for (const [skillId, def] of Object.entries(workflow.skills ?? {})) {
202
+ if (!def.instruction && !def.mcp) {
203
+ errors.push({
204
+ code: "INVALID_INLINE_SKILL",
205
+ message: `Inline skill "${skillId}" must provide at least instruction or mcp`,
206
+ });
207
+ }
208
+ }
171
209
  // Skill references
172
210
  if (knownSkills) {
211
+ // Merge workflow inline skills into known set
212
+ const allKnown = new Set(knownSkills);
213
+ for (const id of Object.keys(workflow.skills ?? {})) {
214
+ allKnown.add(id);
215
+ }
173
216
  for (const [nodeId, node] of Object.entries(workflow.nodes)) {
174
217
  for (const skillId of node.skills) {
175
- if (!knownSkills.has(skillId)) {
218
+ if (!allKnown.has(skillId)) {
176
219
  errors.push({
177
220
  code: "UNKNOWN_SKILL",
178
221
  message: `Node "${nodeId}" references unknown skill "${skillId}"`,
@@ -241,5 +284,29 @@ export const workflowJsonSchema = {
241
284
  },
242
285
  },
243
286
  },
287
+ skills: {
288
+ type: "object",
289
+ description: "Inline skill definitions scoped to this workflow",
290
+ additionalProperties: {
291
+ type: "object",
292
+ properties: {
293
+ name: { type: "string" },
294
+ description: { type: "string" },
295
+ instruction: { type: "string", description: "Natural language expertise injected into the node prompt" },
296
+ mcp: {
297
+ type: "object",
298
+ description: "External MCP server definition",
299
+ properties: {
300
+ type: { type: "string", enum: ["stdio", "http"] },
301
+ command: { type: "string" },
302
+ args: { type: "array", items: { type: "string" } },
303
+ url: { type: "string" },
304
+ headers: { type: "object", additionalProperties: { type: "string" } },
305
+ env: { type: "object", additionalProperties: { type: "string" } },
306
+ },
307
+ },
308
+ },
309
+ },
310
+ },
244
311
  },
245
312
  };
@@ -1,24 +1,24 @@
1
1
  /**
2
2
  * Custom skill discovery — Node-only.
3
3
  *
4
- * Lives outside `skills/index.ts` so the registry stays browser-safe. Anything
5
- * that needs `node:fs` / `node:path` / `process.cwd()` belongs here.
4
+ * Scans multiple skill directories (multi-harness) for SKILL.md files,
5
+ * captures the markdown body as `instruction`, and parses optional `mcp`
6
+ * from frontmatter.
7
+ *
8
+ * Discovery priority (higher number wins on ID collision):
9
+ * .gemini/skills/ < .agents/skills/ < .claude/skills/ < .sweny/skills/
6
10
  */
7
11
  import type { Skill } from "../types.js";
8
12
  /**
9
- * Load custom skills from a `.claude/skills/` directory.
10
- *
11
- * Custom skills are instruction-only (no tools or config) — they provide
12
- * guidance documents that the coding agent reads from the filesystem.
13
- * Registering them here ensures workflow validation recognises the skill IDs
14
- * so nodes referencing them don't produce spurious warnings.
13
+ * Discover custom skills from all known harness directories.
14
+ */
15
+ export declare function discoverSkills(cwd?: string): Skill[];
16
+ /**
17
+ * Legacy API — load custom skills from `.claude/skills/` only.
18
+ * @deprecated Use `discoverSkills` for multi-directory support.
15
19
  */
16
20
  export declare function loadCustomSkills(cwd?: string): Skill[];
17
21
  /**
18
22
  * From all builtins + custom repo skills, return only the skills that are usable.
19
- * Built-in skills are filtered by env vars; custom skills are always included.
20
- *
21
- * Node-only: reads `.claude/skills/` from disk. Browser code should call
22
- * `configuredBuiltinSkills` from `@sweny-ai/core/browser` instead.
23
23
  */
24
24
  export declare function configuredSkills(env?: Record<string, string | undefined>, cwd?: string): Skill[];
@@ -1,58 +1,94 @@
1
1
  /**
2
2
  * Custom skill discovery — Node-only.
3
3
  *
4
- * Lives outside `skills/index.ts` so the registry stays browser-safe. Anything
5
- * that needs `node:fs` / `node:path` / `process.cwd()` belongs here.
4
+ * Scans multiple skill directories (multi-harness) for SKILL.md files,
5
+ * captures the markdown body as `instruction`, and parses optional `mcp`
6
+ * from frontmatter.
7
+ *
8
+ * Discovery priority (higher number wins on ID collision):
9
+ * .gemini/skills/ < .agents/skills/ < .claude/skills/ < .sweny/skills/
6
10
  */
7
11
  import { readdirSync, readFileSync, existsSync } from "node:fs";
8
12
  import { join } from "node:path";
9
13
  import YAML from "yaml";
10
14
  import { builtinSkills, isSkillConfigured } from "./index.js";
15
+ /** Directories to scan, in ascending priority order. Last match wins. */
16
+ const SKILL_DIRS = [".gemini/skills", ".agents/skills", ".claude/skills", ".sweny/skills"];
17
+ /** Valid skill ID: lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens, max 64 chars. */
18
+ const VALID_SKILL_ID = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
11
19
  /**
12
- * Load custom skills from a `.claude/skills/` directory.
13
- *
14
- * Custom skills are instruction-only (no tools or config) — they provide
15
- * guidance documents that the coding agent reads from the filesystem.
16
- * Registering them here ensures workflow validation recognises the skill IDs
17
- * so nodes referencing them don't produce spurious warnings.
20
+ * Discover custom skills from all known harness directories.
18
21
  */
19
- export function loadCustomSkills(cwd = process.cwd()) {
20
- const skillsDir = join(cwd, ".claude", "skills");
21
- if (!existsSync(skillsDir))
22
- return [];
23
- const skills = [];
24
- let entries;
25
- try {
26
- entries = readdirSync(skillsDir, { withFileTypes: true })
27
- .filter((d) => d.isDirectory())
28
- .map((d) => d.name);
29
- }
30
- catch {
31
- return [];
32
- }
33
- for (const dirName of entries) {
34
- const skillFile = join(skillsDir, dirName, "SKILL.md");
35
- if (!existsSync(skillFile))
22
+ export function discoverSkills(cwd = process.cwd()) {
23
+ const skillMap = new Map();
24
+ for (const relDir of SKILL_DIRS) {
25
+ const skillsDir = join(cwd, relDir);
26
+ if (!existsSync(skillsDir))
36
27
  continue;
28
+ let entries;
37
29
  try {
38
- const content = readFileSync(skillFile, "utf-8");
39
- const frontmatter = parseFrontmatter(content);
40
- if (!frontmatter?.name)
41
- continue;
42
- skills.push({
43
- id: frontmatter.name,
44
- name: frontmatter.name,
45
- description: frontmatter.description ?? `Custom skill: ${frontmatter.name}`,
46
- category: "general",
47
- config: {},
48
- tools: [],
49
- });
30
+ entries = readdirSync(skillsDir, { withFileTypes: true })
31
+ .filter((d) => d.isDirectory())
32
+ .map((d) => d.name);
50
33
  }
51
34
  catch {
52
- // Skip malformed skill files
35
+ continue;
36
+ }
37
+ for (const dirName of entries) {
38
+ const skillFile = join(skillsDir, dirName, "SKILL.md");
39
+ if (!existsSync(skillFile))
40
+ continue;
41
+ try {
42
+ const content = readFileSync(skillFile, "utf-8");
43
+ const parsed = parseSkillMd(content);
44
+ if (!parsed)
45
+ continue;
46
+ skillMap.set(parsed.id, parsed);
47
+ }
48
+ catch {
49
+ // Skip malformed skill files
50
+ }
51
+ }
52
+ }
53
+ return [...skillMap.values()];
54
+ }
55
+ /**
56
+ * Parse a SKILL.md file into a Skill object.
57
+ */
58
+ function parseSkillMd(content) {
59
+ const raw = parseFrontmatter(content);
60
+ if (!raw?.name)
61
+ return null;
62
+ // Narrow the loosely-typed YAML output into the fields we expect
63
+ const fm = raw;
64
+ const id = String(fm.name);
65
+ if (!VALID_SKILL_ID.test(id) || id.includes("--") || id.length > 64)
66
+ return null;
67
+ const instruction = extractBody(content);
68
+ let mcp;
69
+ if (fm.mcp && typeof fm.mcp === "object") {
70
+ const m = fm.mcp;
71
+ if (m.command || m.url) {
72
+ mcp = {
73
+ type: m.type ?? (m.command ? "stdio" : "http"),
74
+ ...(m.command ? { command: m.command } : {}),
75
+ ...(m.args ? { args: m.args } : {}),
76
+ ...(m.url ? { url: m.url } : {}),
77
+ ...(m.headers ? { headers: m.headers } : {}),
78
+ ...(m.env ? { env: m.env } : {}),
79
+ };
53
80
  }
54
81
  }
55
- return skills;
82
+ return {
83
+ id,
84
+ name: id,
85
+ description: typeof fm.description === "string" ? fm.description : `Custom skill: ${id}`,
86
+ category: "general",
87
+ config: {},
88
+ tools: [],
89
+ instruction: instruction || undefined,
90
+ mcp,
91
+ };
56
92
  }
57
93
  /** Extract YAML frontmatter from a markdown file (between --- delimiters). */
58
94
  function parseFrontmatter(content) {
@@ -66,23 +102,42 @@ function parseFrontmatter(content) {
66
102
  return null;
67
103
  }
68
104
  }
105
+ /** Extract the markdown body (everything after closing ---). */
106
+ function extractBody(content) {
107
+ const match = content.match(/^---\s*\n[\s\S]*?\n---\s*\n?([\s\S]*)/);
108
+ if (!match)
109
+ return "";
110
+ return match[1].trim();
111
+ }
112
+ /**
113
+ * Legacy API — load custom skills from `.claude/skills/` only.
114
+ * @deprecated Use `discoverSkills` for multi-directory support.
115
+ */
116
+ export function loadCustomSkills(cwd = process.cwd()) {
117
+ return discoverSkills(cwd);
118
+ }
69
119
  /**
70
120
  * From all builtins + custom repo skills, return only the skills that are usable.
71
- * Built-in skills are filtered by env vars; custom skills are always included.
72
- *
73
- * Node-only: reads `.claude/skills/` from disk. Browser code should call
74
- * `configuredBuiltinSkills` from `@sweny-ai/core/browser` instead.
75
121
  */
76
122
  export function configuredSkills(env = process.env, cwd) {
77
123
  const configured = builtinSkills.filter((s) => isSkillConfigured(s, env));
78
- const custom = loadCustomSkills(cwd);
79
- // Deduplicate: built-in skills take precedence over custom skills with the same ID
80
- const ids = new Set(configured.map((s) => s.id));
124
+ const custom = discoverSkills(cwd);
125
+ // Custom skills can override built-in IDs. When overriding, merge:
126
+ // keep the built-in's tools and config, add the custom instruction/mcp.
127
+ const configuredMap = new Map(configured.map((s) => [s.id, s]));
81
128
  for (const skill of custom) {
82
- if (!ids.has(skill.id)) {
83
- configured.push(skill);
84
- ids.add(skill.id);
129
+ const existing = configuredMap.get(skill.id);
130
+ if (existing) {
131
+ // Merge: built-in tools + custom instruction/mcp
132
+ configuredMap.set(skill.id, {
133
+ ...existing,
134
+ instruction: skill.instruction ?? existing.instruction,
135
+ mcp: skill.mcp ?? existing.mcp,
136
+ });
137
+ }
138
+ else {
139
+ configuredMap.set(skill.id, skill);
85
140
  }
86
141
  }
87
- return configured;
142
+ return [...configuredMap.values()];
88
143
  }
package/dist/types.d.ts CHANGED
@@ -30,6 +30,10 @@ export interface Skill {
30
30
  category: SkillCategory;
31
31
  config: Record<string, ConfigField>;
32
32
  tools: Tool[];
33
+ /** Natural language expertise injected into the node prompt when this skill is referenced. */
34
+ instruction?: string;
35
+ /** External MCP server definition wired for nodes referencing this skill. */
36
+ mcp?: McpServerConfig;
33
37
  }
34
38
  /** A node in the workflow DAG */
35
39
  export interface Node {
@@ -59,6 +63,19 @@ export interface Workflow {
59
63
  nodes: Record<string, Node>;
60
64
  edges: Edge[];
61
65
  entry: string;
66
+ skills?: Record<string, SkillDefinition>;
67
+ }
68
+ /**
69
+ * Inline skill definition in a workflow's `skills` block.
70
+ * Must provide at least `instruction` or `mcp`.
71
+ */
72
+ export interface SkillDefinition {
73
+ name?: string;
74
+ description?: string;
75
+ /** Natural language expertise injected into the node prompt. */
76
+ instruction?: string;
77
+ /** External MCP server. */
78
+ mcp?: McpServerConfig;
62
79
  }
63
80
  export interface NodeResult {
64
81
  status: "success" | "skipped" | "failed";
@@ -158,7 +175,7 @@ export interface Claude {
158
175
  }): Promise<string>;
159
176
  }
160
177
  export interface McpServerConfig {
161
- type: "stdio" | "http";
178
+ type?: "stdio" | "http";
162
179
  command?: string;
163
180
  args?: string[];
164
181
  url?: string;
@@ -1,3 +1,3 @@
1
- export const triageWorkflow = {"id":"triage","name":"Alert Triage","description":"Investigate a production alert, determine root cause, create an issue, implement a fix, and notify the team","entry":"prepare","nodes":{"prepare":{"name":"Load Rules & Context","instruction":"You are preparing for a triage workflow. Your job is to fetch and review any knowledge documents listed in the input.\n\n1. Check input for `rulesUrls`, `contextUrls`, `rules_urls`, and `context_urls` arrays (any of these may be present).\n2. For each URL, fetch its content:\n - Linear document URLs → use the Linear MCP tools (get_document or search_documentation)\n - Other HTTP URLs → fetch directly\n3. Summarize the key rules and context that downstream workflow nodes should follow.\n4. Output the consolidated information so it's available to all subsequent steps.\n\nIf there are no URLs to fetch, just pass through — this step is a no-op.","skills":["linear"]},"gather":{"name":"Gather Context","instruction":"You are investigating a production alert. Gather all relevant context using the available tools:\n\n1. **Observability**: Pull error details, stack traces, recent logs, metrics, and active incidents around the time of the alert. Use whichever observability tools are available to you.\n2. **Source control**: Check recent commits, pull requests, and deploys that might be related.\n3. **Issue tracker**: Search for similar past issues or known problems.\n\nBe thorough — the investigation step depends on complete context. Use every tool available to you.","skills":["github","sentry","datadog","betterstack","linear"]},"investigate":{"name":"Root Cause Analysis","instruction":"Based on the gathered context, classify every distinct issue you found into one of two buckets: **novel** or **duplicate**.\n\nFor EACH issue found:\n1. Identify the root cause and affected code/service.\n2. Assess severity: critical (service down), high (major feature broken), medium (degraded), low (cosmetic/minor).\n3. Assess fix complexity: \"simple\" (a few lines, clear change), \"moderate\" (multiple files but well-understood), or \"complex\" (architectural, risky, or unclear).\n4. **Novelty check (REQUIRED):** Filing a duplicate ticket is worse than under-reporting — the team inherits sprawl that humans have to clean up later. Complete EVERY step below before classifying anything as novel.\n4a. **Search broadly.** Run multiple queries against the issue tracker (BOTH open AND closed): the affected file or module path, the error message or log string verbatim, the feature or service name, and the symptom family (e.g. \"startup crash\", \"data dropped silently\"). Do not rely on a single keyword — a narrow search is how duplicates slip through.\n4b. **Read the full body of every promising candidate — do NOT match on title alone.** For each top match, open the issue and read its entire description, including any \"Recommended Fix\", \"Proposed Fix\", \"Action Items\", or \"Acceptance Criteria\" section. An existing open issue whose recommended fix already prescribes the change you were about to propose IS a duplicate, even if the symptom you just observed is a new instance of the same underlying bug.\n4c. **Check for meta-issues and bug families.** If an existing ticket names a pattern that your finding is another instance of (same root cause, same function/module, different input or caller), treat your finding as a sibling of that family and comment on the existing ticket. Do not file a parallel ticket per instance — that is the exact pattern that produces backlog sprawl.\n4d. **When uncertain, comment don't file.** If you are less than ~80% confident a finding is truly novel after steps 4a–4c, classify it as a duplicate and add a \"+1 — another instance\" comment to the closest existing issue with the new context. Over-reporting duplicates is cheap; ticket sprawl is expensive.\n\n - If matched → **duplicate**. Record the existing issue ID in `duplicate_of`.\n - If no match after 4a–4d → **novel**.\n\n**Output rules:**\n- `findings`: array of ALL issues found (both novel and duplicate).\n- `novel_count`: how many findings are novel (not duplicates).\n- `highest_severity`: the highest severity across ALL findings.\n- `recommendation`: what should happen next.\n\nDownstream nodes will act ONLY on novel findings. Duplicates will be +1'd automatically.","skills":["github","linear"],"output":{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"Short description of the issue"},"root_cause":{"type":"string"},"severity":{"type":"string","enum":["critical","high","medium","low"]},"affected_services":{"type":"array","items":{"type":"string"}},"is_duplicate":{"type":"boolean"},"duplicate_of":{"type":"string","description":"Existing issue ID/URL if duplicate"},"fix_approach":{"type":"string"},"fix_complexity":{"type":"string","enum":["simple","moderate","complex"]}},"required":["title","root_cause","severity","is_duplicate"]}},"novel_count":{"type":"number","description":"Count of novel (non-duplicate) findings"},"highest_severity":{"type":"string","enum":["critical","high","medium","low"]},"recommendation":{"type":"string"}},"required":["findings","novel_count","highest_severity","recommendation"]}},"create_issue":{"name":"Create Issues & Triage Duplicates","instruction":"Process ALL findings from the investigation. The findings array contains both novel and duplicate issues.\n\n**For each NOVEL finding** (is_duplicate = false):\n1. Create a new issue with a clear, actionable title.\n2. Include: root cause, severity, affected services, reproduction steps, and recommended fix.\n3. Add appropriate labels (bug, severity level, affected service).\n4. Link to relevant commits, PRs, or existing issues.\n\n**For each DUPLICATE finding** (is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the existing issue is closed/done, reopen it.\n\nIf context.issueTemplate is provided, use it as the format for new issue bodies. Otherwise use a clear structure with: Summary, Root Cause, Impact, Steps to Reproduce, and Recommended Fix.\n\nUse the issue tracker specified in context (e.g. Linear, Jira, or GitHub Issues) — do NOT fall back to a different tracker. Output the created/updated issue identifiers.","skills":["linear","github"]},"skip":{"name":"Skip — All Duplicates or Low Priority","instruction":"Every finding from the investigation was either a duplicate or low-priority. No new issues need to be created.\n\nFor each **duplicate** finding (check the findings array for items where is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the issue is closed/done, reopen it.\n\nFor **low priority** findings, log a brief note about why they were skipped.","skills":["linear","github"]},"implement":{"name":"Implement Fix","instruction":"Implement the fix identified during investigation:\n\n1. Create a feature branch from the base branch (check context for baseBranch, default \"main\").\n2. Read the relevant source files to understand the current code.\n3. Make the necessary code changes — fix the bug, nothing more.\n4. Run any existing tests if available to verify the fix doesn't break anything.\n5. Stage and commit with a clear commit message referencing the issue.\n\nKeep changes minimal and focused. Do not refactor surrounding code or add unrelated improvements.\n\nIf the fix turns out to be more complex than expected, stop and explain why — do not force a bad fix.","skills":["github"]},"create_pr":{"name":"Open Pull Request","instruction":"Open a pull request for the fix:\n\n1. Push the branch to the remote.\n2. Create a PR with a clear title referencing the issue (e.g. \"[OFF-1020] fix: guard empty pdf_texts before access\").\n3. In the PR body, include: summary of the bug, what the fix does, and a link to the issue.\n4. Add appropriate labels if available.\n\nIf context.prTemplate is provided, use it as the format for the PR body. Otherwise use a clear structure with: Summary, Changes, Testing, and Related Issues.\n\nIf context.cloudRunId is provided, append this HTML comment to the end of the PR body: <!-- sweny:run_id:{context.cloudRunId} -->\n\nReturn the PR URL and number.","skills":["github"]},"notify":{"name":"Notify Team","instruction":"Send a notification summarizing the triage result:\n\n1. Include: alert summary, severity, root cause (1-2 sentences), and links to any created issues or PRs.\n2. For critical/high severity, make the notification urgent.\n3. For medium/low, a standard notification is fine.\n\nUse whichever notification channel is available to you.","skills":["slack","notification"]}},"edges":[{"from":"prepare","to":"gather"},{"from":"gather","to":"investigate"},{"from":"investigate","to":"create_issue","when":"novel_count is greater than 0 AND highest_severity is medium or higher"},{"from":"investigate","to":"skip","when":"novel_count is 0, OR highest_severity is low"},{"from":"create_issue","to":"implement","when":"at least one novel finding has fix_complexity simple or moderate AND fix_approach is provided"},{"from":"create_issue","to":"notify","when":"all novel findings have fix_complexity complex, OR no clear fix_approach"},{"from":"skip","to":"notify"},{"from":"implement","to":"create_pr"},{"from":"create_pr","to":"notify"}]};
2
- export const implementWorkflow = {"id":"implement","name":"Implement Fix","description":"Implement a code fix for an issue and open a pull request","entry":"analyze","nodes":{"analyze":{"name":"Analyze Issue","instruction":"Read the issue details and understand what needs to be fixed:\n\n1. Fetch the issue from the tracker (GitHub or Linear).\n2. Read the relevant source files to understand the current code.\n3. Identify the exact files and lines that need to change.\n4. Plan the fix approach.\n\nOutput a clear analysis of what needs to change and why.","skills":["github","linear"],"output":{"type":"object","properties":{"issue_summary":{"type":"string"},"files_to_change":{"type":"array","items":{"type":"string"}},"fix_plan":{"type":"string"},"risk_level":{"type":"string","enum":["low","medium","high"]}},"required":["issue_summary","fix_plan"]}},"implement":{"name":"Implement Fix","instruction":"Implement the planned fix:\n\n1. Create a feature branch.\n2. Make the necessary code changes.\n3. Ensure changes are minimal and focused — fix the bug, nothing more.\n4. Stage and commit with a clear commit message referencing the issue.\n\nIf the fix is too risky or complex, explain why and skip.","skills":["github"]},"create_pr":{"name":"Open Pull Request","instruction":"Open a pull request for the fix:\n\n1. Push the branch to the remote.\n2. Create a PR with a clear title and description.\n3. Reference the original issue in the PR body.\n4. Add appropriate reviewers or labels if possible.\n\nIf context.prTemplate is provided, use it as the format for the PR body. Otherwise use a clear structure with: Summary, Changes, Testing, and Related Issues.\n\nIf context.cloudRunId is provided, append this HTML comment to the end of the PR body: <!-- sweny:run_id:{context.cloudRunId} -->\n\nReturn the PR URL.","skills":["github"]},"notify":{"name":"Notify","instruction":"Send a notification about the implementation result:\n\n1. Include: issue reference, PR link, brief summary of changes.\n2. Keep it concise — one message, not a wall of text.","skills":["slack","notification"]},"skip":{"name":"Skip — Too Complex","instruction":"The fix was determined to be too complex or risky for automated implementation.\nAdd a comment to the issue explaining what was found and why it needs manual attention.","skills":["github","linear"]}},"edges":[{"from":"analyze","to":"implement","when":"Fix risk level is low or medium and a clear plan exists"},{"from":"analyze","to":"skip","when":"Fix is too complex, risky, or unclear"},{"from":"implement","to":"create_pr"},{"from":"skip","to":"notify"},{"from":"create_pr","to":"notify"}]};
3
- export const seedContentWorkflow = {"id":"seed-content","name":"Content Generation Pipeline","description":"Audit content gaps, generate educational content, validate quality, and publish to Supabase","entry":"audit","nodes":{"audit":{"name":"Audit Content Gaps","instruction":"You are auditing an educational platform's content library.\n\nUse supabase_count to check how many items exist for each combination of:\n- Content types: worksheets, games, lessons\n- Subjects: math, science, english, history, geography, economics, music, art, life-skills, foreign-language\n- Difficulties: beginner, easy, medium, hard\n\nAlso check the input parameters — if the user specified a subject, topic, difficulty, and count,\nskip the full audit and just report what they want generated.\n\nReturn a structured content plan: what to generate, how many, at what difficulty and grade level.\nPrioritize gaps where a subject has ZERO content of a given type.","skills":["supabase"],"output":{"type":"object","properties":{"plan":{"type":"array","items":{"type":"object","properties":{"contentType":{"type":"string"},"subject":{"type":"string"},"topic":{"type":"string"},"difficulty":{"type":"string"},"gradeLevel":{"type":"string"},"count":{"type":"number"},"reason":{"type":"string"}},"required":["contentType","subject","topic","difficulty","count"]}},"summary":{"type":"string"}},"required":["plan","summary"]}},"generate":{"name":"Generate Content","instruction":"You are an expert educator creating content for children ages 5-14.\n\nFor each item in the content plan from the previous step, generate the content directly.\nThe content must be high-quality, engaging, age-appropriate, and educationally sound.\n\n**For worksheets:**\nGenerate a JSON array of problems. Each problem needs: \"problem\", \"answer\", \"format\" (answer-line | multiple-choice | write-on).\nRequirements:\n- Every answer MUST be correct. Double-check math, facts, spelling.\n- Problems must progress in difficulty within the set.\n- Use at least 2 different problem structures (e.g., equations + word problems for math).\n- Vary phrasing — no two problems should feel copy-pasted.\n- For the target grade level, use appropriate vocabulary and complexity.\n\n**For lessons:**\nGenerate complete HTML + CSS + JS for an interactive lesson.\nRequirements:\n- Must have substantial educational content (not just a shell with navigation).\n- Include interactive elements: quizzes, click-to-reveal, drag-and-drop.\n- Must be visually engaging with colors, icons, and clear typography.\n- Include practice exercises with immediate feedback.\n- Minimum 5 teaching sections plus 2 practice activities.\n\n**For games:**\nGenerate a description and educational requirements only (actual game code is generated\nby the platform's Phaser-based generator).\n\nUse supabase_insert to write each piece of content to the appropriate table.\n\nTable schemas:\n- worksheets: id (auto), title, subject, topic, difficulty, grade_level, num_questions, problems (jsonb), status='published', source='ai-seed'\n- lessons: id (auto), title, subject, difficulty, grade_level, lesson_type, html_code, css_code, js_code, status='published', source='ai-seed'\n- games: id (auto), title, subject, difficulty, grade_level, game_type, html_code, css_code, js_code, status='published', source='ai-seed'\n\nReturn a summary of what was generated and inserted.","skills":["supabase"],"output":{"type":"object","properties":{"generated":{"type":"array","items":{"type":"object","properties":{"contentType":{"type":"string"},"title":{"type":"string"},"subject":{"type":"string"},"id":{"type":"string"},"status":{"type":"string"}}}},"totalGenerated":{"type":"number"},"errors":{"type":"array","items":{"type":"string"}}},"required":["generated","totalGenerated"]}},"validate":{"name":"Validate Content Quality","instruction":"Review the content that was just generated and inserted.\n\nFor each item, use supabase_query to fetch it back and verify:\n\n**Worksheets:**\n- Parse the problems JSON — are all answers correct?\n- Are problems diverse (not repetitive)?\n- Is difficulty appropriate for the stated grade level?\n- Are there the right number of questions?\n\n**Lessons:**\n- Is the HTML substantial (> 500 characters)?\n- Does it contain actual educational content (not just navigation shells)?\n- Are there interactive elements?\n\n**Games:**\n- Does the HTML contain a working game structure?\n\nFlag any issues. If critical issues are found (wrong answers, empty content),\nuse supabase_update to set status='draft' and add a note.\n\nReturn a quality report.","skills":["supabase"],"output":{"type":"object","properties":{"totalReviewed":{"type":"number"},"passed":{"type":"number"},"flagged":{"type":"number"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"issue":{"type":"string"},"severity":{"type":"string"},"action":{"type":"string"}}}},"summary":{"type":"string"}},"required":["totalReviewed","passed","flagged","summary"]}},"report":{"name":"Generate Report","instruction":"Compile a final report of the content generation run.\n\nInclude:\n- Total content generated by type and subject\n- Quality validation results\n- Any issues found and actions taken\n- Recommendations for next content generation run (what gaps remain)\n\nFormat as a clean markdown summary that a solo operator can scan in 30 seconds.","skills":[],"output":{"type":"object","properties":{"markdown":{"type":"string"},"nextPriorities":{"type":"array","items":{"type":"string"}}},"required":["markdown"]}}},"edges":[{"from":"audit","to":"generate"},{"from":"generate","to":"validate"},{"from":"validate","to":"report"}]};
1
+ export const triageWorkflow = {"id":"triage","name":"Alert Triage","description":"Investigate a production alert, determine root cause, create an issue, implement a fix, and notify the team","entry":"prepare","nodes":{"prepare":{"name":"Load Rules & Context","instruction":"You are preparing for a triage workflow. Your job is to fetch and review any knowledge documents listed in the input.\n\n1. Check input for `rulesUrls`, `contextUrls`, `rules_urls`, and `context_urls` arrays (any of these may be present).\n2. For each URL, fetch its content:\n - Linear document URLs → use the Linear MCP tools (get_document or search_documentation)\n - Other HTTP URLs → fetch directly\n3. Summarize the key rules and context that downstream workflow nodes should follow.\n4. Output the consolidated information so it's available to all subsequent steps.\n\nIf there are no URLs to fetch, just pass through — this step is a no-op.","skills":["linear"]},"gather":{"name":"Gather Context","instruction":"You are investigating a production alert. Gather all relevant context using the available tools:\n\n1. **Observability**: Pull error details, stack traces, recent logs, metrics, and active incidents around the time of the alert. Use whichever observability tools are available to you.\n2. **Source control**: Check recent commits, pull requests, and deploys that might be related.\n3. **Issue tracker**: Search for similar past issues or known problems.\n\nBe thorough — the investigation step depends on complete context. Use every tool available to you.","skills":["github","sentry","datadog","betterstack","linear"]},"investigate":{"name":"Root Cause Analysis","instruction":"Based on the gathered context, classify every distinct issue you found into one of two buckets: **novel** or **duplicate**.\n\nFor EACH issue found:\n1. Identify the root cause and affected code/service.\n2. Assess severity: critical (service down), high (major feature broken), medium (degraded), low (cosmetic/minor).\n3. Assess fix complexity: \"simple\" (a few lines, clear change), \"moderate\" (multiple files but well-understood), or \"complex\" (architectural, risky, or unclear).\n4. **Novelty check (REQUIRED):** Filing a duplicate ticket is worse than under-reporting — the team inherits sprawl that humans have to clean up later. Complete EVERY step below before classifying anything as novel.\n4a. **Search broadly.** Run multiple queries against the issue tracker (BOTH open AND closed): the affected file or module path, the error message or log string verbatim, the feature or service name, and the symptom family (e.g. \"startup crash\", \"data dropped silently\"). Do not rely on a single keyword — a narrow search is how duplicates slip through.\n4b. **Read the full body of every promising candidate — do NOT match on title alone.** For each top match, open the issue and read its entire description, including any \"Recommended Fix\", \"Proposed Fix\", \"Action Items\", or \"Acceptance Criteria\" section. An existing open issue whose recommended fix already prescribes the change you were about to propose IS a duplicate, even if the symptom you just observed is a new instance of the same underlying bug.\n4c. **Check for meta-issues and bug families.** If an existing ticket names a pattern that your finding is another instance of (same root cause, same function/module, different input or caller), treat your finding as a sibling of that family and comment on the existing ticket. Do not file a parallel ticket per instance — that is the exact pattern that produces backlog sprawl.\n4d. **When uncertain, comment don't file.** If you are less than ~80% confident a finding is truly novel after steps 4a–4c, classify it as a duplicate and add a \"+1 — another instance\" comment to the closest existing issue with the new context. Over-reporting duplicates is cheap; ticket sprawl is expensive.\n\n - If matched → **duplicate**. Record the existing issue ID in `duplicate_of`.\n - If no match after 4a–4d → **novel**.\n\n**Output rules:**\n- `findings`: array of ALL issues found (both novel and duplicate).\n- `novel_count`: how many findings are novel (not duplicates).\n- `highest_severity`: the highest severity across ALL findings.\n- `recommendation`: what should happen next.\n\nDownstream nodes will act ONLY on novel findings. Duplicates will be +1'd automatically.","skills":["github","linear"],"output":{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"title":{"type":"string","description":"Short description of the issue"},"root_cause":{"type":"string"},"severity":{"type":"string","enum":["critical","high","medium","low"]},"affected_services":{"type":"array","items":{"type":"string"}},"is_duplicate":{"type":"boolean"},"duplicate_of":{"type":"string","description":"Existing issue ID/URL if duplicate"},"fix_approach":{"type":"string"},"fix_complexity":{"type":"string","enum":["simple","moderate","complex"]}},"required":["title","root_cause","severity","is_duplicate"]}},"novel_count":{"type":"number","description":"Count of novel (non-duplicate) findings"},"highest_severity":{"type":"string","enum":["critical","high","medium","low"]},"recommendation":{"type":"string"}},"required":["findings","novel_count","highest_severity","recommendation"]}},"create_issue":{"name":"Create Issues & Triage Duplicates","instruction":"Process ALL findings from the investigation. The findings array contains both novel and duplicate issues.\n\n**For each NOVEL finding** (is_duplicate = false):\n1. Create a new issue with a clear, actionable title.\n2. Include: root cause, severity, affected services, reproduction steps, and recommended fix.\n3. Add appropriate labels (bug, severity level, affected service).\n4. Link to relevant commits, PRs, or existing issues.\n\n**For each DUPLICATE finding** (is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the existing issue is closed/done, reopen it.\n\nIf context.issueTemplate is provided, use it as the format for new issue bodies. Otherwise use a clear structure with: Summary, Root Cause, Impact, Steps to Reproduce, and Recommended Fix.\n\nUse the issue tracker specified in context (e.g. Linear, Jira, or GitHub Issues) — do NOT fall back to a different tracker. Output the created/updated issue identifiers.","skills":["linear","github"]},"skip":{"name":"Skip — All Duplicates or Low Priority","instruction":"Every finding from the investigation was either a duplicate or low-priority. No new issues need to be created.\n\nFor each **duplicate** finding (check the findings array for items where is_duplicate = true):\n1. Find the existing issue (check duplicate_of field).\n2. Check the issue's comments — if the most recent comment is already from SWEny (contains \"+1\") within the last 24 hours, skip adding another comment.\n3. Otherwise add a SHORT comment: \"+1 — seen again {UTC timestamp}. {one sentence of new context}.\" (Keep it under 2 lines. No markdown headers, no emoji, no formatting.)\n4. If the issue is closed/done, reopen it.\n\nFor **low priority** findings, log a brief note about why they were skipped.","skills":["linear","github"]},"implement":{"name":"Implement Fix","instruction":"Implement the fix identified during investigation:\n\n1. Create a feature branch from the base branch (check context for baseBranch, default \"main\").\n2. Read the relevant source files to understand the current code.\n3. Make the necessary code changes — fix the bug, nothing more.\n4. Run any existing tests if available to verify the fix doesn't break anything.\n5. Stage and commit with a clear commit message referencing the issue.\n\nKeep changes minimal and focused. Do not refactor surrounding code or add unrelated improvements.\n\nIf the fix turns out to be more complex than expected, stop and explain why — do not force a bad fix.","skills":["github"]},"create_pr":{"name":"Open Pull Request","instruction":"Open a pull request for the fix:\n\n1. Push the branch to the remote.\n2. Create a PR with a clear title referencing the issue (e.g. \"[OFF-1020] fix: guard empty pdf_texts before access\").\n3. In the PR body, include: summary of the bug, what the fix does, and a link to the issue.\n4. Add appropriate labels if available.\n\nIf context.prTemplate is provided, use it as the format for the PR body. Otherwise use a clear structure with: Summary, Changes, Testing, and Related Issues.\n\nIf context.cloudRunId is provided, append this HTML comment to the end of the PR body: <!-- sweny:run_id:{context.cloudRunId} -->\n\nReturn the PR URL and number.","skills":["github"]},"notify":{"name":"Notify Team","instruction":"Send a notification summarizing the triage result:\n\n1. Include: alert summary, severity, root cause (1-2 sentences), and links to any created issues or PRs.\n2. For critical/high severity, make the notification urgent.\n3. For medium/low, a standard notification is fine.\n\nUse whichever notification channel is available to you.","skills":["slack","notification"]}},"edges":[{"from":"prepare","to":"gather"},{"from":"gather","to":"investigate"},{"from":"investigate","to":"create_issue","when":"novel_count is greater than 0 AND highest_severity is medium or higher"},{"from":"investigate","to":"skip","when":"novel_count is 0, OR highest_severity is low"},{"from":"create_issue","to":"implement","when":"at least one novel finding has fix_complexity simple or moderate AND fix_approach is provided"},{"from":"create_issue","to":"notify","when":"all novel findings have fix_complexity complex, OR no clear fix_approach"},{"from":"skip","to":"notify"},{"from":"implement","to":"create_pr"},{"from":"create_pr","to":"notify"}],"skills":{}};
2
+ export const implementWorkflow = {"id":"implement","name":"Implement Fix","description":"Implement a code fix for an issue and open a pull request","entry":"analyze","nodes":{"analyze":{"name":"Analyze Issue","instruction":"Read the issue details and understand what needs to be fixed:\n\n1. Fetch the issue from the tracker (GitHub or Linear).\n2. Read the relevant source files to understand the current code.\n3. Identify the exact files and lines that need to change.\n4. Plan the fix approach.\n\nOutput a clear analysis of what needs to change and why.","skills":["github","linear"],"output":{"type":"object","properties":{"issue_summary":{"type":"string"},"files_to_change":{"type":"array","items":{"type":"string"}},"fix_plan":{"type":"string"},"risk_level":{"type":"string","enum":["low","medium","high"]}},"required":["issue_summary","fix_plan"]}},"implement":{"name":"Implement Fix","instruction":"Implement the planned fix:\n\n1. Create a feature branch.\n2. Make the necessary code changes.\n3. Ensure changes are minimal and focused — fix the bug, nothing more.\n4. Stage and commit with a clear commit message referencing the issue.\n\nIf the fix is too risky or complex, explain why and skip.","skills":["github"]},"create_pr":{"name":"Open Pull Request","instruction":"Open a pull request for the fix:\n\n1. Push the branch to the remote.\n2. Create a PR with a clear title and description.\n3. Reference the original issue in the PR body.\n4. Add appropriate reviewers or labels if possible.\n\nIf context.prTemplate is provided, use it as the format for the PR body. Otherwise use a clear structure with: Summary, Changes, Testing, and Related Issues.\n\nIf context.cloudRunId is provided, append this HTML comment to the end of the PR body: <!-- sweny:run_id:{context.cloudRunId} -->\n\nReturn the PR URL.","skills":["github"]},"notify":{"name":"Notify","instruction":"Send a notification about the implementation result:\n\n1. Include: issue reference, PR link, brief summary of changes.\n2. Keep it concise — one message, not a wall of text.","skills":["slack","notification"]},"skip":{"name":"Skip — Too Complex","instruction":"The fix was determined to be too complex or risky for automated implementation.\nAdd a comment to the issue explaining what was found and why it needs manual attention.","skills":["github","linear"]}},"edges":[{"from":"analyze","to":"implement","when":"Fix risk level is low or medium and a clear plan exists"},{"from":"analyze","to":"skip","when":"Fix is too complex, risky, or unclear"},{"from":"implement","to":"create_pr"},{"from":"skip","to":"notify"},{"from":"create_pr","to":"notify"}],"skills":{}};
3
+ export const seedContentWorkflow = {"id":"seed-content","name":"Content Generation Pipeline","description":"Audit content gaps, generate educational content, validate quality, and publish to Supabase","entry":"audit","nodes":{"audit":{"name":"Audit Content Gaps","instruction":"You are auditing an educational platform's content library.\n\nUse supabase_count to check how many items exist for each combination of:\n- Content types: worksheets, games, lessons\n- Subjects: math, science, english, history, geography, economics, music, art, life-skills, foreign-language\n- Difficulties: beginner, easy, medium, hard\n\nAlso check the input parameters — if the user specified a subject, topic, difficulty, and count,\nskip the full audit and just report what they want generated.\n\nReturn a structured content plan: what to generate, how many, at what difficulty and grade level.\nPrioritize gaps where a subject has ZERO content of a given type.","skills":["supabase"],"output":{"type":"object","properties":{"plan":{"type":"array","items":{"type":"object","properties":{"contentType":{"type":"string"},"subject":{"type":"string"},"topic":{"type":"string"},"difficulty":{"type":"string"},"gradeLevel":{"type":"string"},"count":{"type":"number"},"reason":{"type":"string"}},"required":["contentType","subject","topic","difficulty","count"]}},"summary":{"type":"string"}},"required":["plan","summary"]}},"generate":{"name":"Generate Content","instruction":"You are an expert educator creating content for children ages 5-14.\n\nFor each item in the content plan from the previous step, generate the content directly.\nThe content must be high-quality, engaging, age-appropriate, and educationally sound.\n\n**For worksheets:**\nGenerate a JSON array of problems. Each problem needs: \"problem\", \"answer\", \"format\" (answer-line | multiple-choice | write-on).\nRequirements:\n- Every answer MUST be correct. Double-check math, facts, spelling.\n- Problems must progress in difficulty within the set.\n- Use at least 2 different problem structures (e.g., equations + word problems for math).\n- Vary phrasing — no two problems should feel copy-pasted.\n- For the target grade level, use appropriate vocabulary and complexity.\n\n**For lessons:**\nGenerate complete HTML + CSS + JS for an interactive lesson.\nRequirements:\n- Must have substantial educational content (not just a shell with navigation).\n- Include interactive elements: quizzes, click-to-reveal, drag-and-drop.\n- Must be visually engaging with colors, icons, and clear typography.\n- Include practice exercises with immediate feedback.\n- Minimum 5 teaching sections plus 2 practice activities.\n\n**For games:**\nGenerate a description and educational requirements only (actual game code is generated\nby the platform's Phaser-based generator).\n\nUse supabase_insert to write each piece of content to the appropriate table.\n\nTable schemas:\n- worksheets: id (auto), title, subject, topic, difficulty, grade_level, num_questions, problems (jsonb), status='published', source='ai-seed'\n- lessons: id (auto), title, subject, difficulty, grade_level, lesson_type, html_code, css_code, js_code, status='published', source='ai-seed'\n- games: id (auto), title, subject, difficulty, grade_level, game_type, html_code, css_code, js_code, status='published', source='ai-seed'\n\nReturn a summary of what was generated and inserted.","skills":["supabase"],"output":{"type":"object","properties":{"generated":{"type":"array","items":{"type":"object","properties":{"contentType":{"type":"string"},"title":{"type":"string"},"subject":{"type":"string"},"id":{"type":"string"},"status":{"type":"string"}}}},"totalGenerated":{"type":"number"},"errors":{"type":"array","items":{"type":"string"}}},"required":["generated","totalGenerated"]}},"validate":{"name":"Validate Content Quality","instruction":"Review the content that was just generated and inserted.\n\nFor each item, use supabase_query to fetch it back and verify:\n\n**Worksheets:**\n- Parse the problems JSON — are all answers correct?\n- Are problems diverse (not repetitive)?\n- Is difficulty appropriate for the stated grade level?\n- Are there the right number of questions?\n\n**Lessons:**\n- Is the HTML substantial (> 500 characters)?\n- Does it contain actual educational content (not just navigation shells)?\n- Are there interactive elements?\n\n**Games:**\n- Does the HTML contain a working game structure?\n\nFlag any issues. If critical issues are found (wrong answers, empty content),\nuse supabase_update to set status='draft' and add a note.\n\nReturn a quality report.","skills":["supabase"],"output":{"type":"object","properties":{"totalReviewed":{"type":"number"},"passed":{"type":"number"},"flagged":{"type":"number"},"issues":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"issue":{"type":"string"},"severity":{"type":"string"},"action":{"type":"string"}}}},"summary":{"type":"string"}},"required":["totalReviewed","passed","flagged","summary"]}},"report":{"name":"Generate Report","instruction":"Compile a final report of the content generation run.\n\nInclude:\n- Total content generated by type and subject\n- Quality validation results\n- Any issues found and actions taken\n- Recommendations for next content generation run (what gaps remain)\n\nFormat as a clean markdown summary that a solo operator can scan in 30 seconds.","skills":[],"output":{"type":"object","properties":{"markdown":{"type":"string"},"nextPriorities":{"type":"array","items":{"type":"string"}}},"required":["markdown"]}}},"edges":[{"from":"audit","to":"generate"},{"from":"generate","to":"validate"},{"from":"validate","to":"report"}],"skills":{}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.51",
3
+ "version": "0.1.52",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"
@@ -14,7 +14,8 @@
14
14
  "./skills": "./dist/skills/index.js",
15
15
  "./workflows": "./dist/workflows/index.js",
16
16
  "./testing": "./dist/testing.js",
17
- "./schema": "./dist/schema.js"
17
+ "./schema": "./dist/schema.js",
18
+ "./init": "./dist/cli/init.js"
18
19
  },
19
20
  "typesVersions": {
20
21
  "*": {
@@ -35,6 +36,9 @@
35
36
  ],
36
37
  "schema": [
37
38
  "dist/schema.d.ts"
39
+ ],
40
+ "init": [
41
+ "dist/cli/init.d.ts"
38
42
  ]
39
43
  }
40
44
  },