Skip to content
Merged
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
57 changes: 57 additions & 0 deletions .cursor/agents/deployment-ops.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Deployment Ops Agent

You are a deployment and operations agent for auto-me-bot, a Probot GitHub App running as a GCP Cloud Function (Gen2) on Cloud Run.

## Environment

- GCP project: `auto-me-bot`
- Region: `us-central1`
- Service: Cloud Function `auto-me-bot` on Cloud Run
- Runtime: Node.js 22
- Secrets: APP_ID, PRIVATE_KEY, WEBHOOK_SECRET (Secret Manager)
- Deployer SA: `[email protected]` (GitHub Actions WIF)
- `allUsers` invoker access is set manually, not via CI

## Capabilities

Use the GCP MCP tool (`run_gcloud_command`) for all operations. Refer to the `gcp-log-analysis` rule for query templates and MCP tool limitations.

## Post-Deploy Health Check

1. Verify service status: `gcloud run services describe auto-me-bot --region=us-central1`
2. Confirm latest revision is ready and serving 100% traffic
3. Check for ERROR severity logs since deployment
4. Check for HTTP 4xx/5xx responses
5. Check stderr for application-level errors
6. Review request latencies for cold-start impact

## Troubleshooting Playbook

### Service not ready

- Check revision conditions for error messages
- Common: secret access denied (Secret Manager IAM), container health check failures

### HTTP 500s

- Check stderr for stack traces
- Common: GitHub App token issues (expired, revoked, repo access blocked)

### High latency

- Cold starts: ~400-500ms (expected for scale-from-zero)
- Warm requests: ~5ms
- If consistently high: check if `startup-cpu-boost` annotation is enabled

### "no config found" in logs

- Normal behavior: the webhook came from a repo without `.github/auto-me-bot.yml`
- Returns HTTP 200, not an error

## Deployment Verification

After a release deploy (`release.yml`):
1. Run post-deploy health check above
2. Compare revision name to the previous one to confirm the new revision is active
3. If smoke-test workflow exists, check its status
4. Monitor for 10-15 minutes for delayed issues (cold start errors, secret rotation)
10 changes: 3 additions & 7 deletions .cursor/commands/serve-docs.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ globs:
- "docs/**/*.md"
- "mkdocs.yml"
---
# Ensure venv is set up
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# Serve docs
mkdocs serve
test -d .venv || python3 -m venv .venv
.venv/bin/pip install -q -r requirements.txt
.venv/bin/mkdocs serve
13 changes: 13 additions & 0 deletions .cursor/rules/ci-workflows.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
description: CI/CD workflow conventions for auto-me-bot GitHub Actions
globs: .github/workflows/**
alwaysApply: false
---

# CI/CD Workflows

- Use `npm ci` for CI/CD installs (not `npm install`)
- Pin action versions (e.g., `actions/checkout@v6`)
- Keep Probot and commitlint updated
- Deployment uses GCP Cloud Functions Gen2 via `gcloud functions deploy`
- The `--allow-unauthenticated` flag is NOT used; `allUsers` invoker access is set manually
12 changes: 12 additions & 0 deletions .cursor/rules/coding-standards.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
description: JavaScript coding standards for auto-me-bot
globs: "**/*.js"
alwaysApply: false
---

# Coding Standards

- Indentation: 4 spaces
- Line length: 100 chars (JS), 120 chars (Markdown)
- Async patterns: `async/await` (no raw Promises)
- Error handling: Always catch and report API errors
23 changes: 23 additions & 0 deletions .cursor/rules/cursor-tooling.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
description: Available Cursor agents, commands, and skills for auto-me-bot
alwaysApply: true
---

# Cursor Tooling

## Agents (.cursor/agents/)
- **code-reviewer** - Reviews code changes against handler contract, testing, and general quality checklists
- **docs-writer** - Keeps MkDocs documentation in sync with code changes
- **deployment-ops** - Checks GCP service health, analyzes logs, and troubleshoots post-deploy issues

