@sweny-ai/core 0.1.0

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 (93) hide show
  1. package/dist/__tests__/claude.test.d.ts +1 -0
  2. package/dist/__tests__/claude.test.js +328 -0
  3. package/dist/__tests__/executor.test.d.ts +1 -0
  4. package/dist/__tests__/executor.test.js +296 -0
  5. package/dist/__tests__/integration/datadog.integration.test.d.ts +1 -0
  6. package/dist/__tests__/integration/datadog.integration.test.js +23 -0
  7. package/dist/__tests__/integration/e2e-workflow.integration.test.d.ts +1 -0
  8. package/dist/__tests__/integration/e2e-workflow.integration.test.js +75 -0
  9. package/dist/__tests__/integration/github.integration.test.d.ts +1 -0
  10. package/dist/__tests__/integration/github.integration.test.js +37 -0
  11. package/dist/__tests__/integration/harness.d.ts +24 -0
  12. package/dist/__tests__/integration/harness.js +34 -0
  13. package/dist/__tests__/integration/linear.integration.test.d.ts +1 -0
  14. package/dist/__tests__/integration/linear.integration.test.js +15 -0
  15. package/dist/__tests__/integration/sentry.integration.test.d.ts +1 -0
  16. package/dist/__tests__/integration/sentry.integration.test.js +20 -0
  17. package/dist/__tests__/integration/slack.integration.test.d.ts +1 -0
  18. package/dist/__tests__/integration/slack.integration.test.js +22 -0
  19. package/dist/__tests__/schema.test.d.ts +1 -0
  20. package/dist/__tests__/schema.test.js +239 -0
  21. package/dist/__tests__/skills-index.test.d.ts +1 -0
  22. package/dist/__tests__/skills-index.test.js +122 -0
  23. package/dist/__tests__/skills.test.d.ts +1 -0
  24. package/dist/__tests__/skills.test.js +296 -0
  25. package/dist/__tests__/studio.test.d.ts +1 -0
  26. package/dist/__tests__/studio.test.js +172 -0
  27. package/dist/__tests__/testing.test.d.ts +1 -0
  28. package/dist/__tests__/testing.test.js +224 -0
  29. package/dist/browser.d.ts +17 -0
  30. package/dist/browser.js +22 -0
  31. package/dist/claude.d.ts +48 -0
  32. package/dist/claude.js +293 -0
  33. package/dist/cli/check.d.ts +11 -0
  34. package/dist/cli/check.js +237 -0
  35. package/dist/cli/config-file.d.ts +12 -0
  36. package/dist/cli/config-file.js +208 -0
  37. package/dist/cli/config.d.ts +77 -0
  38. package/dist/cli/config.js +565 -0
  39. package/dist/cli/main.d.ts +10 -0
  40. package/dist/cli/main.js +744 -0
  41. package/dist/cli/output.d.ts +26 -0
  42. package/dist/cli/output.js +357 -0
  43. package/dist/cli/renderer.d.ts +33 -0
  44. package/dist/cli/renderer.js +423 -0
  45. package/dist/cli/renderer.test.d.ts +1 -0
  46. package/dist/cli/renderer.test.js +302 -0
  47. package/dist/cli/setup.d.ts +11 -0
  48. package/dist/cli/setup.js +310 -0
  49. package/dist/executor.d.ts +29 -0
  50. package/dist/executor.js +173 -0
  51. package/dist/executor.test.d.ts +1 -0
  52. package/dist/executor.test.js +314 -0
  53. package/dist/index.d.ts +37 -0
  54. package/dist/index.js +36 -0
  55. package/dist/mcp.d.ts +11 -0
  56. package/dist/mcp.js +183 -0
  57. package/dist/mcp.test.d.ts +1 -0
  58. package/dist/mcp.test.js +334 -0
  59. package/dist/schema.d.ts +318 -0
  60. package/dist/schema.js +207 -0
  61. package/dist/skills/betterstack.d.ts +7 -0
  62. package/dist/skills/betterstack.js +114 -0
  63. package/dist/skills/datadog.d.ts +7 -0
  64. package/dist/skills/datadog.js +107 -0
  65. package/dist/skills/github.d.ts +8 -0
  66. package/dist/skills/github.js +155 -0
  67. package/dist/skills/index.d.ts +68 -0
  68. package/dist/skills/index.js +134 -0
  69. package/dist/skills/linear.d.ts +7 -0
  70. package/dist/skills/linear.js +89 -0
  71. package/dist/skills/notification.d.ts +11 -0
  72. package/dist/skills/notification.js +142 -0
  73. package/dist/skills/sentry.d.ts +7 -0
  74. package/dist/skills/sentry.js +105 -0
  75. package/dist/skills/slack.d.ts +8 -0
  76. package/dist/skills/slack.js +115 -0
  77. package/dist/studio.d.ts +124 -0
  78. package/dist/studio.js +174 -0
  79. package/dist/testing.d.ts +88 -0
  80. package/dist/testing.js +253 -0
  81. package/dist/types.d.ts +144 -0
  82. package/dist/types.js +11 -0
  83. package/dist/workflow-builder.d.ts +45 -0
  84. package/dist/workflow-builder.js +120 -0
  85. package/dist/workflow-builder.test.d.ts +1 -0
  86. package/dist/workflow-builder.test.js +117 -0
  87. package/dist/workflows/implement.d.ts +11 -0
  88. package/dist/workflows/implement.js +83 -0
  89. package/dist/workflows/index.d.ts +2 -0
  90. package/dist/workflows/index.js +2 -0
  91. package/dist/workflows/triage.d.ts +18 -0
  92. package/dist/workflows/triage.js +108 -0
  93. package/package.json +83 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Run lightweight connectivity checks for all configured providers.
