Skip to content
Draft
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
14 changes: 14 additions & 0 deletions agentic-pr-review/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@ Artifacts: uploads `review-agent.json` from the workspace when present (for debu
| `provider` | no | Review backend; the action resolves it via `scripts/providers/<provider>.sh` (default: `cursor`). |
| `deepen-length` | no | Passed to `rmacklin/fetch-through-merge-base` as `deepen_length` (default: `30`). |
| `additional-prompt` | no | Extra text appended to the review prompt after `prompts/review.md`. |
| `langfuse-secret-key` | no | Langfuse secret key; set with `langfuse-public-key` to enable tracing. |
| `langfuse-public-key` | no | Langfuse public key; required when tracing is enabled. |
| `langfuse-base-url` | no | Langfuse base URL (defaults to `https://cloud.langfuse.com`). |

## Secrets and permissions

- Store **`app-id`**, **`private-key`**, and **`provider-api-key`** as repository (or org) secrets; do not commit them.
- If enabling Langfuse tracing, also store **`langfuse-secret-key`** and **`langfuse-public-key`** as secrets.
- The calling workflow needs permission to **read** contents and **write** pull requests (for posting the review). The GitHub App installation must be allowed to clone the repo and create reviews on the target repository.

## Example Usage
Expand Down Expand Up @@ -98,3 +102,13 @@ If your workflow should avoid reviewing draft or closed pull requests, add a sma
mkdir -p .cursor
cp path/to/agentic-pr-review/config/cli-config.json .cursor/cli-config.json
```

## Langfuse tracing (Cursor)

Provide `langfuse-secret-key` and `langfuse-public-key` inputs to trace Cursor agent activity to Langfuse. When both are present, the action:

- Copies the bundled Cursor hooks from `config/langfuse-hooks` into `.cursor/`
- Installs hook dependencies with `npm ci` before invoking the Cursor CLI
- Exports `LANGFUSE_*` variables (optional `langfuse-base-url` overrides the default `https://cloud.langfuse.com`)

If the keys are omitted, Langfuse tracing is skipped and the review runs as before.
15 changes: 15 additions & 0 deletions agentic-pr-review/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ inputs:
description: Extra instructions appended to the review prompt (e.g. full text of an @nitro-pr-review PR comment)
required: false
default: ""
langfuse-secret-key:
description: Langfuse secret key used to enable Cursor tracing
required: false
default: ""
langfuse-public-key:
description: Langfuse public key used to enable Cursor tracing
required: false
default: ""
langfuse-base-url:
description: Langfuse base URL (defaults to https://cloud.langfuse.com)
required: false
default: ""

runs:
using: composite
Expand Down Expand Up @@ -111,6 +123,9 @@ runs:
REVIEW_JSON_PATH: ${{ github.workspace }}/review-agent.json
REVIEW_PROMPT_PATH: ${{ github.action_path }}/prompts/review.md
REVIEW_ADDITIONAL_INSTRUCTIONS: ${{ inputs.additional-prompt }}
LANGFUSE_SECRET_KEY: ${{ inputs.langfuse-secret-key }}
LANGFUSE_PUBLIC_KEY: ${{ inputs.langfuse-public-key }}
LANGFUSE_BASE_URL: ${{ inputs.langfuse-base-url }}
run: |
set -euo pipefail

Expand Down
65 changes: 65 additions & 0 deletions agentic-pr-review/config/langfuse-hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
"version": 1,
"hooks": {
"beforeSubmitPrompt": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"afterAgentResponse": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"afterAgentThought": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"beforeShellExecution": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"afterShellExecution": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"beforeMCPExecution": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"afterMCPExecution": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"beforeReadFile": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"afterFileEdit": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"stop": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"beforeTabFileRead": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
],
"afterTabFileEdit": [
{
"command": "node .cursor/hooks/hook-handler.js"
}
]
}
}
70 changes: 70 additions & 0 deletions agentic-pr-review/config/langfuse-hooks/hooks/hook-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env node

/**
* Cursor Hooks Langfuse Integration
*
* Main entry point for Cursor hooks that sends traces to Langfuse.
*
* Features:
* - All 12 Cursor hooks supported (Agent + Tab)
* - Traces grouped by conversation_id
* - Sessions grouped by workspace
* - Dynamic tags based on activity
* - Completion scores and efficiency metrics
* - Rich metadata and edit statistics
*
* @version 1.1.0
* @see https://cursor.com/docs/agent/hooks
* @see https://langfuse.com/docs
*/

import { readStdin } from "./lib/utils.js";
import {
getOrCreateTrace,
flushLangfuse,
HOOK_HANDLER_VERSION,
} from "./lib/langfuse-client.js";
import { routeHookHandler } from "./lib/handlers.js";

/**
* Main handler function
* Reads hook data from stdin, creates Langfuse trace, and routes to handler
*/
async function main() {
try {
// Read JSON input from stdin
const input = await readStdin();

// Get or create a trace for this conversation
const trace = getOrCreateTrace(input);

// Route to the appropriate handler based on hook type
const hookName = input.hook_event_name;
const response = routeHookHandler(hookName, trace, input);

// Output response to Cursor if handler returned one
if (response !== null && response !== undefined) {
console.log(JSON.stringify(response));
}

// Flush all pending events to Langfuse before exiting
await flushLangfuse();
} catch (error) {
// Log error but don't crash - we don't want to block Cursor
console.error(`[Langfuse Hook v${HOOK_HANDLER_VERSION}] Error: ${error.message}`);

// Still output a permissive response so Cursor can continue
// This ensures the hook doesn't block operations if something goes wrong
console.log(
JSON.stringify({
continue: true,
permission: "allow",
}),
);

process.exit(1);
}
}

// Run the main function
main();
Loading