@stackbilt/aegis-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.
- package/package.json +96 -0
- package/schema.sql +586 -0
- package/src/adapters/voice/cloudflare-agent.ts +34 -0
- package/src/auth.ts +124 -0
- package/src/bluesky.ts +464 -0
- package/src/claude-tools/content.ts +188 -0
- package/src/claude-tools/email.ts +69 -0
- package/src/claude-tools/github.ts +440 -0
- package/src/claude-tools/goals.ts +116 -0
- package/src/claude-tools/index.ts +353 -0
- package/src/claude-tools/web.ts +59 -0
- package/src/claude.ts +406 -0
- package/src/codebeast.ts +200 -0
- package/src/composite.ts +715 -0
- package/src/content/column.ts +80 -0
- package/src/content/hero-image.ts +47 -0
- package/src/content/index.ts +27 -0
- package/src/content/journal.ts +91 -0
- package/src/content/roundtable.ts +163 -0
- package/src/core.ts +309 -0
- package/src/dashboard.ts +620 -0
- package/src/decision-docs.ts +284 -0
- package/src/dispatch.ts +13 -0
- package/src/edge-env.ts +58 -0
- package/src/email.ts +850 -0
- package/src/exports.ts +156 -0
- package/src/github-projects.ts +312 -0
- package/src/github.ts +670 -0
- package/src/groq.ts +247 -0
- package/src/health-page.ts +578 -0
- package/src/index.ts +89 -0
- package/src/kernel/argus-actions.ts +397 -0
- package/src/kernel/argus-correlation.ts +639 -0
- package/src/kernel/board.ts +91 -0
- package/src/kernel/briefing.ts +177 -0
- package/src/kernel/classify-memory-topic.ts +166 -0
- package/src/kernel/cognition.ts +377 -0
- package/src/kernel/court-cards.ts +163 -0
- package/src/kernel/dispatch.ts +587 -0
- package/src/kernel/domain.ts +50 -0
- package/src/kernel/dynamic-tools.ts +322 -0
- package/src/kernel/executor-port.ts +45 -0
- package/src/kernel/executors/claude.ts +73 -0
- package/src/kernel/executors/direct.ts +237 -0
- package/src/kernel/executors/groq.ts +18 -0
- package/src/kernel/executors/index.ts +87 -0
- package/src/kernel/executors/tarotscript.ts +104 -0
- package/src/kernel/executors/workers-ai.ts +54 -0
- package/src/kernel/insight-cache.ts +76 -0
- package/src/kernel/memory/agenda.ts +200 -0
- package/src/kernel/memory/blocks.ts +188 -0
- package/src/kernel/memory/consolidation.ts +194 -0
- package/src/kernel/memory/episodic.ts +241 -0
- package/src/kernel/memory/goals.ts +156 -0
- package/src/kernel/memory/graph.ts +290 -0
- package/src/kernel/memory/index.ts +11 -0
- package/src/kernel/memory/insights.ts +316 -0
- package/src/kernel/memory/procedural.ts +467 -0
- package/src/kernel/memory/pruning.ts +67 -0
- package/src/kernel/memory/recall.ts +367 -0
- package/src/kernel/memory/semantic.ts +315 -0
- package/src/kernel/memory/synthesis.ts +161 -0
- package/src/kernel/memory-adapter.ts +369 -0
- package/src/kernel/memory-guardrails.ts +76 -0
- package/src/kernel/port.ts +23 -0
- package/src/kernel/resilience.ts +322 -0
- package/src/kernel/router.ts +471 -0
- package/src/kernel/scheduled/agent-dispatch.ts +252 -0
- package/src/kernel/scheduled/argus-analytics.ts +247 -0
- package/src/kernel/scheduled/argus-heartbeat.ts +320 -0
- package/src/kernel/scheduled/argus-notify.ts +348 -0
- package/src/kernel/scheduled/board-sync.ts +110 -0
- package/src/kernel/scheduled/ci-watcher.ts +125 -0
- package/src/kernel/scheduled/cognitive-metrics.ts +377 -0
- package/src/kernel/scheduled/consolidation.ts +229 -0
- package/src/kernel/scheduled/content-drip.ts +47 -0
- package/src/kernel/scheduled/content.ts +6 -0
- package/src/kernel/scheduled/conversation-facts.ts +204 -0
- package/src/kernel/scheduled/cost-report.ts +84 -0
- package/src/kernel/scheduled/curiosity.ts +219 -0
- package/src/kernel/scheduled/dev-activity.ts +44 -0
- package/src/kernel/scheduled/digest.ts +317 -0
- package/src/kernel/scheduled/dreaming/agenda-triage.ts +115 -0
- package/src/kernel/scheduled/dreaming/facts.ts +239 -0
- package/src/kernel/scheduled/dreaming/index.ts +8 -0
- package/src/kernel/scheduled/dreaming/llm.ts +33 -0
- package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +124 -0
- package/src/kernel/scheduled/dreaming/persona.ts +75 -0
- package/src/kernel/scheduled/dreaming/symbolic.ts +31 -0
- package/src/kernel/scheduled/dreaming/task-proposals.ts +80 -0
- package/src/kernel/scheduled/dreaming.ts +66 -0
- package/src/kernel/scheduled/entropy.ts +149 -0
- package/src/kernel/scheduled/escalation.ts +192 -0
- package/src/kernel/scheduled/feed-watcher.ts +206 -0
- package/src/kernel/scheduled/goals.ts +214 -0
- package/src/kernel/scheduled/governance.ts +41 -0
- package/src/kernel/scheduled/heartbeat.ts +220 -0
- package/src/kernel/scheduled/inbox-processor.ts +174 -0
- package/src/kernel/scheduled/index.ts +245 -0
- package/src/kernel/scheduled/issue-proposer.ts +478 -0
- package/src/kernel/scheduled/issue-watcher.ts +128 -0
- package/src/kernel/scheduled/pr-automerge.ts +213 -0
- package/src/kernel/scheduled/product-health.ts +107 -0
- package/src/kernel/scheduled/reflection.ts +373 -0
- package/src/kernel/scheduled/self-improvement.ts +114 -0
- package/src/kernel/scheduled/social-engage.ts +175 -0
- package/src/kernel/scheduled/task-audit.ts +60 -0
- package/src/kernel/symbolic.ts +156 -0
- package/src/kernel/types.ts +145 -0
- package/src/landing.ts +1190 -0
- package/src/lib/audit-chain/chain.ts +28 -0
- package/src/lib/audit-chain/types.ts +12 -0
- package/src/lib/observability/errors.ts +55 -0
- package/src/markdown.ts +164 -0
- package/src/mcp/handlers.ts +647 -0
- package/src/mcp/server.ts +184 -0
- package/src/mcp/tools.ts +316 -0
- package/src/mcp-client.ts +275 -0
- package/src/mcp-server.ts +2 -0
- package/src/operator/config.example.ts +60 -0
- package/src/operator/config.ts +60 -0
- package/src/operator/index.ts +46 -0
- package/src/operator/persona.example.ts +34 -0
- package/src/operator/persona.ts +34 -0
- package/src/operator/prompt-builder.ts +190 -0
- package/src/operator/types.ts +43 -0
- package/src/pulse.ts +1179 -0
- package/src/routes/bluesky.ts +116 -0
- package/src/routes/cc-tasks.ts +328 -0
- package/src/routes/codebeast.ts +1 -0
- package/src/routes/content.ts +194 -0
- package/src/routes/conversations.ts +25 -0
- package/src/routes/dynamic-tools.ts +111 -0
- package/src/routes/feedback.ts +192 -0
- package/src/routes/health.ts +147 -0
- package/src/routes/messages.ts +228 -0
- package/src/routes/observability.ts +82 -0
- package/src/routes/operator-logs.ts +42 -0
- package/src/routes/pages.ts +96 -0
- package/src/routes/sessions.ts +54 -0
- package/src/sanitize.ts +73 -0
- package/src/schema-enums.ts +155 -0
- package/src/search.ts +112 -0
- package/src/task-intelligence.ts +497 -0
- package/src/types.ts +194 -0
- package/src/ui.ts +5 -0
- package/src/version.ts +3 -0
- package/src/workers-ai-chat.ts +333 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
// GitHub in-process tool definitions + handlers
|
|
2
|
+
// Extracted from claude-tools.ts for LOC governance
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
getRepoTree,
|
|
6
|
+
getFileContent,
|
|
7
|
+
getRecentCommits,
|
|
8
|
+
listIssues,
|
|
9
|
+
listPullRequests,
|
|
10
|
+
listOrgRepos,
|
|
11
|
+
createIssue,
|
|
12
|
+
createBranch,
|
|
13
|
+
getFileSha,
|
|
14
|
+
updateFile,
|
|
15
|
+
createPullRequest,
|
|
16
|
+
createBlob,
|
|
17
|
+
getBaseTreeSha,
|
|
18
|
+
createTree,
|
|
19
|
+
createGitCommit,
|
|
20
|
+
updateRef,
|
|
21
|
+
listWorkflowRuns,
|
|
22
|
+
getCombinedStatus,
|
|
23
|
+
mergePullRequest,
|
|
24
|
+
resolveRepoName,
|
|
25
|
+
} from '../github.js';
|
|
26
|
+
import { addAgendaItem } from '../kernel/memory/index.js';
|
|
27
|
+
import { ensureOnBoard } from '../kernel/board.js';
|
|
28
|
+
|
|
29
|
+
// ─── Tool definitions ────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
const REPO_PARAM = { repo: { type: 'string', description: 'GitHub repo in "org/name" format. Defaults to the AEGIS repo.' } };
|
|
32
|
+
|
|
33
|
+
const LIST_REPO_FILES_TOOL = {
|
|
34
|
+
name: 'list_repo_files',
|
|
35
|
+
description: 'List all files in a GitHub repository as a flat path list. Defaults to the AEGIS repo. Use this to understand repo structure before reading specific files.',
|
|
36
|
+
input_schema: { type: 'object' as const, properties: { ...REPO_PARAM }, required: [] },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const READ_REPO_FILE_TOOL = {
|
|
40
|
+
name: 'read_repo_file',
|
|
41
|
+
description: 'Read the content of a file from a GitHub repository. Defaults to the AEGIS repo. Content is truncated at 8000 chars.',
|
|
42
|
+
input_schema: {
|
|
43
|
+
type: 'object' as const,
|
|
44
|
+
properties: {
|
|
45
|
+
path: { type: 'string', description: 'File path relative to repo root (e.g., "web/src/claude.ts")' },
|
|
46
|
+
...REPO_PARAM,
|
|
47
|
+
},
|
|
48
|
+
required: ['path'],
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const GET_REPO_COMMITS_TOOL = {
|
|
53
|
+
name: 'get_repo_commits',
|
|
54
|
+
description: 'Get recent commits from a GitHub repository. Defaults to the AEGIS repo.',
|
|
55
|
+
input_schema: {
|
|
56
|
+
type: 'object' as const,
|
|
57
|
+
properties: {
|
|
58
|
+
limit: { type: 'number', description: 'Number of commits to fetch (default: 10, max: 20)' },
|
|
59
|
+
...REPO_PARAM,
|
|
60
|
+
},
|
|
61
|
+
required: [],
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const GET_REPO_ISSUES_TOOL = {
|
|
66
|
+
name: 'get_repo_issues',
|
|
67
|
+
description: 'List GitHub issues for a repository. Defaults to the AEGIS repo. Check before creating new issues to avoid duplicates.',
|
|
68
|
+
input_schema: {
|
|
69
|
+
type: 'object' as const,
|
|
70
|
+
properties: {
|
|
71
|
+
state: { type: 'string', enum: ['open', 'closed', 'all'], description: 'Issue state filter (default: open)' },
|
|
72
|
+
...REPO_PARAM,
|
|
73
|
+
},
|
|
74
|
+
required: [],
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const GET_REPO_PRS_TOOL = {
|
|
79
|
+
name: 'get_repo_prs',
|
|
80
|
+
description: 'List pull requests for a GitHub repository. Defaults to the AEGIS repo.',
|
|
81
|
+
input_schema: {
|
|
82
|
+
type: 'object' as const,
|
|
83
|
+
properties: {
|
|
84
|
+
state: { type: 'string', enum: ['open', 'closed', 'all'], description: 'PR state filter (default: open)' },
|
|
85
|
+
...REPO_PARAM,
|
|
86
|
+
},
|
|
87
|
+
required: [],
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const CREATE_IMPROVEMENT_ISSUE_TOOL = {
|
|
92
|
+
name: 'create_improvement_issue',
|
|
93
|
+
description: 'Create a GitHub issue and mirror it to the agent agenda. Use for improvements, bugs, or tasks requiring human review. Defaults to the AEGIS repo.',
|
|
94
|
+
input_schema: {
|
|
95
|
+
type: 'object' as const,
|
|
96
|
+
properties: {
|
|
97
|
+
title: { type: 'string', description: 'Issue title — concise and specific' },
|
|
98
|
+
body: { type: 'string', description: 'Full issue body in Markdown — problem, proposed solution, affected files' },
|
|
99
|
+
labels: { type: 'array', items: { type: 'string' }, description: 'GitHub labels (e.g., ["self-improvement", "enhancement"])' },
|
|
100
|
+
priority: { type: 'string', enum: ['low', 'medium', 'high'], description: 'Agenda priority' },
|
|
101
|
+
...REPO_PARAM,
|
|
102
|
+
},
|
|
103
|
+
required: ['title', 'body', 'priority'],
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const CREATE_IMPROVEMENT_PR_TOOL = {
|
|
108
|
+
name: 'create_improvement_pr',
|
|
109
|
+
description: 'Create a GitHub branch, apply a single-file change, and open a pull request. Single-file changes only. Defaults to the AEGIS repo.',
|
|
110
|
+
input_schema: {
|
|
111
|
+
type: 'object' as const,
|
|
112
|
+
properties: {
|
|
113
|
+
title: { type: 'string', description: 'PR title' },
|
|
114
|
+
body: { type: 'string', description: 'PR description in Markdown — what changed and why' },
|
|
115
|
+
file_path: { type: 'string', description: 'Repo-relative path to file to modify (e.g., "web/src/groq.ts")' },
|
|
116
|
+
new_content: { type: 'string', description: 'Complete new file content (full file, not a diff)' },
|
|
117
|
+
branch_name: { type: 'string', description: 'Branch name to create (e.g., "aegis/improve-error-handling")' },
|
|
118
|
+
commit_message: { type: 'string', description: 'Git commit message for the file change' },
|
|
119
|
+
...REPO_PARAM,
|
|
120
|
+
},
|
|
121
|
+
required: ['title', 'body', 'file_path', 'new_content', 'branch_name', 'commit_message'],
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const CREATE_MULTI_FILE_PR_TOOL = {
|
|
126
|
+
name: 'create_multi_file_pr',
|
|
127
|
+
description: 'Create a GitHub branch with multiple file changes in a single atomic commit, then open a pull request. Use when a change touches 2+ files. Defaults to the AEGIS repo.',
|
|
128
|
+
input_schema: {
|
|
129
|
+
type: 'object' as const,
|
|
130
|
+
properties: {
|
|
131
|
+
title: { type: 'string', description: 'PR title' },
|
|
132
|
+
body: { type: 'string', description: 'PR description in Markdown' },
|
|
133
|
+
branch_name: { type: 'string', description: 'Branch name (e.g., "aegis/multi-file-fix")' },
|
|
134
|
+
commit_message: { type: 'string', description: 'Git commit message' },
|
|
135
|
+
files: {
|
|
136
|
+
type: 'array',
|
|
137
|
+
description: 'Array of file changes (max 20)',
|
|
138
|
+
items: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
path: { type: 'string', description: 'File path relative to repo root' },
|
|
142
|
+
content: { type: 'string', description: 'Complete new file content' },
|
|
143
|
+
},
|
|
144
|
+
required: ['path', 'content'],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
...REPO_PARAM,
|
|
148
|
+
},
|
|
149
|
+
required: ['title', 'body', 'branch_name', 'commit_message', 'files'],
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const LIST_ORG_REPOS_TOOL = {
|
|
154
|
+
name: 'list_org_repos',
|
|
155
|
+
description: 'List repositories in a GitHub organization. Useful for discovering repos, checking what projects exist, and cross-repo awareness. Defaults to the org derived from GITHUB_REPO.',
|
|
156
|
+
input_schema: {
|
|
157
|
+
type: 'object' as const,
|
|
158
|
+
properties: {
|
|
159
|
+
org: { type: 'string', description: 'GitHub org (default: derived from GITHUB_REPO)' },
|
|
160
|
+
type: { type: 'string', enum: ['all', 'public', 'private'], description: 'Repo type filter (default: all)' },
|
|
161
|
+
},
|
|
162
|
+
required: [],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const GET_CI_STATUS_TOOL = {
|
|
167
|
+
name: 'get_ci_status',
|
|
168
|
+
description: 'Get CI/CD status for a branch or commit. Shows both GitHub Actions workflow runs and commit status checks. Defaults to the AEGIS repo main branch.',
|
|
169
|
+
input_schema: {
|
|
170
|
+
type: 'object' as const,
|
|
171
|
+
properties: {
|
|
172
|
+
ref: { type: 'string', description: 'Branch name or commit SHA (default: "main")' },
|
|
173
|
+
...REPO_PARAM,
|
|
174
|
+
},
|
|
175
|
+
required: [],
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const TRIGGER_WORKER_DEPLOY_TOOL = {
|
|
180
|
+
name: 'trigger_worker_deploy',
|
|
181
|
+
description: 'Propose a Worker deployment for operator approval. NEVER auto-deploys — always creates a [PROPOSED ACTION] agenda item. Use when CI is green and a deploy is warranted. Defaults to the AEGIS repo.',
|
|
182
|
+
input_schema: {
|
|
183
|
+
type: 'object' as const,
|
|
184
|
+
properties: {
|
|
185
|
+
reason: { type: 'string', description: 'Why a deploy is needed (e.g., "CI passed on PR #42, ready to ship")' },
|
|
186
|
+
pr_numbers: { type: 'array', items: { type: 'number' }, description: 'PR numbers included in this deploy (optional)' },
|
|
187
|
+
...REPO_PARAM,
|
|
188
|
+
},
|
|
189
|
+
required: ['reason'],
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const MERGE_PR_TOOL = {
|
|
194
|
+
name: 'merge_pull_request',
|
|
195
|
+
description: 'Merge an approved pull request. Uses squash by default for clean history. Auto-resolves the matching agenda item. Defaults to the AEGIS repo.',
|
|
196
|
+
input_schema: {
|
|
197
|
+
type: 'object' as const,
|
|
198
|
+
properties: {
|
|
199
|
+
pull_number: { type: 'number', description: 'PR number to merge' },
|
|
200
|
+
method: { type: 'string', enum: ['merge', 'squash', 'rebase'], description: 'Merge strategy (default: squash)' },
|
|
201
|
+
commit_message: { type: 'string', description: 'Custom squash/merge commit message (optional)' },
|
|
202
|
+
...REPO_PARAM,
|
|
203
|
+
},
|
|
204
|
+
required: ['pull_number'],
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const GITHUB_TOOLS = [
|
|
209
|
+
LIST_ORG_REPOS_TOOL,
|
|
210
|
+
LIST_REPO_FILES_TOOL,
|
|
211
|
+
READ_REPO_FILE_TOOL,
|
|
212
|
+
GET_REPO_COMMITS_TOOL,
|
|
213
|
+
GET_REPO_ISSUES_TOOL,
|
|
214
|
+
GET_REPO_PRS_TOOL,
|
|
215
|
+
GET_CI_STATUS_TOOL,
|
|
216
|
+
TRIGGER_WORKER_DEPLOY_TOOL,
|
|
217
|
+
CREATE_IMPROVEMENT_ISSUE_TOOL,
|
|
218
|
+
CREATE_IMPROVEMENT_PR_TOOL,
|
|
219
|
+
CREATE_MULTI_FILE_PR_TOOL,
|
|
220
|
+
MERGE_PR_TOOL,
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
// ─── Handler ─────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
export async function handleGithubTool(
|
|
226
|
+
db: D1Database,
|
|
227
|
+
name: string,
|
|
228
|
+
input: Record<string, unknown>,
|
|
229
|
+
githubToken: string,
|
|
230
|
+
githubRepo: string,
|
|
231
|
+
): Promise<string | null> {
|
|
232
|
+
const targetRepo = resolveRepoName((input.repo as string | undefined) ?? githubRepo);
|
|
233
|
+
|
|
234
|
+
if (name === 'list_org_repos') {
|
|
235
|
+
const org = (input.org as string | undefined) ?? githubRepo.split('/')[0];
|
|
236
|
+
const type = (input.type as 'all' | 'public' | 'private' | undefined) ?? 'all';
|
|
237
|
+
const repos = await listOrgRepos(githubToken, org, type);
|
|
238
|
+
if (repos.length === 0) return `No repositories found in ${org}.`;
|
|
239
|
+
return repos.map(r =>
|
|
240
|
+
`**${r.name}** (${r.visibility}) — ${r.description ?? 'no description'}\n ${r.language ?? 'n/a'} · updated ${r.updated_at.slice(0, 10)} · ${r.url}`
|
|
241
|
+
).join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (name === 'list_repo_files') {
|
|
245
|
+
const paths = await getRepoTree(githubToken, targetRepo);
|
|
246
|
+
return `Repository file tree (${paths.length} files):\n${paths.join('\n')}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (name === 'read_repo_file') {
|
|
250
|
+
const path = input.path as string;
|
|
251
|
+
try {
|
|
252
|
+
const content = await getFileContent(githubToken, targetRepo, path);
|
|
253
|
+
const truncated = content.length > 8000
|
|
254
|
+
? content.slice(0, 8000) + `\n\n[... truncated at 8000 of ${content.length} chars ...]`
|
|
255
|
+
: content;
|
|
256
|
+
return `File: ${path}\n\`\`\`\n${truncated}\n\`\`\``;
|
|
257
|
+
} catch (err) {
|
|
258
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
259
|
+
if (msg.includes('404')) {
|
|
260
|
+
return `File not found: "${path}" in ${targetRepo}. Call list_repo_files to get the correct path, then retry.`;
|
|
261
|
+
}
|
|
262
|
+
throw err;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (name === 'get_repo_commits') {
|
|
267
|
+
const limit = Math.min((input.limit as number | undefined) ?? 10, 20);
|
|
268
|
+
const commits = await getRecentCommits(githubToken, targetRepo, limit);
|
|
269
|
+
return commits.map(c =>
|
|
270
|
+
`${c.sha.slice(0, 8)} — ${c.message} (${c.author}, ${c.date.slice(0, 10)})`
|
|
271
|
+
).join('\n');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (name === 'get_repo_issues') {
|
|
275
|
+
const state = (input.state as 'open' | 'closed' | 'all' | undefined) ?? 'open';
|
|
276
|
+
const issues = await listIssues(githubToken, targetRepo, state);
|
|
277
|
+
if (issues.length === 0) return 'No issues found.';
|
|
278
|
+
return issues.map(i =>
|
|
279
|
+
`#${i.number}: ${i.title} [${i.state}]${i.labels.length ? ` (${i.labels.join(', ')})` : ''} — ${i.url}`
|
|
280
|
+
).join('\n');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (name === 'get_repo_prs') {
|
|
284
|
+
const state = (input.state as 'open' | 'closed' | 'all' | undefined) ?? 'open';
|
|
285
|
+
const prs = await listPullRequests(githubToken, targetRepo, state);
|
|
286
|
+
if (prs.length === 0) return 'No pull requests found.';
|
|
287
|
+
return prs.map(p =>
|
|
288
|
+
`#${p.number}: ${p.title} [${p.state}${p.merged ? ', merged' : ''}] ${p.head} → ${p.base} — ${p.url}`
|
|
289
|
+
).join('\n');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (name === 'get_ci_status') {
|
|
293
|
+
const ref = (input.ref as string | undefined) ?? 'main';
|
|
294
|
+
const [status, runs] = await Promise.all([
|
|
295
|
+
getCombinedStatus(githubToken, targetRepo, ref),
|
|
296
|
+
listWorkflowRuns(githubToken, targetRepo, ref, 5),
|
|
297
|
+
]);
|
|
298
|
+
const parts: string[] = [];
|
|
299
|
+
if (status.total > 0) {
|
|
300
|
+
parts.push(`**Commit status** (${ref}): ${status.state} (${status.total} checks)`);
|
|
301
|
+
for (const s of status.statuses) {
|
|
302
|
+
parts.push(` ${s.state === 'success' ? '+' : s.state === 'pending' ? '~' : '-'} ${s.context}: ${s.state} — ${s.description}`);
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
parts.push(`**Commit status** (${ref}): no status checks`);
|
|
306
|
+
}
|
|
307
|
+
if (runs.length > 0) {
|
|
308
|
+
parts.push(`\n**Actions** (${ref}):`);
|
|
309
|
+
for (const r of runs) {
|
|
310
|
+
const icon = r.conclusion === 'success' ? '+' : r.conclusion === 'failure' ? '-' : '~';
|
|
311
|
+
parts.push(` ${icon} ${r.name}: ${r.conclusion ?? r.status} (${r.event}, ${r.created_at.slice(0, 10)}) — ${r.url}`);
|
|
312
|
+
}
|
|
313
|
+
} else {
|
|
314
|
+
parts.push(`\n**Actions** (${ref}): no workflow runs`);
|
|
315
|
+
}
|
|
316
|
+
return parts.join('\n');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (name === 'merge_pull_request') {
|
|
320
|
+
const pullNumber = input.pull_number as number;
|
|
321
|
+
const method = (input.method as 'merge' | 'squash' | 'rebase' | undefined) ?? 'squash';
|
|
322
|
+
const commitMessage = input.commit_message as string | undefined;
|
|
323
|
+
const result = await mergePullRequest(githubToken, targetRepo, pullNumber, method, commitMessage);
|
|
324
|
+
if (!result.merged) return `Failed to merge PR #${pullNumber}: ${result.message}`;
|
|
325
|
+
// Auto-resolve matching agenda item
|
|
326
|
+
await db.prepare(
|
|
327
|
+
"UPDATE agent_agenda SET status = 'done', resolved_at = datetime('now') WHERE status = 'active' AND item LIKE ?"
|
|
328
|
+
).bind(`%PR #${pullNumber}%`).run();
|
|
329
|
+
return `Merged PR #${pullNumber} via ${method} (sha: ${result.sha.slice(0, 8)}). Agenda item auto-resolved.`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (name === 'create_improvement_issue') {
|
|
333
|
+
const i = input as { title: string; body: string; labels?: string[]; priority: 'low' | 'medium' | 'high' };
|
|
334
|
+
const { number, url } = await createIssue(
|
|
335
|
+
githubToken, targetRepo, i.title, i.body,
|
|
336
|
+
i.labels ?? ['self-improvement'],
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Add to project board
|
|
340
|
+
const projectIdRow = await db.prepare(
|
|
341
|
+
"SELECT received_at FROM web_events WHERE event_id = 'board_project_id'"
|
|
342
|
+
).first<{ received_at: string }>();
|
|
343
|
+
if (projectIdRow?.received_at) {
|
|
344
|
+
await ensureOnBoard(db, githubToken, projectIdRow.received_at, targetRepo, number, i.title, 'backlog').catch(() => {});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return `Created GitHub issue #${number}: ${url}`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (name === 'create_improvement_pr') {
|
|
351
|
+
const i = input as {
|
|
352
|
+
title: string;
|
|
353
|
+
body: string;
|
|
354
|
+
file_path: string;
|
|
355
|
+
new_content: string;
|
|
356
|
+
branch_name: string;
|
|
357
|
+
commit_message: string;
|
|
358
|
+
};
|
|
359
|
+
// 1. Get HEAD SHA from most recent commit
|
|
360
|
+
const commits = await getRecentCommits(githubToken, targetRepo, 1);
|
|
361
|
+
if (commits.length === 0) throw new Error('Could not resolve HEAD SHA');
|
|
362
|
+
const headSha = commits[0].sha;
|
|
363
|
+
// 2. Create branch
|
|
364
|
+
await createBranch(githubToken, targetRepo, i.branch_name, headSha);
|
|
365
|
+
// 3. Get current file SHA for optimistic concurrency
|
|
366
|
+
const fileSha = await getFileSha(githubToken, targetRepo, i.file_path, i.branch_name);
|
|
367
|
+
// 4. Update file on new branch
|
|
368
|
+
await updateFile(githubToken, targetRepo, i.file_path, i.new_content, i.commit_message, fileSha, i.branch_name);
|
|
369
|
+
// 5. Open PR
|
|
370
|
+
const { number, url } = await createPullRequest(githubToken, targetRepo, i.title, i.body, i.branch_name);
|
|
371
|
+
return `Created PR #${number}: ${url}\nBranch: ${i.branch_name}\nFile changed: ${i.file_path}`;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (name === 'create_multi_file_pr') {
|
|
375
|
+
const i = input as {
|
|
376
|
+
title: string;
|
|
377
|
+
body: string;
|
|
378
|
+
branch_name: string;
|
|
379
|
+
commit_message: string;
|
|
380
|
+
files: Array<{ path: string; content: string }>;
|
|
381
|
+
};
|
|
382
|
+
if (!i.files || i.files.length === 0) return 'Error: files array is empty.';
|
|
383
|
+
if (i.files.length > 20) return 'Error: too many files (max 20 per PR).';
|
|
384
|
+
|
|
385
|
+
// 1. Get HEAD SHA
|
|
386
|
+
const commits = await getRecentCommits(githubToken, targetRepo, 1);
|
|
387
|
+
if (commits.length === 0) throw new Error('Could not resolve HEAD SHA');
|
|
388
|
+
const headSha = commits[0].sha;
|
|
389
|
+
|
|
390
|
+
// 2. Create branch from HEAD
|
|
391
|
+
await createBranch(githubToken, targetRepo, i.branch_name, headSha);
|
|
392
|
+
|
|
393
|
+
// 3. Get base tree SHA
|
|
394
|
+
const baseTreeSha = await getBaseTreeSha(githubToken, targetRepo, headSha);
|
|
395
|
+
|
|
396
|
+
// 4. Create blobs for each file
|
|
397
|
+
const blobEntries: Array<{ path: string; blobSha: string }> = [];
|
|
398
|
+
for (const file of i.files) {
|
|
399
|
+
const blobSha = await createBlob(githubToken, targetRepo, file.content);
|
|
400
|
+
blobEntries.push({ path: file.path, blobSha });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// 5. Create new tree
|
|
404
|
+
const treeSha = await createTree(githubToken, targetRepo, baseTreeSha, blobEntries);
|
|
405
|
+
|
|
406
|
+
// 6. Create commit
|
|
407
|
+
const commitSha = await createGitCommit(githubToken, targetRepo, i.commit_message, treeSha, headSha);
|
|
408
|
+
|
|
409
|
+
// 7. Update branch ref to point to new commit
|
|
410
|
+
await updateRef(githubToken, targetRepo, `heads/${i.branch_name}`, commitSha);
|
|
411
|
+
|
|
412
|
+
// 8. Open PR
|
|
413
|
+
const { number, url } = await createPullRequest(githubToken, targetRepo, i.title, i.body, i.branch_name);
|
|
414
|
+
|
|
415
|
+
const fileList = i.files.map(f => f.path).join(', ');
|
|
416
|
+
return `Created PR #${number}: ${url}\nBranch: ${i.branch_name}\nFiles changed (${i.files.length}): ${fileList}`;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (name === 'trigger_worker_deploy') {
|
|
420
|
+
const i = input as { reason: string; pr_numbers?: number[]; repo?: string };
|
|
421
|
+
let commitContext = '';
|
|
422
|
+
try {
|
|
423
|
+
const commits = await getRecentCommits(githubToken, targetRepo, 1);
|
|
424
|
+
if (commits.length > 0) {
|
|
425
|
+
commitContext = ` Latest commit: ${commits[0].sha.slice(0, 8)} ("${commits[0].message}")`;
|
|
426
|
+
}
|
|
427
|
+
} catch {
|
|
428
|
+
// Non-fatal
|
|
429
|
+
}
|
|
430
|
+
const prContext = i.pr_numbers && i.pr_numbers.length > 0
|
|
431
|
+
? ` PRs: ${i.pr_numbers.map(n => `#${n}`).join(', ')}.`
|
|
432
|
+
: '';
|
|
433
|
+
const agendaItem = `[PROPOSED ACTION] Deploy ${targetRepo} — ${i.reason}`;
|
|
434
|
+
const agendaCtx = `Triggered by AEGIS based on CI/CD observation.${prContext}${commitContext} Operator must approve and run: npx wrangler deploy (from web/).`;
|
|
435
|
+
const agendaId = await addAgendaItem(db, agendaItem, agendaCtx, 'high');
|
|
436
|
+
return `Deployment proposed for ${targetRepo}. Added to agenda as item #${agendaId} for operator approval.`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Goal management in-process tool definitions + handlers
|
|
2
|
+
// Extracted from claude-tools.ts for LOC governance
|
|
3
|
+
|
|
4
|
+
import { addGoal, getActiveGoals, updateGoalStatus, getGoalActions } from '../kernel/memory/index.js';
|
|
5
|
+
|
|
6
|
+
// ─── Tool definitions ────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
const ADD_GOAL_TOOL = {
|
|
9
|
+
name: 'add_goal',
|
|
10
|
+
description: 'Create a persistent autonomous goal. AEGIS will evaluate this goal every N hours on a schedule, check BizOps state, and create [PROPOSED ACTION] agenda items when action is needed.',
|
|
11
|
+
input_schema: {
|
|
12
|
+
type: 'object' as const,
|
|
13
|
+
properties: {
|
|
14
|
+
title: { type: 'string', description: 'Short goal title (e.g. "Monitor BOI deadline")' },
|
|
15
|
+
description: { type: 'string', description: 'What to check and what to do if action is needed' },
|
|
16
|
+
schedule_hours: { type: 'number', description: 'How often to run in hours (default: 6)' },
|
|
17
|
+
},
|
|
18
|
+
required: ['title'],
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const LIST_GOALS_TOOL = {
|
|
23
|
+
name: 'list_goals',
|
|
24
|
+
description: 'List all active autonomous goals.',
|
|
25
|
+
input_schema: { type: 'object' as const, properties: {}, required: [] },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const UPDATE_GOAL_STATUS_TOOL = {
|
|
29
|
+
name: 'update_goal_status',
|
|
30
|
+
description: 'Pause, complete, or mark a goal as failed.',
|
|
31
|
+
input_schema: {
|
|
32
|
+
type: 'object' as const,
|
|
33
|
+
properties: {
|
|
34
|
+
id: { type: 'string', description: 'Goal ID' },
|
|
35
|
+
status: { type: 'string', enum: ['paused', 'completed', 'failed'], description: 'New status' },
|
|
36
|
+
},
|
|
37
|
+
required: ['id', 'status'],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const LIST_GOAL_ACTIONS_TOOL = {
|
|
42
|
+
name: 'list_goal_actions',
|
|
43
|
+
description: 'Show what AEGIS did autonomously overnight — proposed actions, executed steps, and skipped runs.',
|
|
44
|
+
input_schema: {
|
|
45
|
+
type: 'object' as const,
|
|
46
|
+
properties: {
|
|
47
|
+
goal_id: { type: 'string', description: 'Filter by goal ID (optional — omit for all goals)' },
|
|
48
|
+
limit: { type: 'number', description: 'Max results (default: 20)' },
|
|
49
|
+
},
|
|
50
|
+
required: [],
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const UPGRADE_GOAL_AUTHORITY_TOOL = {
|
|
55
|
+
name: 'upgrade_goal_authority',
|
|
56
|
+
description: 'Upgrade a goal\'s authority level. Use "auto_low" to allow safe autonomous execution of approved low-risk actions (memory, agenda, read-only BizOps). Requires explicit operator approval.',
|
|
57
|
+
input_schema: {
|
|
58
|
+
type: 'object' as const,
|
|
59
|
+
properties: {
|
|
60
|
+
id: { type: 'string', description: 'Goal ID' },
|
|
61
|
+
authority_level: { type: 'string', enum: ['propose', 'auto_low'], description: 'New authority level' },
|
|
62
|
+
},
|
|
63
|
+
required: ['id', 'authority_level'],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const GOAL_TOOLS = [ADD_GOAL_TOOL, LIST_GOALS_TOOL, UPDATE_GOAL_STATUS_TOOL, LIST_GOAL_ACTIONS_TOOL, UPGRADE_GOAL_AUTHORITY_TOOL];
|
|
68
|
+
|
|
69
|
+
// ─── Handler ─────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
export async function handleGoalTool(
|
|
72
|
+
db: D1Database,
|
|
73
|
+
name: string,
|
|
74
|
+
input: Record<string, unknown>,
|
|
75
|
+
): Promise<string | null> {
|
|
76
|
+
if (name === 'add_goal') {
|
|
77
|
+
const i = input as { title: string; description?: string; schedule_hours?: number };
|
|
78
|
+
const id = await addGoal(db, i.title, i.description, i.schedule_hours ?? 6);
|
|
79
|
+
return `Created goal "${i.title}" (ID: ${id}, every ${i.schedule_hours ?? 6}h)`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (name === 'list_goals') {
|
|
83
|
+
const goals = await getActiveGoals(db);
|
|
84
|
+
if (goals.length === 0) return 'No active goals.';
|
|
85
|
+
return goals.map(g =>
|
|
86
|
+
`[${g.id}] ${g.title} — every ${g.schedule_hours}h, run ${g.run_count}x` +
|
|
87
|
+
(g.next_run_at ? `, next: ${g.next_run_at.slice(0, 16)}` : '') +
|
|
88
|
+
(g.description ? `\n ${g.description}` : '')
|
|
89
|
+
).join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (name === 'update_goal_status') {
|
|
93
|
+
const i = input as { id: string; status: 'paused' | 'completed' | 'failed' };
|
|
94
|
+
await updateGoalStatus(db, i.id, i.status);
|
|
95
|
+
return `Goal ${i.id} marked as ${i.status}`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (name === 'list_goal_actions') {
|
|
99
|
+
const i = input as { goal_id?: string; limit?: number };
|
|
100
|
+
const actions = await getGoalActions(db, i.goal_id, i.limit ?? 20);
|
|
101
|
+
if (actions.length === 0) return 'No goal actions recorded yet.';
|
|
102
|
+
return actions.map(a =>
|
|
103
|
+
`[${a.created_at.slice(0, 16)}] ${a.action_type.toUpperCase()}${a.auto_executed ? ' [AUTO]' : ''} (goal: ${a.goal_id ?? 'none'}) — ${a.description.slice(0, 120)}`
|
|
104
|
+
).join('\n');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (name === 'upgrade_goal_authority') {
|
|
108
|
+
const i = input as { id: string; authority_level: 'propose' | 'auto_low' };
|
|
109
|
+
await db.prepare(
|
|
110
|
+
'UPDATE agent_goals SET authority_level = ? WHERE id = ?'
|
|
111
|
+
).bind(i.authority_level, i.id).run();
|
|
112
|
+
return `Goal ${i.id} authority set to ${i.authority_level}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return null;
|
|
116
|
+
}
|