## Commands (.cursor/commands/)
- **run-tests-with-coverage** - `npm test` (c8 + mocha)
- **run-tests-quick** - `npm run tests` (mocha only, faster iteration)
- **lint-fix** - `npm run lint -- --fix`
- **serve-docs** - Sets up venv and runs `mkdocs serve`
- **smoke-test** - Runs `scripts/smoke-test.js` against deployed Cloud Function (needs FUNCTION_URL and WEBHOOK_SECRET in .env)

## Skills (.cursor/skills/)
- **add-handler** - Step-by-step guide for creating a new PR handler (file, tests, registration, docs)

Suggest relevant commands/agents/skills when they match the user's task.
62 changes: 62 additions & 0 deletions .cursor/rules/gcp-log-analysis.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
description: GCP log analysis patterns for the auto-me-bot Cloud Function on Cloud Run
alwaysApply: false
---

# GCP Log Analysis

## Project Context
- GCP project: `auto-me-bot`
- Service: Cloud Function `auto-me-bot` running on Cloud Run in `us-central1`
- Runtime: Node.js 22
- Secrets: APP_ID, PRIVATE_KEY, WEBHOOK_SECRET (via Secret Manager)
- Deployer SA: `[email protected]` (GitHub Actions WIF)
- `allUsers` invoker access on the Cloud Run service is set manually, not via CI. The `--allow-unauthenticated` flag was removed from the deploy command. If `setIamPolicy` errors appear in audit logs, investigate whether the flag was re-added or if `gcloud functions deploy` is inferring it from existing function metadata.

## GCP MCP Tool Limitations
The `run_gcloud_command` MCP tool splits array args on spaces. Compound filters using `AND` in `gcloud logging read` will break because the positional filter arg gets tokenized.

**Workarounds:**
- Use single-condition filters as the positional arg: `severity=ERROR`, `httpRequest.status>=400`
- Use `--freshness` to control time range instead of timestamp filters
- Run multiple focused queries rather than one complex query
- Use `--format=json(field1,field2)` projections to reduce output size

## Useful Log Queries

### Errors only (last 2 days)
```
args: ["logging", "read", "severity=ERROR", "--freshness=2d", "--format=json(timestamp,textPayload,protoPayload.status.message,httpRequest.status)", "--limit=20"]
```

### HTTP errors (4xx/5xx)
```
args: ["logging", "read", "httpRequest.status>=400", "--freshness=2d", "--format=json(timestamp,httpRequest.status,httpRequest.latency,httpRequest.remoteIp)"]
```

### App stderr (runtime errors)
```
args: ["logging", "read", "logName=projects/auto-me-bot/logs/run.googleapis.com%2Fstderr", "--freshness=2d", "--format=json(timestamp,textPayload,severity)", "--limit=20"]
```

### Recent requests with latency
```
args: ["logging", "read", "--freshness=1h", "--limit=30", "--format=json(timestamp,httpRequest.status,httpRequest.latency,textPayload,severity)"]
```

### Service status
```
args: ["run", "services", "describe", "auto-me-bot", "--region=us-central1", "--format=json(status,spec.template.metadata.annotations)"]
```

### Revision history
```
args: ["run", "revisions", "list", "--service=auto-me-bot", "--region=us-central1", "--limit=5", "--format=json(metadata.name,status.conditions,metadata.creationTimestamp)"]
```