3
+ * Uses raw fetch — does NOT import provider packages.
4
+ */
5
+ export async function checkProviderConnectivity(config) {
6
+ const results = [];
7
+ const githubChecked = { done: false }; // avoid double-checking same token
8
+ // ── Coding agent ────────────────────────────────────────────────────────────
9
+ if (config.codingAgentProvider === "claude") {
10
+ if (!config.anthropicApiKey && !config.claudeOauthToken) {
11
+ results.push({ name: "Anthropic (claude agent)", status: "skip", detail: "No key configured" });
12
+ }
13
+ else if (config.anthropicApiKey) {
14
+ results.push(await checkAnthropic(config.anthropicApiKey));
15
+ }
16
+ else {
17
+ results.push({
18
+ name: "Anthropic (claude agent)",
19
+ status: "skip",
20
+ detail: "CLAUDE_CODE_OAUTH_TOKEN set — skipping API check",
21
+ });
22
+ }
23
+ }
24
+ else {
25
+ results.push({
26
+ name: `Coding agent (${config.codingAgentProvider})`,
27
+ status: "skip",
28
+ detail: "Connectivity check not implemented for this provider",
29
+ });
30
+ }
31
+ // ── Observability ────────────────────────────────────────────────────────────
32
+ if (config.observabilityProvider === "datadog") {
33
+ results.push(await checkDatadog(config.observabilityCredentials));
34
+ }
35
+ else if (config.observabilityProvider === "sentry") {
36
+ results.push(await checkSentry(config.observabilityCredentials));
37
+ }
38
+ else if (config.observabilityProvider === "file") {
39
+ results.push({ name: "Observability (file)", status: "skip", detail: "File provider — no network check needed" });
40
+ }
41
+ else {
42
+ results.push({
43
+ name: `Observability (${config.observabilityProvider})`,
44
+ status: "skip",
45
+ detail: "Connectivity check not implemented for this provider",
46
+ });
47
+ }
48
+ // ── Issue tracker ────────────────────────────────────────────────────────────
49
+ if (config.issueTrackerProvider === "linear") {
50
+ results.push(await checkLinear(config.linearApiKey));
51
+ }
52
+ else if (config.issueTrackerProvider === "github-issues") {
53
+ const token = config.githubToken || config.botToken;
54
+ results.push(await checkGitHub(token, "Issue tracker (github-issues)"));
55
+ githubChecked.done = true;
56
+ }
57
+ else if (config.issueTrackerProvider === "file") {
58
+ results.push({
59
+ name: "Issue tracker (file)",
60
+ status: "skip",
61
+ detail: "File provider — no network check needed",
62
+ });
63
+ }
64
+ else {
65
+ results.push({
66
+ name: `Issue tracker (${config.issueTrackerProvider})`,
67
+ status: "skip",
68
+ detail: "Connectivity check not implemented for this provider",
69
+ });
70
+ }
71
+ // ── Source control ───────────────────────────────────────────────────────────
72
+ if (config.sourceControlProvider === "github" && !githubChecked.done) {
73
+ const token = config.githubToken || config.botToken;
74
+ results.push(await checkGitHub(token, "Source control (github)"));
75
+ }
76
+ else if (config.sourceControlProvider === "github" && githubChecked.done) {
77
+ // Already checked via github-issues — reuse
78
+ const existing = results.find((r) => r.name === "Issue tracker (github-issues)");
79
+ if (existing) {
80
+ results.push({ ...existing, name: "Source control (github)" });
81
+ }
82
+ }
83
+ else if (config.sourceControlProvider === "file") {
84
+ results.push({
85
+ name: "Source control (file)",
86
+ status: "skip",
87
+ detail: "File provider — no network check needed",
88
+ });
89
+ }
90
+ else {
91
+ results.push({
92
+ name: `Source control (${config.sourceControlProvider})`,
93
+ status: "skip",
94
+ detail: "Connectivity check not implemented for this provider",
95
+ });
96
+ }
97
+ return results;
98
+ }
99
+ // ── Provider check functions ─────────────────────────────────────────────────
100
+ async function checkAnthropic(apiKey) {
101
+ const name = "Anthropic (claude agent)";
102
+ try {
103
+ const res = await fetch("https://api.anthropic.com/v1/models", {
104
+ headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
105
+ });
106
+ if (res.status === 200) {
107
+ return { name, status: "ok", detail: "API key is valid" };
108
+ }
109
+ if (res.status === 401) {
110
+ return {
111
+ name,
112
+ status: "fail",
113
+ detail: "401 Unauthorized — check ANTHROPIC_API_KEY at https://console.anthropic.com",
114
+ };
115
+ }
116
+ return { name, status: "fail", detail: `Unexpected HTTP ${res.status}` };
117
+ }
118
+ catch (err) {
119
+ return { name, status: "fail", detail: networkErrorMessage(err) };
120
+ }
121
+ }
122
+ async function checkDatadog(creds) {
123
+ const name = "Observability (datadog)";
124
+ const { apiKey, appKey, site = "datadoghq.com" } = creds;
125
+ if (!apiKey || !appKey) {
126
+ return { name, status: "skip", detail: "DD_API_KEY or DD_APP_KEY not configured" };
127
+ }
128
+ try {
129
+ const res = await fetch(`https://api.${site}/api/v2/validate`, {
130
+ headers: { "DD-API-KEY": apiKey, "DD-APPLICATION-KEY": appKey },
131
+ });
132
+ if (res.status === 200) {
133
+ return { name, status: "ok", detail: "API key is valid" };
134
+ }
135
+ if (res.status === 403) {
136
+ return {
137
+ name,
138
+ status: "fail",
139
+ detail: "403 Forbidden — check DD_API_KEY and DD_APP_KEY at https://app.datadoghq.com/organization-settings/api-keys",
140
+ };
141
+ }
142
+ return { name, status: "fail", detail: `Unexpected HTTP ${res.status}` };
143
+ }
144
+ catch (err) {
145
+ return { name, status: "fail", detail: networkErrorMessage(err) };
146
+ }
147
+ }
148
+ async function checkSentry(creds) {
149
+ const name = "Observability (sentry)";
150
+ const { authToken } = creds;
151
+ if (!authToken) {
152
+ return { name, status: "skip", detail: "SENTRY_AUTH_TOKEN not configured" };
153
+ }
154
+ try {
155
+ const res = await fetch("https://sentry.io/api/0/", {
156
+ headers: { Authorization: `Bearer ${authToken}` },
157
+ });
158
+ if (res.status === 200) {
159
+ return { name, status: "ok", detail: "Auth token is valid" };
160
+ }
161
+ if (res.status === 401) {
162
+ return {
163
+ name,
164
+ status: "fail",
165
+ detail: "401 Unauthorized — check SENTRY_AUTH_TOKEN at https://sentry.io/settings/auth-tokens/",
166
+ };
167
+ }
168
+ return { name, status: "fail", detail: `Unexpected HTTP ${res.status}` };
169
+ }
170
+ catch (err) {
171
+ return { name, status: "fail", detail: networkErrorMessage(err) };
172
+ }
173
+ }
174
+ async function checkLinear(apiKey) {
175
+ const name = "Issue tracker (linear)";
176
+ if (!apiKey) {
177
+ return { name, status: "skip", detail: "LINEAR_API_KEY not configured" };
178
+ }
179
+ try {
180
+ const res = await fetch("https://api.linear.app/graphql", {
181
+ method: "POST",
182
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
183
+ body: JSON.stringify({ query: "{ viewer { id name } }" }),
184
+ });
185
+ if (res.status === 200) {
186
+ const body = (await res.json());
187
+ if (body.errors?.length) {
188
+ return { name, status: "fail", detail: "GraphQL error — check LINEAR_API_KEY" };
189
+ }
190
+ const displayName = body.data?.viewer?.name ?? "authenticated";
191
+ return { name, status: "ok", detail: `Authenticated as ${displayName}` };
192
+ }
193
+ if (res.status === 401) {
194
+ return {
195
+ name,
196
+ status: "fail",
197
+ detail: "401 Unauthorized — check LINEAR_API_KEY at https://linear.app/settings/api",
198
+ };
199
+ }
200
+ return { name, status: "fail", detail: `Unexpected HTTP ${res.status}` };
201
+ }
202
+ catch (err) {
203
+ return { name, status: "fail", detail: networkErrorMessage(err) };
204
+ }
205
+ }
206
+ async function checkGitHub(token, name) {
207
+ if (!token) {
208
+ return { name, status: "skip", detail: "GITHUB_TOKEN not configured" };
209
+ }
210
+ try {
211
+ const res = await fetch("https://api.github.com/user", {
212
+ headers: { Authorization: `token ${token}`, "User-Agent": "sweny-cli" },
213
+ });
214
+ if (res.status === 200) {
215
+ const body = (await res.json());
216
+ return { name, status: "ok", detail: `Authenticated as ${body.login ?? "unknown"}` };
217
+ }
218
+ if (res.status === 401) {
219
+ return {
220
+ name,
221
+ status: "fail",
222
+ detail: "401 Bad credentials — check GITHUB_TOKEN at https://github.com/settings/tokens",
223
+ };
224
+ }
225
+ return { name, status: "fail", detail: `Unexpected HTTP ${res.status}` };
226
+ }
227
+ catch (err) {
228
+ return { name, status: "fail", detail: networkErrorMessage(err) };
229
+ }
230
+ }
231
+ function networkErrorMessage(err) {
232
+ const msg = err instanceof Error ? err.message : String(err);
233
+ if (/ENOTFOUND|ETIMEDOUT|ECONNREFUSED/i.test(msg)) {
234
+ return `Network error — check your internet connection (${msg})`;
235
+ }
236
+ return msg;
237
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Auto-load a `.env` file from the given directory.
3
+ * Sets `process.env[KEY]` only if not already defined (real env vars win).
4
+ */
5
+ export declare function loadDotenv(cwd?: string): void;
6
+ /**
7
+ * Search upward from `cwd` for `.sweny.yml` and parse it into flat key-value pairs.
8
+ * Returns empty object if no config file is found.
9
+ */
10
+ export declare function loadConfigFile(cwd?: string): Record<string, string>;
11
+ /** Starter config written by `sweny init`. */
12
+ export declare const STARTER_CONFIG = "# .sweny.yml \u2014 SWEny project configuration\n# Commit this file. Secrets (API keys, tokens) go in .env (gitignored).\n#\n# Every key matches a CLI flag: \"time-range: 4h\" is the same as \"--time-range 4h\".\n# CLI flags override this file; env vars override this file; this file overrides defaults.\n\n# \u2500\u2500 Providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# observability-provider: datadog # datadog | sentry | cloudwatch | splunk | elastic | newrelic | loki | prometheus | pagerduty | heroku | opsgenie | vercel | supabase | netlify | fly | render | file\n# issue-tracker-provider: github-issues # github-issues | linear | jira\n# source-control-provider: github # github | gitlab\n# coding-agent-provider: claude # claude | codex | gemini\n# notification-provider: console # console | slack | teams | discord | email | webhook\n\n# \u2500\u2500 Investigation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# time-range: 24h\n# severity-focus: errors\n# service-filter: \"*\"\n# investigation-depth: standard # quick | standard | thorough\n\n# \u2500\u2500 PR / branch \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# base-branch: main\n# pr-labels: agent,triage,needs-review\n\n# \u2500\u2500 Paths \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# service-map-path: .github/service-map.yml\n# log-file: ./logs/errors.json # required when observability-provider is \"file\"\n\n# \u2500\u2500 Cache \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# cache-dir: .sweny/cache\n# cache-ttl: 86400\n\n# \u2500\u2500 MCP servers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Extend the coding agent with additional tools via MCP.\n# Value is a JSON object \u2014 each key is a server name you choose.\n# See docs/mcp-servers.md for a full catalog with copy-paste configs.\n#\n# Example: GitHub MCP server (query PRs, issues, CI run logs)\n# mcp-servers-json: '{\"github\":{\"type\":\"stdio\",\"command\":\"npx\",\"args\":[\"-y\",\"@modelcontextprotocol/server-github@latest\"],\"env\":{\"GITHUB_PERSONAL_ACCESS_TOKEN\":\"ghp_...\"}}}'\n\n# \u2500\u2500 Local-only quick start \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Uncomment to run without any external services (just an LLM API key):\n# observability-provider: file\n# log-file: ./sample-errors.json\n# issue-tracker-provider: file\n# source-control-provider: file\n# notification-provider: file\n# output-dir: .sweny/output\n\n# \u2500\u2500 Credentials (.env) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Copy the relevant block into your .env file and fill in the values.\n#\n# Claude (coding agent) \u2014 https://console.anthropic.com/settings/api-keys\n# ANTHROPIC_API_KEY=sk-ant-...\n#\n# GitHub (source control + issue tracker)\n# GITHUB_TOKEN=ghp_... # https://github.com/settings/tokens (repo + issues scopes)\n#\n# Datadog (observability) \u2014 https://app.datadoghq.com/organization-settings\n# DD_API_KEY=... # Organization Settings > API Keys\n# DD_APP_KEY=... # Organization Settings > Application Keys\n# DD_SITE=datadoghq.com # or datadoghq.eu, us3.datadoghq.com, etc.\n#\n# Sentry (observability) \u2014 https://sentry.io/settings/auth-tokens/\n# SENTRY_AUTH_TOKEN=sntrys_...\n# SENTRY_ORG=your-org-slug # from sentry.io/organizations/<slug>/\n# SENTRY_PROJECT=your-project # Project Settings > General > Project Slug\n#\n# Linear (issue tracker) \u2014 https://linear.app/settings/api\n# LINEAR_API_KEY=lin_api_...\n# LINEAR_TEAM_ID=... # Settings > Workspace > Teams > [team] > copy ID from URL\n# LINEAR_BUG_LABEL_ID=... # Settings > Labels > [label] > copy ID from URL\n#\n# Jira (issue tracker) \u2014 https://your-org.atlassian.net\n# JIRA_BASE_URL=https://your-org.atlassian.net\n# JIRA_EMAIL=you@company.com # your Atlassian account email\n# JIRA_API_TOKEN=... # https://id.atlassian.com/manage-profile/security/api-tokens\n#\n# Vercel (observability) \u2014 https://vercel.com/account/tokens\n# VERCEL_TOKEN=...\n# VERCEL_PROJECT_ID=prj_... # Project Settings > General > Project ID\n# VERCEL_TEAM_ID=team_... # optional, for team-owned projects\n#\n# Supabase (observability) \u2014 https://supabase.com/dashboard/account/tokens\n# SUPABASE_MANAGEMENT_KEY=...\n# SUPABASE_PROJECT_REF=... # Project Settings > General > Reference ID\n#\n# Netlify (observability) \u2014 https://app.netlify.com/user/applications#personal-access-tokens\n# NETLIFY_TOKEN=...\n# NETLIFY_SITE_ID=... # Site Settings > General > Site ID\n#\n# Fly.io (observability) \u2014 https://fly.io/user/personal_access_tokens\n# FLY_TOKEN=...\n# FLY_APP_NAME=... # the name of your Fly.io application\n#\n# Render (observability) \u2014 https://dashboard.render.com/u/settings\n# RENDER_API_KEY=...\n# RENDER_SERVICE_ID=srv-... # from your service's Settings page\n#\n# Prometheus (observability) \u2014 self-hosted or Grafana Cloud\n# PROMETHEUS_URL=http://prometheus.internal:9090\n# PROMETHEUS_TOKEN=... # optional, for secured instances\n#\n# PagerDuty (observability) \u2014 https://your-account.pagerduty.com/api_keys\n# PAGERDUTY_API_KEY=...\n#\n# Honeycomb (observability) \u2014 https://docs.honeycomb.io/api/\n# HONEYCOMB_API_KEY=...\n# HONEYCOMB_DATASET=... # dataset name (e.g. production)\n#\n# Heroku (observability) \u2014 https://devcenter.heroku.com/articles/platform-api-reference\n# HEROKU_API_KEY=... # https://dashboard.heroku.com/account\n# HEROKU_APP_NAME=... # the name of your Heroku application\n#\n# OpsGenie (observability) \u2014 https://support.atlassian.com/opsgenie/docs/api-key-management/\n# OPSGENIE_API_KEY=...\n# OPSGENIE_REGION=us # or eu for EU-hosted accounts\n#\n# Slack (notifications) \u2014 https://api.slack.com/apps\n# NOTIFICATION_WEBHOOK_URL=https://hooks.slack.com/services/...\n# # or use a bot token: SLACK_BOT_TOKEN=xoxb-...\n";
@@ -0,0 +1,208 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ /**
4
+ * Auto-load a `.env` file from the given directory.
5
+ * Sets `process.env[KEY]` only if not already defined (real env vars win).
6
+ */
7
+ export function loadDotenv(cwd = process.cwd()) {
8
+ const envPath = path.join(cwd, ".env");
9
+ let content;
10
+ try {
11
+ content = fs.readFileSync(envPath, "utf-8");
12
+ }
13
+ catch {
14
+ return; // no .env — silently skip
15
+ }
16
+ for (const line of content.split("\n")) {
17
+ const trimmed = line.trim();
18
+ if (!trimmed || trimmed.startsWith("#"))
19
+ continue;
20
+ const eqIndex = trimmed.indexOf("=");
21
+ if (eqIndex === -1)
22
+ continue;
23
+ const key = trimmed.slice(0, eqIndex).trim();
24
+ let value = trimmed.slice(eqIndex + 1).trim();
25
+ // Strip surrounding quotes
26
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
27
+ value = value.slice(1, -1);
28
+ }
29
+ if (process.env[key] === undefined) {
30
+ process.env[key] = value;
31
+ }
32
+ }
33
+ }
34
+ /**
35
+ * Search upward from `cwd` for `.sweny.yml` and parse it into flat key-value pairs.
36
+ * Returns empty object if no config file is found.
37
+ */
38
+ export function loadConfigFile(cwd = process.cwd()) {
39
+ const filePath = findConfigFile(cwd);
40
+ if (!filePath)
41
+ return {};
42
+ let content;
43
+ try {
44
+ content = fs.readFileSync(filePath, "utf-8");
45
+ }
46
+ catch {
47
+ return {};
48
+ }
49
+ const config = {};
50
+ for (const line of content.split("\n")) {
51
+ const trimmed = line.trim();
52
+ if (!trimmed || trimmed.startsWith("#"))
53
+ continue;
54
+ const colonIndex = trimmed.indexOf(":");
55
+ if (colonIndex === -1)
56
+ continue;
57
+ const key = trimmed.slice(0, colonIndex).trim();
58
+ let value = trimmed.slice(colonIndex + 1).trim();
59
+ // Strip surrounding quotes
60
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
61
+ value = value.slice(1, -1);
62
+ }
63
+ if (key && value !== "") {
64
+ config[key] = value;
65
+ }
66
+ }
67
+ return config;
68
+ }
69
+ function findConfigFile(startDir) {
70
+ let dir = path.resolve(startDir);
71
+ const { root } = path.parse(dir);
72
+ while (true) {
73
+ const candidate = path.join(dir, ".sweny.yml");
74
+ try {
75
+ fs.accessSync(candidate, fs.constants.R_OK);
76
+ return candidate;
77
+ }
78
+ catch {
79
+ // not found — walk up
80
+ }
81
+ const parent = path.dirname(dir);
82
+ if (parent === dir || dir === root)
83
+ return null;
84
+ dir = parent;
85
+ }
86
+ }
87
+ /** Starter config written by `sweny init`. */
88
+ export const STARTER_CONFIG = `# .sweny.yml — SWEny project configuration
89
+ # Commit this file. Secrets (API keys, tokens) go in .env (gitignored).
90
+ #
91
+ # Every key matches a CLI flag: "time-range: 4h" is the same as "--time-range 4h".
92
+ # CLI flags override this file; env vars override this file; this file overrides defaults.
93
+
94
+ # ── Providers ────────────────────────────────────────────────────────
95
+ # observability-provider: datadog # datadog | sentry | cloudwatch | splunk | elastic | newrelic | loki | prometheus | pagerduty | heroku | opsgenie | vercel | supabase | netlify | fly | render | file
96
+ # issue-tracker-provider: github-issues # github-issues | linear | jira
97
+ # source-control-provider: github # github | gitlab
98
+ # coding-agent-provider: claude # claude | codex | gemini
99
+ # notification-provider: console # console | slack | teams | discord | email | webhook
100
+
101
+ # ── Investigation ────────────────────────────────────────────────────
102
+ # time-range: 24h
103
+ # severity-focus: errors
104
+ # service-filter: "*"
105
+ # investigation-depth: standard # quick | standard | thorough
106
+
107
+ # ── PR / branch ──────────────────────────────────────────────────────
108
+ # base-branch: main
109
+ # pr-labels: agent,triage,needs-review
110
+
111
+ # ── Paths ─────────────────────────────────────────────────────────────
112
+ # service-map-path: .github/service-map.yml
113
+ # log-file: ./logs/errors.json # required when observability-provider is "file"
114
+
115
+ # ── Cache ─────────────────────────────────────────────────────────────
116
+ # cache-dir: .sweny/cache
117
+ # cache-ttl: 86400
118
+
119
+ # ── MCP servers ───────────────────────────────────────────────────────
120
+ # Extend the coding agent with additional tools via MCP.
121
+ # Value is a JSON object — each key is a server name you choose.
122
+ # See docs/mcp-servers.md for a full catalog with copy-paste configs.
123
+ #
124
+ # Example: GitHub MCP server (query PRs, issues, CI run logs)
125
+ # mcp-servers-json: '{"github":{"type":"stdio","command":"npx","args":["-y","@modelcontextprotocol/server-github@latest"],"env":{"GITHUB_PERSONAL_ACCESS_TOKEN":"ghp_..."}}}'
126
+
127
+ # ── Local-only quick start ───────────────────────────────────────────
128
+ # Uncomment to run without any external services (just an LLM API key):
129
+ # observability-provider: file
130
+ # log-file: ./sample-errors.json
131
+ # issue-tracker-provider: file
132
+ # source-control-provider: file
133
+ # notification-provider: file
134
+ # output-dir: .sweny/output
135
+
136
+ # ── Credentials (.env) ───────────────────────────────────────────────
137
+ # Copy the relevant block into your .env file and fill in the values.
138
+ #
139
+ # Claude (coding agent) — https://console.anthropic.com/settings/api-keys
140
+ # ANTHROPIC_API_KEY=sk-ant-...
141
+ #
142
+ # GitHub (source control + issue tracker)
143
+ # GITHUB_TOKEN=ghp_... # https://github.com/settings/tokens (repo + issues scopes)
144
+ #
145
+ # Datadog (observability) — https://app.datadoghq.com/organization-settings
146
+ # DD_API_KEY=... # Organization Settings > API Keys
147
+ # DD_APP_KEY=... # Organization Settings > Application Keys
148
+ # DD_SITE=datadoghq.com # or datadoghq.eu, us3.datadoghq.com, etc.
149
+ #
150
+ # Sentry (observability) — https://sentry.io/settings/auth-tokens/
151
+ # SENTRY_AUTH_TOKEN=sntrys_...
152
+ # SENTRY_ORG=your-org-slug # from sentry.io/organizations/<slug>/
153
+ # SENTRY_PROJECT=your-project # Project Settings > General > Project Slug
154
+ #
155
+ # Linear (issue tracker) — https://linear.app/settings/api
156
+ # LINEAR_API_KEY=lin_api_...
157
+ # LINEAR_TEAM_ID=... # Settings > Workspace > Teams > [team] > copy ID from URL
158
+ # LINEAR_BUG_LABEL_ID=... # Settings > Labels > [label] > copy ID from URL
159
+ #
160
+ # Jira (issue tracker) — https://your-org.atlassian.net
161
+ # JIRA_BASE_URL=https://your-org.atlassian.net
162
+ # JIRA_EMAIL=you@company.com # your Atlassian account email
163
+ # JIRA_API_TOKEN=... # https://id.atlassian.com/manage-profile/security/api-tokens
164
+ #
165
+ # Vercel (observability) — https://vercel.com/account/tokens
166
+ # VERCEL_TOKEN=...
167
+ # VERCEL_PROJECT_ID=prj_... # Project Settings > General > Project ID
168
+ # VERCEL_TEAM_ID=team_... # optional, for team-owned projects
169
+ #
170
+ # Supabase (observability) — https://supabase.com/dashboard/account/tokens
171
+ # SUPABASE_MANAGEMENT_KEY=...
172
+ # SUPABASE_PROJECT_REF=... # Project Settings > General > Reference ID
173
+ #
174
+ # Netlify (observability) — https://app.netlify.com/user/applications#personal-access-tokens
175
+ # NETLIFY_TOKEN=...
176
+ # NETLIFY_SITE_ID=... # Site Settings > General > Site ID
177
+ #
178
+ # Fly.io (observability) — https://fly.io/user/personal_access_tokens
179
+ # FLY_TOKEN=...
180
+ # FLY_APP_NAME=... # the name of your Fly.io application
181
+ #
182
+ # Render (observability) — https://dashboard.render.com/u/settings
183
+ # RENDER_API_KEY=...
184
+ # RENDER_SERVICE_ID=srv-... # from your service's Settings page
185
+ #
186
+ # Prometheus (observability) — self-hosted or Grafana Cloud
187
+ # PROMETHEUS_URL=http://prometheus.internal:9090
188
+ # PROMETHEUS_TOKEN=... # optional, for secured instances
189
+ #
190
+ # PagerDuty (observability) — https://your-account.pagerduty.com/api_keys
191
+ # PAGERDUTY_API_KEY=...
192
+ #
193
+ # Honeycomb (observability) — https://docs.honeycomb.io/api/
194
+ # HONEYCOMB_API_KEY=...
195
+ # HONEYCOMB_DATASET=... # dataset name (e.g. production)
196
+ #
197
+ # Heroku (observability) — https://devcenter.heroku.com/articles/platform-api-reference
198
+ # HEROKU_API_KEY=... # https://dashboard.heroku.com/account
199
+ # HEROKU_APP_NAME=... # the name of your Heroku application
200
+ #
201
+ # OpsGenie (observability) — https://support.atlassian.com/opsgenie/docs/api-key-management/
202
+ # OPSGENIE_API_KEY=...
203
+ # OPSGENIE_REGION=us # or eu for EU-hosted accounts
204
+ #
205
+ # Slack (notifications) — https://api.slack.com/apps
206
+ # NOTIFICATION_WEBHOOK_URL=https://hooks.slack.com/services/...
207
+ # # or use a bot token: SLACK_BOT_TOKEN=xoxb-...
208
+ `;
@@ -0,0 +1,77 @@
1
+ import type { Command } from "commander";
2
+ import type { McpServerConfig } from "../types.js";
3
+ export interface CliConfig {
4
+ codingAgentProvider: string;
5
+ anthropicApiKey: string;
6
+ claudeOauthToken: string;
7
+ openaiApiKey: string;
8
+ geminiApiKey: string;
9
+ observabilityProvider: string;
10
+ observabilityCredentials: Record<string, string>;
11
+ issueTrackerProvider: string;
12
+ linearApiKey: string;
13
+ linearTeamId: string;
14
+ linearBugLabelId: string;
15
+ linearTriageLabelId: string;
16
+ linearStateBacklog: string;
17
+ linearStateInProgress: string;
18
+ linearStatePeerReview: string;
19
+ timeRange: string;
20
+ severityFocus: string;
21
+ serviceFilter: string;
22
+ investigationDepth: string;
23
+ maxInvestigateTurns: number;
24
+ maxImplementTurns: number;
25
+ baseBranch: string;
26
+ prLabels: string[];
27
+ issueLabels: string[];
28
+ dryRun: boolean;
29
+ reviewMode: "auto" | "review";
30
+ noveltyMode: boolean;
31
+ issueOverride: string;
32
+ additionalInstructions: string;
33
+ serviceMapPath: string;
34
+ githubToken: string;
35
+ botToken: string;
36
+ sourceControlProvider: string;
37
+ jiraBaseUrl: string;
38
+ jiraEmail: string;
39
+ jiraApiToken: string;
40
+ gitlabToken: string;
41
+ gitlabProjectId: string;
42
+ gitlabBaseUrl: string;
43
+ notificationProvider: string;
44
+ notificationWebhookUrl: string;
45
+ sendgridApiKey: string;
46
+ emailFrom: string;
47
+ emailTo: string;
48
+ webhookSigningSecret: string;
49
+ repository: string;
50
+ repositoryOwner: string;
51
+ json: boolean;
52
+ bell: boolean;
53
+ cacheDir: string;
54
+ cacheTtl: number;
55
+ noCache: boolean;
56
+ outputDir: string;
57
+ mcpServers: Record<string, McpServerConfig>;
58
+ workspaceTools: string[];
59
+ }
60
+ export declare function registerTriageCommand(program: Command): Command;
61
+ export declare function parseCliInputs(options: Record<string, unknown>, fileConfig?: Record<string, string>): CliConfig;
62
+ /**
63
+ * All recognized workspace tool names. Update here when adding a new Category B MCP server.
64
+ *
65
+ * Requirements to add a tool:
66
+ * - Must have an HTTP transport endpoint (preferred) OR a stable pre-installed binary path
67
+ * - npx -y is intentionally NOT used — runtime package downloads bypass lockfiles
68
+ * - Asana is intentionally absent: no stable HTTP MCP endpoint as of current release
69
+ */
70
+ export declare const SUPPORTED_WORKSPACE_TOOLS: Set<string>;
71
+ export declare function validateInputs(config: CliConfig): string[];
72
+ /**
73
+ * Returns non-fatal warnings about the configuration.
74
+ * These are surfaced before the spinner starts but do not abort the run.
75
+ */
76
+ export declare function validateWarnings(config: Pick<CliConfig, "serviceMapPath">): string[];
77
+ export declare function registerImplementCommand(program: Command): Command;