@sweny-ai/core 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/setup.js CHANGED
@@ -120,6 +120,7 @@ async function createLinearLabel(apiKey, teamId, def, parentId) {
120
120
  color: `#${def.color}`,
121
121
  description: def.description,
122
122
  ...(parentId ? { parentId } : {}),
123
+ ...(def === AGENT_PARENT ? { isGroup: true } : {}),
123
124
  },
124
125
  });
125
126
  if (!data.issueLabelCreate.success)
package/dist/executor.js CHANGED
@@ -46,9 +46,14 @@ export async function execute(workflow, input, options) {
46
46
  return output;
47
47
  },
48
48
  }));
49
+ // Prepend additional context to instruction if provided
50
+ const additionalContext = typeof input?.additionalContext === "string" ? input.additionalContext : "";
51
+ const instruction = additionalContext
52
+ ? `## Additional Context & Rules\n\n${additionalContext}\n\n---\n\n${node.instruction}`
53
+ : node.instruction;
49
54
  // Run Claude on this node
50
55
  const result = await claude.run({
51
- instruction: node.instruction,
56
+ instruction,
52
57
  context,
53
58
  tools: trackedTools,
54
59
  outputSchema: node.output,
@@ -166,11 +171,13 @@ function validate(workflow, skills) {
166
171
  if (!workflow.nodes[edge.to])
167
172
  throw new Error(`Edge references unknown node: "${edge.to}"`);
168
173
  }
169
- // Warn about missing skills (non-fatal skills might be registered later)
170
- const allSkillIds = new Set(Object.values(workflow.nodes).flatMap((n) => n.skills));
171
- for (const id of allSkillIds) {
172
- if (!skills.has(id)) {
173
- consoleLogger.warn(`Workflow references unregistered skill: "${id}"`);
174
+ // Check that each node has at least one available skill (if it lists any)
175
+ for (const [nodeId, node] of Object.entries(workflow.nodes)) {
176
+ if (node.skills.length === 0)
177
+ continue;
178
+ const available = node.skills.filter((id) => skills.has(id));
179
+ if (available.length === 0) {
180
+ consoleLogger.warn(`Node "${nodeId}" has no available skills (needs one of: ${node.skills.join(", ")})`);
174
181
  }
175
182
  }
176
183
  }
package/dist/index.d.ts CHANGED
@@ -35,3 +35,5 @@ export { buildAutoMcpServers } from "./mcp.js";
35
35
  export type { McpServerConfig, McpAutoConfig } from "./types.js";
36
36
  export { buildWorkflow, refineWorkflow } from "./workflow-builder.js";
37
37
  export type { BuildWorkflowOptions } from "./workflow-builder.js";
38
+ export { resolveTemplates, loadAdditionalContext, loadTemplate } from "./templates.js";
39
+ export type { Templates } from "./templates.js";
package/dist/index.js CHANGED
@@ -34,3 +34,5 @@ export { workflowZ, nodeZ, edgeZ, skillZ, parseWorkflow, validateWorkflow, workf
34
34
  export { buildAutoMcpServers } from "./mcp.js";
35
35
  // Workflow builder
36
36
  export { buildWorkflow, refineWorkflow } from "./workflow-builder.js";
37
+ // Templates
38
+ export { resolveTemplates, loadAdditionalContext, loadTemplate } from "./templates.js";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Template loading for issues and PRs.
3
+ *
4
+ * Templates can come from:
5
+ * 1. Local file path (relative to repo root)
6
+ * 2. URL (fetched at runtime)
7
+ * 3. Built-in defaults
8
+ */
9
+ export declare const DEFAULT_ISSUE_TEMPLATE = "## Summary\n<!-- One-line description of the issue -->\n\n## Root Cause\n<!-- What caused this issue -->\n\n## Impact\n- **Severity**: <!-- critical / high / medium / low -->\n- **Affected Services**: <!-- list -->\n- **User Impact**: <!-- description -->\n\n## Steps to Reproduce\n1. ...\n\n## Recommended Fix\n<!-- Proposed solution -->\n\n## Related\n- Commits: <!-- relevant commits -->\n- PRs: <!-- related PRs -->\n";
10
+ export declare const DEFAULT_PR_TEMPLATE = "## Summary\n<!-- What does this PR do? -->\n\n## Root Cause\n<!-- What caused the issue this fixes? -->\n\n## Changes\n<!-- Bullet list of changes -->\n\n## Testing\n- [ ] Tested locally\n- [ ] No breaking changes\n\n## Related Issues\nFixes #\n";
11
+ export interface Templates {
12
+ issueTemplate: string;
13
+ prTemplate: string;
14
+ }
15
+ /**
16
+ * Load a template from a file path or URL.
17
+ * Returns the default if source is empty or loading fails.
18
+ */
19
+ export declare function loadTemplate(source: string | undefined, fallback: string, cwd?: string): Promise<string>;
20
+ /**
21
+ * Resolve issue and PR templates from config.
22
+ */
23
+ export declare function resolveTemplates(config: {
24
+ issueTemplate?: string;
25
+ prTemplate?: string;
26
+ }, cwd?: string): Promise<Templates>;
27
+ /**
28
+ * Load additional context documents (local files or URLs).
29
+ * Each source is loaded and wrapped with a header.
30
+ */
31
+ export declare function loadAdditionalContext(sources: string[], cwd?: string): Promise<string>;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Template loading for issues and PRs.
3
+ *
4
+ * Templates can come from:
5
+ * 1. Local file path (relative to repo root)
6
+ * 2. URL (fetched at runtime)
7
+ * 3. Built-in defaults
8
+ */
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ export const DEFAULT_ISSUE_TEMPLATE = `## Summary
12
+ <!-- One-line description of the issue -->
13
+
14
+ ## Root Cause
15
+ <!-- What caused this issue -->
16
+
17
+ ## Impact
18
+ - **Severity**: <!-- critical / high / medium / low -->
19
+ - **Affected Services**: <!-- list -->
20
+ - **User Impact**: <!-- description -->
21
+
22
+ ## Steps to Reproduce
23
+ 1. ...
24
+
25
+ ## Recommended Fix
26
+ <!-- Proposed solution -->
27
+
28
+ ## Related
29
+ - Commits: <!-- relevant commits -->
30
+ - PRs: <!-- related PRs -->
31
+ `;
32
+ export const DEFAULT_PR_TEMPLATE = `## Summary
33
+ <!-- What does this PR do? -->
34
+
35
+ ## Root Cause
36
+ <!-- What caused the issue this fixes? -->
37
+
38
+ ## Changes
39
+ <!-- Bullet list of changes -->
40
+
41
+ ## Testing
42
+ - [ ] Tested locally
43
+ - [ ] No breaking changes
44
+
45
+ ## Related Issues
46
+ Fixes #
47
+ `;
48
+ /**
49
+ * Load a template from a file path or URL.
50
+ * Returns the default if source is empty or loading fails.
51
+ */
52
+ export async function loadTemplate(source, fallback, cwd = process.cwd()) {
53
+ if (!source || source.trim() === "")
54
+ return fallback;
55
+ const trimmed = source.trim();
56
+ // URL
57
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
58
+ try {
59
+ const res = await fetch(trimmed, { signal: AbortSignal.timeout(10_000) });
60
+ if (!res.ok) {
61
+ console.warn(`[templates] Failed to fetch ${trimmed} (HTTP ${res.status}), using default`);
62
+ return fallback;
63
+ }
64
+ return await res.text();
65
+ }
66
+ catch (err) {
67
+ console.warn(`[templates] Failed to fetch ${trimmed}: ${err.message}, using default`);
68
+ return fallback;
69
+ }
70
+ }
71
+ // Local file
72
+ const resolved = path.resolve(cwd, trimmed);
73
+ try {
74
+ return fs.readFileSync(resolved, "utf-8");
75
+ }
76
+ catch {
77
+ console.warn(`[templates] Template file not found: ${resolved}, using default`);
78
+ return fallback;
79
+ }
80
+ }
81
+ /**
82
+ * Resolve issue and PR templates from config.
83
+ */
84
+ export async function resolveTemplates(config, cwd) {
85
+ const [issueTemplate, prTemplate] = await Promise.all([
86
+ loadTemplate(config.issueTemplate, DEFAULT_ISSUE_TEMPLATE, cwd),
87
+ loadTemplate(config.prTemplate, DEFAULT_PR_TEMPLATE, cwd),
88
+ ]);
89
+ return { issueTemplate, prTemplate };
90
+ }
91
+ /**
92
+ * Load additional context documents (local files or URLs).
93
+ * Each source is loaded and wrapped with a header.
94
+ */
95
+ export async function loadAdditionalContext(sources, cwd = process.cwd()) {
96
+ if (sources.length === 0)
97
+ return "";
98
+ const parts = [];
99
+ for (const source of sources) {
100
+ const trimmed = source.trim();
101
+ if (!trimmed)
102
+ continue;
103
+ const label = trimmed.startsWith("http") ? trimmed : path.basename(trimmed);
104
+ const content = await loadTemplate(trimmed, "", cwd);
105
+ if (content) {
106
+ parts.push(`### ${label}\n\n${content}`);
107
+ }
108
+ }
109
+ return parts.length > 0 ? parts.join("\n\n---\n\n") : "";
110
+ }
@@ -56,6 +56,8 @@ If the fix is too risky or complex, explain why and skip.`,
56
56
  3. Reference the original issue in the PR body.
57
57
  4. Add appropriate reviewers or labels if possible.
58
58
 
59
+ If 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.
60
+
59
61
  Return the PR URL.`,
60
62
  skills: ["github"],
61
63
  },
@@ -41,7 +41,15 @@ Be thorough — the investigation step depends on complete context. Use every to
41
41
  4. Determine affected services and users.
42
42
  5. Recommend a fix approach.
43
43
 
44
- **Novelty check (REQUIRED):** Search the issue tracker for existing open issues that cover the same root cause. Use github_search_issues and/or linear_search_issues with relevant keywords. If you find an existing issue that matches, set is_duplicate=true and duplicate_of to the issue identifier (e.g. "#42" or "ENG-123").`,
44
+ **Novelty check (REQUIRED you MUST do this before finishing):**
45
+ Search the issue tracker for existing issues (BOTH open AND closed) that cover the same root cause, error pattern, or affected service. Use github_search_issues and/or linear_search_issues with multiple keyword variations.
46
+
47
+ A match means ANY of:
48
+ - An issue about the same root cause (even if closed/fixed)
49
+ - An issue about the same error message or pattern in the same service
50
+ - An issue that a human would consider "the same bug"
51
+
52
+ Set is_duplicate=true if ANY match is found. Set is_duplicate=false ONLY if you searched and found zero matches. You MUST always set this field.`,
45
53
  skills: ["github", "linear"],
46
54
  output: {
47
55
  type: "object",
@@ -50,26 +58,27 @@ Be thorough — the investigation step depends on complete context. Use every to
50
58
  severity: { type: "string", enum: ["critical", "high", "medium", "low"] },
51
59
  affected_services: { type: "array", items: { type: "string" } },
52
60
  is_duplicate: { type: "boolean" },
53
- duplicate_of: { type: "string", description: "Issue ID if duplicate" },
61
+ duplicate_of: { type: "string", description: "Issue ID/URL if duplicate" },
54
62
  recommendation: { type: "string" },
55
63
  fix_approach: { type: "string" },
56
64
  },
57
- required: ["root_cause", "severity", "recommendation"],
65
+ required: ["root_cause", "severity", "is_duplicate", "recommendation"],
58
66
  },
59
67
  },
