Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ default_events:
# - create
# - delete
- deployment
# - deployment_status
- deployment_status
# - fork
# - gollum
# - issue_comment
# - issues
- issue_comment
- issues
# - label
# - milestone
# - member
Expand All @@ -36,8 +36,10 @@ default_events:
# - project_column
# - public
- pull_request
# - pull_request_review
# - pull_request_review_comment
- pull_request_review
- pull_request_review_comment
- discussion # added this
- discussion_comment # added this
# - push
# - release
# - repository
Expand Down Expand Up @@ -70,7 +72,7 @@ default_permissions:

# Issues and related comments, assignees, labels, and milestones.
# https://developer.github.com/v3/apps/permissions/#permission-on-issues
# issues: write
issues: write

# Search repositories, list collaborators, and access repository '.
# https://developer.github.com/v3/apps/permissions/#metadata-permissions
Expand Down Expand Up @@ -123,6 +125,11 @@ default_permissions:
# Get notified of, and update, content references.
# https://developer.github.com/v3/apps/permissions/
# organization_administration: read

# Discussions and related comments.
# https://developer.github.com/v3/apps/permissions/#permission-on-discussions
discussions: write

# The name of the GitHub App. Defaults to the name specified in package.json
# name: My Probot App

Expand Down
File renamed without changes.
241 changes: 187 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,189 @@
// Deployments API example
// See: https://developer.github.com/v3/repos/deployments/ to learn more
import fetch from "node-fetch";

const BOT_NAME = "@agentwaspai";
const COGNEE_API = process.env.COGNEE_SERVICE_URL || "http://localhost:8001";

function formatReviewReply(question, results) {
if (!results.length) {
return `No memory found for **${question}**.`;
}

return [
`# AgentWasp Recall`,
``,
`**Question:** ${question}`,
``,
...results.map(r => r.text),
].join("\n\n---\n\n");
}

/**
* This is the main entrypoint to your Probot app
* @param {import('probot').Probot} app
*/
export default (app) => {
// Your code here
app.log.info("Yay, the app was loaded!");
app.on(
["pull_request.opened", "pull_request.synchronize"],
async (context) => {
// Creates a deployment on a pull request event
// Then sets the deployment status to success
// NOTE: this example doesn't actually integrate with a cloud
// provider to deploy your app, it just demos the basic API usage.
app.log.info(context.payload);

// Probot API note: context.repo() => { username: 'hiimbex', repo: 'testing-things' }
const res = await context.octokit.rest.repos.createDeployment(
context.repo({
ref: context.payload.pull_request.head.ref, // The ref to deploy. This can be a branch, tag, or SHA.
task: "deploy", // Specifies a task to execute (e.g., deploy or deploy:migrations).
auto_merge: true, // Attempts to automatically merge the default branch into the requested ref, if it is behind the default branch.
required_contexts: [], // The status contexts to verify against commit status checks. If this parameter is omitted, then all unique contexts will be verified before a deployment is created. To bypass checking entirely pass an empty array. Defaults to all unique contexts.
payload: {
schema: "rocks!",
}, // JSON payload with extra information about the deployment. Default: ""
environment: "production", // Name for the target deployment environment (e.g., production, staging, qa)
description: "My Probot App's first deploy!", // Short description of the deployment
transient_environment: false, // Specifies if the given environment is specific to the deployment and will no longer exist at some point in the future.
production_environment: true, // Specifies if the given environment is one that end-users directly interact with.
}),
);

const deploymentId = res.data.id;
await context.octokit.rest.repos.createDeploymentStatus(
context.repo({
deployment_id: deploymentId,
state: "success", // The state of the status. Can be one of error, failure, inactive, pending, or success
log_url: "https://example.com", // The log URL to associate with this status. This URL should contain output to keep the user updated while the task is running or serve as historical information for what happened in the deployment.
description: "My Probot App set a deployment status!", // A short description of the status.
environment_url: "https://example.com", // Sets the URL for accessing your environment.
auto_inactive: true, // Adds a new inactive status to all prior non-transient, non-production environment deployments with the same repository and environment name as the created status's deployment. An inactive status is only added to deployments that had a success state.
}),
);
},
);

// For more information on building apps:
// https://probot.github.io/docs/

// To get your app running against GitHub, see:
// https://probot.github.io/docs/development/
};
app.log.info("AgentWasp AI loaded.");

