@nairon-ai/aegis 0.2.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 (167) hide show
  1. package/.agents/skills/bug-fix/SKILL.md +91 -0
  2. package/.flue/agents/bug-fix.ts +107 -0
  3. package/.flue/app.ts +16 -0
  4. package/Dockerfile +8 -0
  5. package/LICENSE +21 -0
  6. package/README.md +251 -0
  7. package/dist-node/agent/bug-fix-skill.d.ts +2 -0
  8. package/dist-node/agent/bug-fix-skill.d.ts.map +1 -0
  9. package/dist-node/agent/bug-fix-skill.js +64 -0
  10. package/dist-node/agent/bug-fix-skill.js.map +1 -0
  11. package/dist-node/agent/client.d.ts +14 -0
  12. package/dist-node/agent/client.d.ts.map +1 -0
  13. package/dist-node/agent/client.js +110 -0
  14. package/dist-node/agent/client.js.map +1 -0
  15. package/dist-node/cli/commands/deploy.d.ts +3 -0
  16. package/dist-node/cli/commands/deploy.d.ts.map +1 -0
  17. package/dist-node/cli/commands/deploy.js +94 -0
  18. package/dist-node/cli/commands/deploy.js.map +1 -0
  19. package/dist-node/cli/commands/init.d.ts +3 -0
  20. package/dist-node/cli/commands/init.d.ts.map +1 -0
  21. package/dist-node/cli/commands/init.js +115 -0
  22. package/dist-node/cli/commands/init.js.map +1 -0
  23. package/dist-node/cli/commands/pickup.d.ts +11 -0
  24. package/dist-node/cli/commands/pickup.d.ts.map +1 -0
  25. package/dist-node/cli/commands/pickup.js +43 -0
  26. package/dist-node/cli/commands/pickup.js.map +1 -0
  27. package/dist-node/cli/commands/setup.d.ts +3 -0
  28. package/dist-node/cli/commands/setup.d.ts.map +1 -0
  29. package/dist-node/cli/commands/setup.js +163 -0
  30. package/dist-node/cli/commands/setup.js.map +1 -0
  31. package/dist-node/cli/commands/status.d.ts +3 -0
  32. package/dist-node/cli/commands/status.d.ts.map +1 -0
  33. package/dist-node/cli/commands/status.js +26 -0
  34. package/dist-node/cli/commands/status.js.map +1 -0
  35. package/dist-node/cli/index.d.ts +3 -0
  36. package/dist-node/cli/index.d.ts.map +1 -0
  37. package/dist-node/cli/index.js +36 -0
  38. package/dist-node/cli/index.js.map +1 -0
  39. package/dist-node/cli/paths.d.ts +7 -0
  40. package/dist-node/cli/paths.d.ts.map +1 -0
  41. package/dist-node/cli/paths.js +29 -0
  42. package/dist-node/cli/paths.js.map +1 -0
  43. package/dist-node/cli/state.d.ts +16 -0
  44. package/dist-node/cli/state.d.ts.map +1 -0
  45. package/dist-node/cli/state.js +214 -0
  46. package/dist-node/cli/state.js.map +1 -0
  47. package/dist-node/core/pickup.d.ts +14 -0
  48. package/dist-node/core/pickup.d.ts.map +1 -0
  49. package/dist-node/core/pickup.js +42 -0
  50. package/dist-node/core/pickup.js.map +1 -0
  51. package/dist-node/github/index.d.ts +3 -0
  52. package/dist-node/github/index.d.ts.map +1 -0
  53. package/dist-node/github/index.js +2 -0
  54. package/dist-node/github/index.js.map +1 -0
  55. package/dist-node/github/manifest.d.ts +34 -0
  56. package/dist-node/github/manifest.d.ts.map +1 -0
  57. package/dist-node/github/manifest.js +71 -0
  58. package/dist-node/github/manifest.js.map +1 -0
  59. package/dist-node/integrations/github.d.ts +29 -0
  60. package/dist-node/integrations/github.d.ts.map +1 -0
  61. package/dist-node/integrations/github.js +199 -0
  62. package/dist-node/integrations/github.js.map +1 -0
  63. package/dist-node/integrations/linear.d.ts +15 -0
  64. package/dist-node/integrations/linear.d.ts.map +1 -0
  65. package/dist-node/integrations/linear.js +146 -0
  66. package/dist-node/integrations/linear.js.map +1 -0
  67. package/dist-node/integrations/telegram.d.ts +24 -0
  68. package/dist-node/integrations/telegram.d.ts.map +1 -0
  69. package/dist-node/integrations/telegram.js +39 -0
  70. package/dist-node/integrations/telegram.js.map +1 -0
  71. package/dist-node/integrations/webhooks.d.ts +3 -0
  72. package/dist-node/integrations/webhooks.d.ts.map +1 -0
  73. package/dist-node/integrations/webhooks.js +37 -0
  74. package/dist-node/integrations/webhooks.js.map +1 -0
  75. package/dist-node/sandbox/github-token.d.ts +7 -0
  76. package/dist-node/sandbox/github-token.d.ts.map +1 -0
  77. package/dist-node/sandbox/github-token.js +66 -0
  78. package/dist-node/sandbox/github-token.js.map +1 -0
  79. package/dist-node/sandbox/index.d.ts +2 -0
  80. package/dist-node/sandbox/index.d.ts.map +1 -0
  81. package/dist-node/sandbox/index.js +2 -0
  82. package/dist-node/sandbox/index.js.map +1 -0
  83. package/dist-node/server/app.d.ts +9 -0
  84. package/dist-node/server/app.d.ts.map +1 -0
  85. package/dist-node/server/app.js +216 -0
  86. package/dist-node/server/app.js.map +1 -0
  87. package/dist-node/shared/config.d.ts +5 -0
  88. package/dist-node/shared/config.d.ts.map +1 -0
  89. package/dist-node/shared/config.js +135 -0
  90. package/dist-node/shared/config.js.map +1 -0
  91. package/dist-node/shared/constants.d.ts +16 -0
  92. package/dist-node/shared/constants.d.ts.map +1 -0
  93. package/dist-node/shared/constants.js +29 -0
  94. package/dist-node/shared/constants.js.map +1 -0
  95. package/dist-node/shared/format.d.ts +12 -0
  96. package/dist-node/shared/format.d.ts.map +1 -0
  97. package/dist-node/shared/format.js +71 -0
  98. package/dist-node/shared/format.js.map +1 -0
  99. package/dist-node/shared/index.d.ts +7 -0
  100. package/dist-node/shared/index.d.ts.map +1 -0
  101. package/dist-node/shared/index.js +7 -0
  102. package/dist-node/shared/index.js.map +1 -0
  103. package/dist-node/shared/readiness.d.ts +3 -0
  104. package/dist-node/shared/readiness.d.ts.map +1 -0
  105. package/dist-node/shared/readiness.js +91 -0
  106. package/dist-node/shared/readiness.js.map +1 -0
  107. package/dist-node/shared/run-state.d.ts +5 -0
  108. package/dist-node/shared/run-state.d.ts.map +1 -0
  109. package/dist-node/shared/run-state.js +26 -0
  110. package/dist-node/shared/run-state.js.map +1 -0
  111. package/dist-node/shared/types.d.ts +230 -0
  112. package/dist-node/shared/types.d.ts.map +1 -0
  113. package/dist-node/shared/types.js +5 -0
  114. package/dist-node/shared/types.js.map +1 -0
  115. package/dist-node/sources/github.d.ts +15 -0
  116. package/dist-node/sources/github.d.ts.map +1 -0
  117. package/dist-node/sources/github.js +44 -0
  118. package/dist-node/sources/github.js.map +1 -0
  119. package/dist-node/sources/index.d.ts +6 -0
  120. package/dist-node/sources/index.d.ts.map +1 -0
  121. package/dist-node/sources/index.js +16 -0
  122. package/dist-node/sources/index.js.map +1 -0
  123. package/dist-node/sources/linear.d.ts +15 -0
  124. package/dist-node/sources/linear.d.ts.map +1 -0
  125. package/dist-node/sources/linear.js +32 -0
  126. package/dist-node/sources/linear.js.map +1 -0
  127. package/dist-node/sources/types.d.ts +15 -0
  128. package/dist-node/sources/types.d.ts.map +1 -0
  129. package/dist-node/sources/types.js +2 -0
  130. package/dist-node/sources/types.js.map +1 -0
  131. package/docs/RELEASING.md +52 -0
  132. package/docs/SETUP.md +439 -0
  133. package/package.json +64 -0
  134. package/src/agent/bug-fix-skill.ts +63 -0
  135. package/src/agent/client.ts +156 -0
  136. package/src/cli/commands/deploy.ts +106 -0
  137. package/src/cli/commands/init.ts +119 -0
  138. package/src/cli/commands/pickup.ts +44 -0
  139. package/src/cli/commands/setup.ts +217 -0
  140. package/src/cli/commands/status.ts +24 -0
  141. package/src/cli/index.ts +38 -0
  142. package/src/cli/paths.ts +29 -0
  143. package/src/cli/state.ts +228 -0
  144. package/src/core/pickup.ts +66 -0
  145. package/src/github/index.ts +2 -0
  146. package/src/github/manifest.ts +97 -0
  147. package/src/integrations/github.ts +241 -0
  148. package/src/integrations/linear.ts +195 -0
  149. package/src/integrations/telegram.ts +48 -0
  150. package/src/integrations/webhooks.ts +53 -0
  151. package/src/sandbox/github-token.ts +92 -0
  152. package/src/sandbox/index.ts +1 -0
  153. package/src/server/app.ts +292 -0
  154. package/src/shared/config.ts +154 -0
  155. package/src/shared/constants.ts +30 -0
  156. package/src/shared/format.ts +84 -0
  157. package/src/shared/index.ts +6 -0
  158. package/src/shared/readiness.ts +116 -0
  159. package/src/shared/run-state.ts +32 -0
  160. package/src/shared/types.ts +257 -0
  161. package/src/sources/github.ts +57 -0
  162. package/src/sources/index.ts +20 -0
  163. package/src/sources/linear.ts +44 -0
  164. package/src/sources/types.ts +16 -0
  165. package/tsconfig.json +25 -0
  166. package/tsconfig.node.json +16 -0
  167. package/wrangler.jsonc +43 -0
