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
16 changes: 16 additions & 0 deletions .commandcode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"permissions": {
"allow": [
"Shell(npm:*)",
"Shell(python -m py_compile memory/api/main.py)",
"Shell(node --check index.js)",
"Shell(node \"%COMMANDCODE_SCRATCHPAD%\\format-check.mjs\")",
"Shell(python -c import sys; sys.path.insert(0, 'memory/api'); from main import result_text, result_field; r = {'kind': 'graph_completion', 'text': 'The PR adds a bot.\\n\\n| File | Change |\\n|---|---|\\n| app.yml | events |', 'dataset_name': 'repo-inline-arc-AgentwaspAi', 'raw': {'value': 'fallback'}, 'source': 'graph'}; print(repr(result_text(r))); print(result_field(r, 'dataset_name')); print(repr(result_text({'raw': {'value': 'fallback works'}}))) 2 >& 1)",
"Shell(findstr:*)",
"Shell(python -c \" def result_field(r, key, default=''): if isinstance(r, dict): return r.get(key, default) or default return getattr(r, key, default) or default def result_text(r): text = result_field(r, 'text') if text: return text raw = result_field(r, 'raw') if isinstance(raw, dict) and raw.get('value'): return raw['value'] return str(r) blob = {'kind': 'graph_completion', 'search_type': 'GRAPH_COMPLETION', 'text': 'The PR adds a bot.\\n\\n| File | Change |\\n|------|--------|\\n| app.yml | events |', 'score': None, 'dataset_name': 'repo-inline-arc-AgentwaspAi', 'raw': {'value': 'x'}, 'source': 'graph'} print(result_text(blob)) print('---') print(result_field(blob, 'dataset_name')) print('--- no-text fallback:', result_text({'raw': {'value': 'from raw'}})) print('--- old behavior would have been:', len(str(blob)), 'chars of repr blob') \")",
"Shell(python \"%COMMANDCODE_SCRATCHPAD%\\extract-check.py\")"
],
"deny": [],
"defaultMode": "default"
}
}
7 changes: 7 additions & 0 deletions .commandcode/taste/taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Taste Learnings

## Output & display quality
- Bot output posted to GitHub (e.g. PR comments) must be clean, properly structured markdown that renders readably — never raw Python dict reprs, JSON blobs, or minified rich text. Confidence: 0.75

## Workflow
- When reporting a bug, expects the assistant to trace and identify the root cause ("check where the problem is"), not just patch surface symptoms. Confidence: 0.6
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.
264 changes: 210 additions & 54 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,212 @@
// 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";

const MAX_SECTION_CHARS = 2000;
const MAX_COMMENT_CHARS = 60000; // GitHub hard limit is 65536

// GitHub renders raw JSON as an unreadable blob — fence it as code instead
function renderResultText(text) {
let out = (text || "").trim();

if (/^[[{]/.test(out)) {
try {
out = JSON.stringify(JSON.parse(out), null, 2);
} catch { /* not valid JSON — fence it anyway */ }
out = `\`\`\`json\n${out}\n\`\`\``;
}

if (out.length > MAX_SECTION_CHARS) {
out = `${out.slice(0, MAX_SECTION_CHARS)}\n\n_…(truncated)_`;
}

return out;
}

function formatRecallReply(question, results) {
if (!results.length) {
return "Nothing found in memory. Try `/review` first.";
}

const sections = results.map((r, i) => {
const source = r.dataset_name ? ` — _from \`${r.dataset_name}\`_` : "";
return `**${i + 1}.**${source}\n\n${renderResultText(r.text)}`;
});

const reply = [
"## AgentWasp AI — Memory Recall",
"",
`> **Question:** ${question}`,
"",
sections.join("\n\n---\n\n"),
].join("\n");

return reply.length > MAX_COMMENT_CHARS
? `${reply.slice(0, MAX_COMMENT_CHARS)}\n\n_…(truncated)_`
: reply;
}

/**
* 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();

await context.octokit.rest.issues.createComment(
context.issue({ body: formatRecallReply(question, results) })
);

} 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