app.on("issue_comment.created", async (context) => {
if (context.payload.comment.user?.type === "Bot") return;

const body = context.payload.comment.body.trim();
const number = context.payload.issue.number;
const repo = context.payload.repository.full_name;

if (!body.toLowerCase().includes(BOT_NAME.toLowerCase())) return;

// ── /review ───────────────────────────────────────────────
if (body.toLowerCase().includes("/review")) {
if (!context.payload.issue.pull_request) {
await context.octokit.rest.issues.createComment(
context.issue({ body: "This command only works on pull requests." })
);
return;
}

try {
const [
{ data: diff },
{ data: files },
{ data: comments },
{ data: reviewComments },
{ data: reviews },
{ data: commits },
] = await Promise.all([
context.octokit.rest.pulls.get({
...context.repo(),
pull_number: number,
mediaType: { format: "diff" },
}),
context.octokit.rest.pulls.listFiles({
...context.repo(),
pull_number: number,
}),
context.octokit.rest.issues.listComments({
...context.repo(),
issue_number: number,
}),
context.octokit.rest.pulls.listReviewComments({
...context.repo(),
pull_number: number,
}),
context.octokit.rest.pulls.listReviews({
...context.repo(),
pull_number: number,
}),
context.octokit.rest.pulls.listCommits({
...context.repo(),
pull_number: number,
}),
]);

app.log.info({ diff, files, comments, reviewComments, reviews, commits });

// post raw summary to PR
const summary = `
**Raw PR Data**
- Files changed: ${files.length}
- Commits: ${commits.length}
- Comments: ${comments.length}
- Review comments: ${reviewComments.length}
- Reviews: ${reviews.length}

**Files**
${files.map(f => `- \`${f.filename}\` [${f.status}] +${f.additions} -${f.deletions}`).join("\n")}

**Commits**
${commits.map(c => `- \`${c.sha.slice(0, 7)}\` ${c.commit.message} (${c.commit.author.name})`).join("\n")}

**Diff preview**
\`\`\`diff
${diff.slice(0, 1000)}
\`\`\`
`.trim();

await context.octokit.rest.issues.createComment(
context.issue({ body: summary })
);

// ingest to Cognee
const ingestRes = await fetch(`${COGNEE_API}/ingest`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
repo,
pr_number: number,
diff,
files,
commits,
comments,
review_comments: reviewComments,
reviews,
}),
});

if (!ingestRes.ok) throw new Error(`Ingest failed: ${ingestRes.status}`);

const ingestResult = await ingestRes.json();
app.log.info(`Ingest response: ${JSON.stringify(ingestResult)}`);

await context.octokit.rest.issues.createComment(
context.issue({
body: `PR #${number} data stored in memory and ready for review.`,
})
);

} catch (err) {
app.log.error(`Review failed: ${err.message}`);
await context.octokit.rest.issues.createComment(
context.issue({ body: `Error: ${err.message}` })
);
}
return;
}

// ── /ask ──────────────────────────────────────────────────
if (body.toLowerCase().includes("/ask")) {
const question = body
.replace(new RegExp(BOT_NAME, "gi"), "")
.replace(/\/ask/i, "")
.trim();

if (!question || question.length < 3) {
await context.octokit.rest.issues.createComment(
context.issue({ body: "Please provide a question after `/ask`." })
);
return;
}

try {
const recallRes = await fetch(`${COGNEE_API}/recall`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: question,
repo,
pr_number: number,
}),
});

if (!recallRes.ok) throw new Error(`Recall failed: ${recallRes.status}`);

const { results } = await recallRes.json();

const reply = results.length
? `**AgentWasp AI — Memory Recall**\n\n${results.map((r) => {
// unescape \\n+ const text = r.text.replace(/\\n/g, "\n");
return `---\n\n${text}`;
}).join("\n\n")}`
: "Nothing found in memory. Try `/review` first.";

await context.octokit.rest.issues.createComment(
context.issue({ body: reply })
);

} catch (err) {
app.log.error(`Ask failed: ${err.message}`);
await context.octokit.rest.issues.createComment(
context.issue({ body: `Error: ${err.message}` })
);
}
return;
}
});
};
15 changes: 15 additions & 0 deletions intro.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const INTRO_MESSAGE = `**AgentWasp AI** ( AI code reviewer with persistent memory )

- Automatically reviews every PR using your codebase history
- Remembers past bugs, patterns and review feedback over time
- Answers questions about your code and diffs & updates

**Commands:**
| Command | Description |
|---|---|
| \`/ask <question>\` | Ask anything about this PR or codebase |
| \`/review\` | Trigger a manual review |
| \`@agentwaspai <question>\` | Mention me anywhere in a comment |

> Built with [Cognee](https://cognee.ai) · [Contribute](https://github.com/inline-arc/AgentwaspAi)
`;
Loading