@@ -0,0 +1,154 @@
1
+ import {
2
+ DEFAULT_AGENT_MODEL,
3
+ DEFAULT_APPROVAL_REQUIRED_LABELS,
4
+ DEFAULT_APPROVAL_REQUIRED_PATHS,
5
+ DEFAULT_BASE_BRANCH,
6
+ DEFAULT_BLOCKED_LABEL,
7
+ DEFAULT_BUG_LABEL,
8
+ DEFAULT_IN_PROGRESS_LABEL,
9
+ DEFAULT_LOG_LOOKBACK_MINUTES,
10
+ DEFAULT_NEEDS_INFO_LABEL,
11
+ DEFAULT_PR_OPENED_LABEL,
12
+ DEFAULT_READY_LABEL,
13
+ } from "./constants.js";
14
+ import type { AegisCliConfig, AegisConfig, Bindings } from "./types.js";
15
+
16
+ export function configFromBindings(env: Bindings): AegisConfig {
17
+ return normalizeConfig({
18
+ workerUrl: env.AEGIS_WORKER_URL,
19
+ monitoredRepo: env.MONITORED_REPO,
20
+ baseBranch: env.BASE_BRANCH,
21
+ automationMode: env.AUTOMATION_MODE,
22
+ contextProfile: env.CONTEXT_PROFILE,
23
+ maxConcurrentRuns: parseInteger(env.MAX_CONCURRENT_RUNS, 1),
24
+ readyLabel: env.READY_LABEL,
25
+ bugLabel: env.BUG_LABEL,
26
+ inProgressLabel: env.IN_PROGRESS_LABEL,
27
+ needsInfoLabel: env.NEEDS_INFO_LABEL,
28
+ blockedLabel: env.BLOCKED_LABEL,
29
+ prOpenedLabel: env.PR_OPENED_LABEL,
30
+ lowRiskLabels: parseList(env.LOW_RISK_LABELS),
31
+ alwaysRequireApprovalLabels: parseList(env.ALWAYS_REQUIRE_APPROVAL_LABELS),
32
+ alwaysRequireApprovalPaths: parseList(env.ALWAYS_REQUIRE_APPROVAL_PATHS),
33
+ github: {
34
+ appId: env.GITHUB_APP_ID,
35
+ privateKey: env.GITHUB_APP_PRIVATE_KEY,
36
+ installationId: env.GITHUB_INSTALLATION_ID,
37
+ webhookSecret: env.GITHUB_WEBHOOK_SECRET,
38
+ token: env.GITHUB_TOKEN,
39
+ },
40
+ linear: {
41
+ apiKey: env.LINEAR_API_KEY,
42
+ webhookSecret: env.LINEAR_WEBHOOK_SECRET,
43
+ teamId: env.LINEAR_TEAM_ID,
44
+ projectId: env.LINEAR_PROJECT_ID,
45
+ readyStatusName: env.LINEAR_READY_STATUS ?? "Ready to Implement",
46
+ inProgressStatusName: env.LINEAR_IN_PROGRESS_STATUS ?? "In Progress",
47
+ needsInfoStatusName: env.LINEAR_NEEDS_INFO_STATUS ?? "Needs Info",
48
+ blockedStatusName: env.LINEAR_BLOCKED_STATUS ?? "Blocked",
49
+ bugLabel: env.LINEAR_BUG_LABEL ?? env.BUG_LABEL ?? DEFAULT_BUG_LABEL,
50
+ readyLabel: env.LINEAR_READY_LABEL,
51
+ },
52
+ telegram: {
53
+ botToken: env.TELEGRAM_BOT_TOKEN,
54
+ chatId: env.TELEGRAM_CHAT_ID,
55
+ webhookSecret: env.TELEGRAM_WEBHOOK_SECRET,
56
+ },
57
+ production: {
58
+ databaseUrl: env.DATABASE_URL,
59
+ vercelToken: env.VERCEL_TOKEN,
60
+ vercelProjectId: env.VERCEL_PROJECT_ID,
61
+ vercelTeamId: env.VERCEL_TEAM_ID,
62
+ logLookbackMinutes: parseInteger(
63
+ env.VERCEL_LOG_LOOKBACK_MINUTES,
64
+ DEFAULT_LOG_LOOKBACK_MINUTES,
65
+ ),
66
+ },
67
+ model: env.AGENT_MODEL,
68
+ });
69
+ }
70
+
71
+ export function configFromCli(cli: AegisCliConfig): AegisConfig {
72
+ return normalizeConfig({
73
+ workerUrl: cli.workerUrl,
74
+ monitoredRepo: cli.monitoredRepo ?? "",
75
+ baseBranch: cli.baseBranch,
76
+ automationMode: cli.automationMode,
77
+ contextProfile: cli.contextProfile,
78
+ readyLabel: cli.readyLabel,
79
+ bugLabel: cli.bugLabel,
80
+ github: {
81
+ appId: cli.githubAppId,
82
+ privateKey: cli.githubAppPrivateKey,
83
+ installationId: cli.githubInstallationId,
84
+ webhookSecret: cli.githubWebhookSecret,
85
+ token: cli.githubToken,
86
+ },
87
+ linear: {
88
+ apiKey: cli.linearApiKey,
89
+ webhookSecret: cli.linearWebhookSecret,
90
+ teamId: cli.linearTeamId,
91
+ projectId: cli.linearProjectId,
92
+ readyStatusName: cli.linearReadyStatus ?? "Ready to Implement",
93
+ inProgressStatusName: cli.linearInProgressStatus ?? "In Progress",
94
+ needsInfoStatusName: cli.linearNeedsInfoStatus ?? "Needs Info",
95
+ blockedStatusName: cli.linearBlockedStatus ?? "Blocked",
96
+ bugLabel: cli.linearBugLabel ?? cli.bugLabel ?? DEFAULT_BUG_LABEL,
97
+ },
98
+ telegram: {
99
+ botToken: cli.telegramBotToken,
100
+ chatId: cli.telegramChatId,
101
+ webhookSecret: cli.telegramWebhookSecret,
102
+ },
103
+ production: {
104
+ databaseUrl: cli.databaseUrl,
105
+ vercelToken: cli.vercelToken,
106
+ vercelProjectId: cli.vercelProjectId,
107
+ vercelTeamId: cli.vercelTeamId,
108
+ logLookbackMinutes: DEFAULT_LOG_LOOKBACK_MINUTES,
109
+ },
110
+ model: cli.agentModel,
111
+ });
112
+ }
113
+
114
+ function normalizeConfig(input: Partial<AegisConfig> & { monitoredRepo: string }): AegisConfig {
115
+ const automationMode = input.automationMode ?? "plan-first";
116
+ const contextProfile = input.contextProfile ?? "minimal";
117
+ return {
118
+ workerUrl: input.workerUrl,
119
+ monitoredRepo: input.monitoredRepo,
120
+ baseBranch: input.baseBranch ?? DEFAULT_BASE_BRANCH,
121
+ automationMode,
122
+ contextProfile,
123
+ maxConcurrentRuns: input.maxConcurrentRuns ?? 1,
124
+ readyLabel: input.readyLabel ?? DEFAULT_READY_LABEL,
125
+ bugLabel: input.bugLabel ?? DEFAULT_BUG_LABEL,
126
+ inProgressLabel: input.inProgressLabel ?? DEFAULT_IN_PROGRESS_LABEL,
127
+ needsInfoLabel: input.needsInfoLabel ?? DEFAULT_NEEDS_INFO_LABEL,
128
+ blockedLabel: input.blockedLabel ?? DEFAULT_BLOCKED_LABEL,
129
+ prOpenedLabel: input.prOpenedLabel ?? DEFAULT_PR_OPENED_LABEL,
130
+ lowRiskLabels: input.lowRiskLabels ?? ["low-risk"],
131
+ alwaysRequireApprovalLabels:
132
+ input.alwaysRequireApprovalLabels ?? DEFAULT_APPROVAL_REQUIRED_LABELS,
133
+ alwaysRequireApprovalPaths: input.alwaysRequireApprovalPaths ?? DEFAULT_APPROVAL_REQUIRED_PATHS,
134
+ github: input.github,
135
+ linear: input.linear,
136
+ telegram: input.telegram,
137
+ production: input.production ?? { logLookbackMinutes: DEFAULT_LOG_LOOKBACK_MINUTES },
138
+ model: input.model ?? DEFAULT_AGENT_MODEL,
139
+ };
140
+ }
141
+
142
+ export function parseList(value: string | undefined): string[] {
143
+ if (!value) return [];
144
+ return value
145
+ .split(",")
146
+ .map((item) => item.trim())
147
+ .filter(Boolean);
148
+ }
149
+
150
+ function parseInteger(value: string | undefined, fallback: number): number {
151
+ if (!value) return fallback;
152
+ const parsed = Number.parseInt(value, 10);
153
+ return Number.isFinite(parsed) ? parsed : fallback;
154
+ }
@@ -0,0 +1,30 @@
1
+ export const DEFAULT_READY_LABEL = "ready to implement";
2
+ export const DEFAULT_BUG_LABEL = "bug";
3
+ export const DEFAULT_IN_PROGRESS_LABEL = "aegis:in-progress";
4
+ export const DEFAULT_NEEDS_INFO_LABEL = "aegis:needs-info";
5
+ export const DEFAULT_BLOCKED_LABEL = "aegis:blocked";
6
+ export const DEFAULT_PR_OPENED_LABEL = "aegis:pr-opened";
7
+ export const DEFAULT_BASE_BRANCH = "main";
8
+ export const DEFAULT_AGENT_MODEL = "openai/gpt-5.1";
9
+ export const DEFAULT_LOG_LOOKBACK_MINUTES = 45;
10
+ export const BRANCH_PREFIX = "aegis/bug-";
11
+ export const AGENT_CONTEXT_PATH = "/tmp/aegis-context.json";
12
+ export const SANDBOX_REPO_PATH = "/workspace/project";
13
+ export const BUG_FIX_SKILL = "bug-fix";
14
+
15
+ export const DEFAULT_APPROVAL_REQUIRED_LABELS = [
16
+ "security",
17
+ "payment",
18
+ "billing",
19
+ "data-loss",
20
+ "migration",
21
+ ];
22
+
23
+ export const DEFAULT_APPROVAL_REQUIRED_PATHS = [
24
+ "auth/**",
25
+ "billing/**",
26
+ "payments/**",
27
+ "db/**",
28
+ "database/**",
29
+ "migrations/**",
30
+ ];
@@ -0,0 +1,84 @@
1
+ import { BRANCH_PREFIX } from "./constants.js";
2
+ import type { AgentPlanResult, WorkItem } from "./types.js";
3
+
4
+ export function slugify(value: string, maxLength = 48): string {
5
+ const slug = value
6
+ .toLowerCase()
7
+ .replace(/[^a-z0-9]+/g, "-")
8
+ .replace(/^-+|-+$/g, "")
9
+ .slice(0, maxLength)
10
+ .replace(/-+$/g, "");
11
+ return slug || "bug";
12
+ }
13
+
14
+ export function branchNameFor(item: WorkItem): string {
15
+ const id = item.source === "github" ? `gh-${item.number ?? item.id}` : item.identifier;
16
+ return `${BRANCH_PREFIX}${slugify(`${id}-${item.title}`, 64)}`;
17
+ }
18
+
19
+ export function compactText(value: string, maxLength = 1200): string {
20
+ const clean = value.replace(/\r/g, "").trim();
21
+ if (clean.length <= maxLength) return clean;
22
+ return `${clean.slice(0, maxLength - 12).trim()}...`;
23
+ }
24
+
25
+ export function formatReadyDecisionLine(
26
+ item: WorkItem,
27
+ decision: { ready: boolean; reason: string },
28
+ ) {
29
+ const state = decision.ready ? "would run" : "needs info";
30
+ return `${item.source}:${item.identifier} ${state} - ${decision.reason}`;
31
+ }
32
+
33
+ export function formatTelegramPlanMessage(
34
+ runId: string,
35
+ item: WorkItem,
36
+ plan: AgentPlanResult,
37
+ ): string {
38
+ const risk = plan.risk[0]?.toUpperCase() + plan.risk.slice(1);
39
+ return [
40
+ `Aegis found a likely fix for ${item.identifier}`,
41
+ "",
42
+ `Bug: ${compactText(item.title, 180)}`,
43
+ "",
44
+ compactText(plan.humanSummary, 700),
45
+ "",
46
+ `Fix: ${compactText(plan.proposedFix, 420)}`,
47
+ `Risk: ${risk}`,
48
+ "",
49
+ `Approve: /aegis proceed ${runId}`,
50
+ `Ask: /aegis ask ${runId} <question>`,
51
+ `Stop: /aegis stop ${runId}`,
52
+ ].join("\n");
53
+ }
54
+
55
+ export function formatIssuePlanComment(runId: string, plan: AgentPlanResult): string {
56
+ return `## Aegis RCA checkpoint
57
+
58
+ **Run:** \`${runId}\`
59
+ **Confidence:** ${plan.confidence}
60
+ **Risk:** ${plan.risk}
61
+
62
+ ### Human summary
63
+ ${plan.humanSummary}
64
+
65
+ ### Technical details
66
+ ${plan.technicalSummary}
67
+
68
+ ### Proposed fix
69
+ ${plan.proposedFix}
70
+
71
+ Reply with:
72
+ - \`/aegis proceed ${runId}\` to approve implementation
73
+ - \`/aegis ask ${runId} <question>\` to add context
74
+ - \`/aegis stop ${runId}\` to cancel
75
+ `;
76
+ }
77
+
78
+ export function formatNeedsInfoComment(question: string): string {
79
+ return `Aegis needs one detail before it can safely pick this up:
80
+
81
+ ${question}
82
+
83
+ Add the missing context, then mark the bug ready again.`;
84
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./types.js";
2
+ export * from "./constants.js";
3
+ export * from "./config.js";
4
+ export * from "./format.js";
5
+ export * from "./readiness.js";
6
+ export * from "./run-state.js";
@@ -0,0 +1,116 @@
1
+ import type { AegisConfig, ReadinessDecision, WorkItem } from "./types.js";
2
+
3
+ const BUG_WORDS = [
4
+ "bug",
5
+ "crash",
6
+ "error",
7
+ "exception",
8
+ "fails",
9
+ "failure",
10
+ "broken",
11
+ "regression",
12
+ "incorrect",
13
+ "wrong",
14
+ ];
15
+
16
+ const CONTEXT_WORDS = [
17
+ "expected",
18
+ "actual",
19
+ "reproduce",
20
+ "steps",
21
+ "stack",
22
+ "trace",
23
+ "error",
24
+ "logs",
25
+ "when",
26
+ "click",
27
+ "route",
28
+ "page",
29
+ "api",
30
+ ];
31
+
32
+ export function evaluateReadiness(item: WorkItem, config: AegisConfig): ReadinessDecision {
33
+ const labels = normalizedLabels(item.labels);
34
+ const titleAndBody = `${item.title}\n${item.body}`.toLowerCase();
35
+
36
+ if (!isBug(item, config, labels, titleAndBody)) {
37
+ return {
38
+ ready: false,
39
+ reason: "not labeled or described as a bug",
40
+ question: "Please label this as a bug once it describes broken behavior.",
41
+ };
42
+ }
43
+
44
+ if (!isReady(item, config, labels)) {
45
+ return {
46
+ ready: false,
47
+ reason: "not marked ready to implement",
48
+ question: `Please add the "${config.readyLabel}" label or move it to the configured ready status.`,
49
+ };
50
+ }
51
+
52
+ if (!item.repo) {
53
+ return {
54
+ ready: false,
55
+ reason: "no repository mapping",
56
+ question: "Which GitHub repository should Aegis fix this in?",
57
+ };
58
+ }
59
+
60
+ if (item.body.trim().length < 40 || !hasEnoughBugContext(titleAndBody)) {
61
+ return {
62
+ ready: false,
63
+ reason: "bug report is too thin",
64
+ question:
65
+ "What happened, what should have happened, and where can Aegis look to reproduce or verify it?",
66
+ };
67
+ }
68
+
69
+ const risk = hasRiskyLabel(labels, config) ? "high" : "low";
70
+ const autoImplementAllowed =
71
+ config.automationMode === "auto-low-risk" &&
72
+ risk === "low" &&
73
+ config.lowRiskLabels.some((label) => labels.has(label.toLowerCase()));
74
+
75
+ return {
76
+ ready: true,
77
+ reason: autoImplementAllowed
78
+ ? "ready low-risk bug; auto-implement allowed"
79
+ : "ready bug; approval checkpoint required",
80
+ risk,
81
+ autoImplementAllowed,
82
+ };
83
+ }
84
+
85
+ function isBug(
86
+ item: WorkItem,
87
+ config: AegisConfig,
88
+ labels: Set<string>,
89
+ titleAndBody: string,
90
+ ): boolean {
91
+ return (
92
+ labels.has(config.bugLabel.toLowerCase()) ||
93
+ labels.has(config.linear?.bugLabel?.toLowerCase() ?? "") ||
94
+ BUG_WORDS.some((word) => titleAndBody.includes(word))
95
+ );
96
+ }
97
+
98
+ function isReady(item: WorkItem, config: AegisConfig, labels: Set<string>): boolean {
99
+ return (
100
+ labels.has(config.readyLabel.toLowerCase()) ||
101
+ item.statusName?.toLowerCase() === config.linear?.readyStatusName.toLowerCase()
102
+ );
103
+ }
104
+
105
+ function hasEnoughBugContext(text: string): boolean {
106
+ const hits = CONTEXT_WORDS.filter((word) => text.includes(word)).length;
107
+ return hits >= 2;
108
+ }
109
+
110
+ function hasRiskyLabel(labels: Set<string>, config: AegisConfig): boolean {
111
+ return config.alwaysRequireApprovalLabels.some((label) => labels.has(label.toLowerCase()));
112
+ }
113
+
114
+ function normalizedLabels(labels: string[]): Set<string> {
115
+ return new Set(labels.map((label) => label.toLowerCase()));
116
+ }
@@ -0,0 +1,32 @@
1
+ import { branchNameFor } from "./format.js";
2
+ import type { AutomationMode, WorkItem, WorkRun } from "./types.js";
3
+
4
+ export function runIdFor(item: WorkItem): string {
5
+ const sourceId =
6
+ item.source === "github" ? `gh-${item.repo.replace("/", "-")}-${item.number}` : item.identifier;
7
+ return sourceId.toLowerCase().replace(/[^a-z0-9-]+/g, "-");
8
+ }
9
+
10
+ export function runFromItem(item: WorkItem, mode: AutomationMode): WorkRun {
11
+ const now = new Date().toISOString();
12
+ return {
13
+ id: runIdFor(item),
14
+ source: item.source,
15
+ externalId: item.id,
16
+ identifier: item.identifier,
17
+ repo: item.repo,
18
+ branchName: branchNameFor(item),
19
+ status: "claimed",
20
+ mode,
21
+ createdAt: now,
22
+ updatedAt: now,
23
+ itemUrl: item.url,
24
+ };
25
+ }
26
+
27
+ export function isActivelyTracked(item: WorkItem, labels: string[]): boolean {
28
+ const lowered = labels.map((label) => label.toLowerCase());
29
+ return lowered.some((label) =>
30
+ ["aegis:in-progress", "aegis:needs-info", "aegis:blocked", "aegis:pr-opened"].includes(label),
31
+ );
32
+ }
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Shared types for Aegis - an issue-driven AFK bug-fixing agent.
3
+ */
4
+
5
+ export type WorkSource = "github" | "linear";
6
+
7
+ export type AutomationMode = "plan-first" | "auto-low-risk";
8
+
9
+ export type ContextProfile = "minimal" | "production";
10
+
11
+ export type WorkItemStatus =
12
+ | "ready"
13
+ | "needs-info"
14
+ | "claimed"
15
+ | "planning"
16
+ | "awaiting-approval"
17
+ | "implementing"
18
+ | "pr-opened"
19
+ | "blocked"
20
+ | "failed"
21
+ | "cancelled";
22
+
23
+ export type RiskLevel = "low" | "medium" | "high";
24
+
25
+ export type WorkItem = {
26
+ source: WorkSource;
27
+ id: string;
28
+ number?: number;
29
+ identifier: string;
30
+ title: string;
31
+ body: string;
32
+ url: string;
33
+ repo: string;
34
+ labels: string[];
35
+ statusName?: string;
36
+ createdAt?: string;
37
+ updatedAt?: string;
38
+ author?: string;
39
+ };
40
+
41
+ export type ReadinessDecision =
42
+ | {
43
+ ready: true;
44
+ reason: string;
45
+ risk: RiskLevel;
46
+ autoImplementAllowed: boolean;
47
+ }
48
+ | {
49
+ ready: false;
50
+ reason: string;
51
+ question: string;
52
+ };
53
+
54
+ export type WorkRun = {
55
+ id: string;
56
+ source: WorkSource;
57
+ externalId: string;
58
+ identifier: string;
59
+ repo: string;
60
+ branchName: string;
61
+ status: WorkItemStatus;
62
+ mode: AutomationMode;
63
+ createdAt: string;
64
+ updatedAt: string;
65
+ itemUrl: string;
66
+ prUrl?: string;
67
+ lastError?: string;
68
+ planSummary?: string;
69
+ };
70
+
71
+ export type RunRef = {
72
+ runId: string;
73
+ source: WorkSource;
74
+ identifier: string;
75
+ repo: string;
76
+ branchName: string;
77
+ itemUrl: string;
78
+ };
79
+
80
+ export type AgentCommand = "plan" | "implement";
81
+
82
+ export type AgentRunContext = {
83
+ runId: string;
84
+ command: AgentCommand;
85
+ mode: AutomationMode;
86
+ contextProfile: ContextProfile;
87
+ item: WorkItem;
88
+ branchName: string;
89
+ baseBranch: string;
90
+ repoCloneUrl: string;
91
+ production?: {
92
+ databaseUrl?: string;
93
+ vercelToken?: string;
94
+ vercelProjectId?: string;
95
+ vercelTeamId?: string;
96
+ logLookbackMinutes: number;
97
+ };
98
+ approval?: {
99
+ approvedBy: string;
100
+ approvedAt: string;
101
+ };
102
+ };
103
+
104
+ export type AgentPlanResult = {
105
+ outcome: "awaiting-approval" | "needs-info" | "blocked";
106
+ confidence: "low" | "medium" | "high";
107
+ risk: RiskLevel;
108
+ technicalSummary: string;
109
+ humanSummary: string;
110
+ proposedFix: string;
111
+ question?: string;
112
+ blocker?: string;
113
+ requiresApproval: boolean;
114
+ };
115
+
116
+ export type AgentImplementationResult = {
117
+ outcome: "implemented" | "blocked" | "needs-info";
118
+ confidence: "low" | "medium" | "high";
119
+ risk: RiskLevel;
120
+ technicalSummary: string;
121
+ humanSummary: string;
122
+ filesChanged: string[];
123
+ testsRun: string[];
124
+ branchName?: string;
125
+ commitSha?: string;
126
+ prTitle?: string;
127
+ prBody?: string;
128
+ question?: string;
129
+ blocker?: string;
130
+ };
131
+
132
+ export type AegisConfig = {
133
+ workerUrl?: string;
134
+ monitoredRepo: string;
135
+ baseBranch: string;
136
+ automationMode: AutomationMode;
137
+ contextProfile: ContextProfile;
138
+ maxConcurrentRuns: number;
139
+ readyLabel: string;
140
+ bugLabel: string;
141
+ inProgressLabel: string;
142
+ needsInfoLabel: string;
143
+ blockedLabel: string;
144
+ prOpenedLabel: string;
145
+ lowRiskLabels: string[];
146
+ alwaysRequireApprovalLabels: string[];
147
+ alwaysRequireApprovalPaths: string[];
148
+ github?: {
149
+ appId?: string;
150
+ privateKey?: string;
151
+ installationId?: string;
152
+ webhookSecret?: string;
153
+ token?: string;
154
+ };
155
+ linear?: {
156
+ apiKey?: string;
157
+ webhookSecret?: string;
158
+ teamId?: string;
159
+ projectId?: string;
160
+ readyStatusName: string;
161
+ inProgressStatusName: string;
162
+ needsInfoStatusName?: string;
163
+ blockedStatusName?: string;
164
+ bugLabel: string;
165
+ readyLabel?: string;
166
+ };
167
+ telegram?: {
168
+ botToken?: string;
169
+ chatId?: string;
170
+ webhookSecret?: string;
171
+ };
172
+ production?: {
173
+ databaseUrl?: string;
174
+ vercelToken?: string;
175
+ vercelProjectId?: string;
176
+ vercelTeamId?: string;
177
+ logLookbackMinutes: number;
178
+ };
179
+ model: string;
180
+ };
181
+
182
+ export type Bindings = {
183
+ Sandbox: DurableObjectNamespace;
184
+ AEGIS_WORKER_URL?: string;
185
+ MONITORED_REPO: string;
186
+ BASE_BRANCH?: string;
187
+ AUTOMATION_MODE?: AutomationMode;
188
+ CONTEXT_PROFILE?: ContextProfile;
189
+ MAX_CONCURRENT_RUNS?: string;
190
+ READY_LABEL?: string;
191
+ BUG_LABEL?: string;
192
+ IN_PROGRESS_LABEL?: string;
193
+ NEEDS_INFO_LABEL?: string;
194
+ BLOCKED_LABEL?: string;
195
+ PR_OPENED_LABEL?: string;
196
+ LOW_RISK_LABELS?: string;
197
+ ALWAYS_REQUIRE_APPROVAL_LABELS?: string;
198
+ ALWAYS_REQUIRE_APPROVAL_PATHS?: string;
199
+ GITHUB_APP_ID?: string;
200
+ GITHUB_APP_PRIVATE_KEY?: string;
201
+ GITHUB_INSTALLATION_ID?: string;
202
+ GITHUB_WEBHOOK_SECRET?: string;
203
+ GITHUB_TOKEN?: string;
204
+ LINEAR_API_KEY?: string;
205
+ LINEAR_WEBHOOK_SECRET?: string;
206
+ LINEAR_TEAM_ID?: string;
207
+ LINEAR_PROJECT_ID?: string;
208
+ LINEAR_READY_STATUS?: string;
209
+ LINEAR_IN_PROGRESS_STATUS?: string;
210
+ LINEAR_NEEDS_INFO_STATUS?: string;
211
+ LINEAR_BLOCKED_STATUS?: string;
212
+ LINEAR_BUG_LABEL?: string;
213
+ LINEAR_READY_LABEL?: string;
214
+ TELEGRAM_BOT_TOKEN?: string;
215
+ TELEGRAM_CHAT_ID?: string;
216
+ TELEGRAM_WEBHOOK_SECRET?: string;
217
+ DATABASE_URL?: string;
218
+ VERCEL_TOKEN?: string;
219
+ VERCEL_PROJECT_ID?: string;
220
+ VERCEL_TEAM_ID?: string;
221
+ VERCEL_LOG_LOOKBACK_MINUTES?: string;
222
+ AGENT_MODEL?: string;
223
+ OPENAI_API_KEY?: string;
224
+ };
225
+
226
+ export type AegisCliConfig = {
227
+ workerUrl?: string;
228
+ monitoredRepo?: string;
229
+ baseBranch?: string;
230
+ automationMode?: AutomationMode;
231
+ contextProfile?: ContextProfile;
232
+ readyLabel?: string;
233
+ bugLabel?: string;
234
+ githubAppId?: string;
235
+ githubAppPrivateKey?: string;
236
+ githubInstallationId?: string;
237
+ githubWebhookSecret?: string;
238
+ githubToken?: string;
239
+ linearApiKey?: string;
240
+ linearWebhookSecret?: string;
241
+ linearTeamId?: string;
242
+ linearProjectId?: string;
243
+ linearReadyStatus?: string;
244
+ linearInProgressStatus?: string;
245
+ linearNeedsInfoStatus?: string;
246
+ linearBlockedStatus?: string;
247
+ linearBugLabel?: string;
248
+ telegramBotToken?: string;
249
+ telegramChatId?: string;
250
+ telegramWebhookSecret?: string;
251
+ databaseUrl?: string;
252
+ vercelToken?: string;
253
+ vercelProjectId?: string;
254
+ vercelTeamId?: string;
255
+ agentModel?: string;
256
+ openaiApiKey?: string;
257
+ };