60
68
  create_issue: {
61
- name: "Create or Update Issue",
62
- instruction: `Before creating anything, check whether this root cause is already tracked:
69
+ name: "Create Issue",
70
+ instruction: `Create an issue documenting the investigation findings:
71
+
72
+ 1. Use a clear, actionable title.
73
+ 2. Include: root cause, severity, affected services, reproduction steps, and recommended fix.
74
+ 3. Add appropriate labels (bug, severity level, affected service).
75
+ 4. Link to relevant commits, PRs, or existing issues.
76
+
77
+ **Safety check**: If during creation you notice a very similar issue already exists, add a comment to it using github_add_comment or linear_add_comment instead of creating a duplicate.
63
78
 
64
- 1. Search for existing open issues using github_search_issues and/or linear_search_issues with keywords from the root cause, affected service, and error message.
65
- 2. **If a matching issue exists**: Add a comment to it (using github_add_comment or linear_add_comment) noting this re-occurrence with the current timestamp and any new context. Do NOT create a new issue. Return the existing issue's identifier and URL.
66
- 3. **If no matching issue exists**: Create a new issue with:
67
- - A clear, actionable title
68
- - Root cause, severity, affected services, reproduction steps, and recommended fix
69
- - Appropriate labels (bug, severity level, affected service)
70
- - Links to relevant commits, PRs, or existing issues
79
+ If context.issueTemplate is provided, use it as the format for the issue body. Otherwise use a clear structure with: Summary, Root Cause, Impact, Steps to Reproduce, and Recommended Fix.
71
80
 
72
- Use whichever tracker is available to you.`,
81
+ Create the issue in whichever tracker is available to you.`,
73
82
  skills: ["linear", "github"],
74
83
  },
75
84
  notify: {
@@ -97,13 +106,13 @@ Log a brief note about why it was skipped. No further action needed.`,
97
106
  {
98
107
  from: "investigate",
99
108
  to: "create_issue",
100
- when: "The issue is novel (not a duplicate) and severity is medium or higher",
109
+ when: "is_duplicate is false AND severity is medium or higher",
101
110
  },
102
111
  // investigate → skip (if duplicate or low priority)
103
112
  {
104
113
  from: "investigate",
105
114
  to: "skip",
106
- when: "The issue is a duplicate of an existing ticket, or severity is low",
115
+ when: "is_duplicate is true, OR severity is low",
107
116
  },
108
117
  // create_issue → notify (always)
109
118
  { from: "create_issue", to: "notify" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"