## Analysis Checklist
1. Check service status and current revision health
2. Query severity>=WARNING for errors/warnings
3. Query httpRequest.status>=400 for HTTP errors
4. Check stderr for application-level errors
5. Review recent request latencies for cold-start patterns (~400-500ms cold vs ~5ms warm)
20 changes: 20 additions & 0 deletions .cursor/rules/handler-development.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
description: Handler development patterns for auto-me-bot PR handlers
globs: src/handlers/**
alwaysApply: false
---

# Handler Development

Every handler in `src/handlers/` MUST export:
1. `match(context)` - Returns boolean, determines if handler should run
2. `run(context, config, startedAt)` - Async function that executes handler logic

## Handler Lifecycle (PR handlers)
1. Create check-run with status `in_progress`
2. Perform handler operations
3. Update check-run with status `completed` and appropriate conclusion

## Registration
- Add handler to `CONFIG_SPEC` in `src/auto-me-bot.js`
- Events are registered in `ON_EVENTS` in `src/auto-me-bot.js`
24 changes: 24 additions & 0 deletions .cursor/rules/project-overview.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
description: Auto-me-bot project overview, architecture, and tech stack
alwaysApply: true
---

# Auto-Me-Bot

Probot GitHub App that automates repository management through configurable handlers.

## Architecture
- **Config Types**: Event types (currently only 'pr')
- **Handlers**: Operations for config types (located in src/handlers/)
- **Handler Contract**: Each handler exports `match()` and `run()` functions
- User config: `.github/auto-me-bot.yml`
- Handler registration: `src/auto-me-bot.js` CONFIG_SPEC
- Event registration: `src/auto-me-bot.js` ON_EVENTS

## Tech Stack
- Runtime: Node.js 22+
- Framework: Probot 14+
- Testing: Mocha + Chai + Sinon
- Coverage: c8
- Linting: ESLint 9+
- Docs: MkDocs (Python)
51 changes: 0 additions & 51 deletions .cursor/rules/project-rules.mdc

This file was deleted.

13 changes: 13 additions & 0 deletions .cursor/rules/testing.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
description: Testing patterns and requirements for auto-me-bot
globs: tests/**
alwaysApply: false
---

# Testing

- Test `match()` and `run()` separately for each handler
- Mock all GitHub API calls (`context.octokit`)
- Achieve full coverage for handler logic
- Use descriptive test names
- Test fixtures live in `tests/fixtures/`
13 changes: 4 additions & 9 deletions .cursor/skills/add-handler/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This skill guides you through creating a new handler for auto-me-bot.

### 2. Implement match() Function
```javascript
module.exports.match = function(context) {
function match(context) {
let event = 'pull_request';
let actions = ['opened', 'edited']; // Adjust based on handler needs
return event in context.payload ? actions.includes(context.payload.action) : false;
Expand All @@ -27,8 +27,7 @@ module.exports.match = function(context) {
const CHECK_NAME = 'Handler Name';
const BOT_CHECK_URL = 'https://auto-me-bot.figenblat.com/handlers/handler-name';

module.exports.run = async function(context, config, startedAt) {
// Create check-run
async function run(context, config, startedAt) {
let checkRun = await context.octokit.checks.create(context.repo({
head_sha: context.payload.pull_request.head.sha,
name: CHECK_NAME,
Expand All @@ -38,10 +37,8 @@ module.exports.run = async function(context, config, startedAt) {
}));

try {
// Handler logic here
let success = true; // Replace with actual logic

// Update check-run with result
await context.octokit.checks.update(context.repo({
check_run_id: checkRun.data.id,
name: CHECK_NAME,
Expand All @@ -56,7 +53,6 @@ module.exports.run = async function(context, config, startedAt) {
}
}));
} catch (error) {
// Handle errors gracefully
await context.octokit.checks.update(context.repo({
check_run_id: checkRun.data.id,
status: 'completed',
Expand All @@ -68,6 +64,8 @@ module.exports.run = async function(context, config, startedAt) {
}));
}
}

export default {match, run}
```

### 4. Create Test File
Expand All @@ -79,18 +77,15 @@ module.exports.run = async function(context, config, startedAt) {
### 5. Register Handler
Edit `src/auto-me-bot.js`:
```javascript
// Import handler
import handlerName from './handlers/pr-handler-name.js'

// Add to CONFIG_SPEC
const CONFIG_SPEC = Object.freeze({
pr: {
// ... existing handlers
handlerName: handlerName,
}
});

// Add required events to ON_EVENTS if needed
const ON_EVENTS = Object.freeze([
// ... existing events
'pull_request.opened', // Example
Expand Down