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
29 changes: 26 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Orchestrate complex, long-running coding tasks to an ephemeral cloud environment
- [Agent Workflow](./examples/agent/README.md)
- [Webhook Integration](./examples/webhook/README.md)
- [GitHub Actions](./examples/github-actions/README.md)
- [Gitpatch Goals](./examples/gitpatch-goals/README.md)

## Send work to a Cloud based session

Expand Down
39 changes: 39 additions & 0 deletions packages/core/examples/gitpatch-goals/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# GitPatch Goals Review Example

This example demonstrates how to use the Jules SDK to evaluate the code generated by an AI agent. It uses a "Repoless" session to simulate code generation, then extracts the GitPatch (the diff of the generated code) and uses a second session to review the patch against the original prompt's goals and generic coding standards.

## Requirements

- Node.js >= 18 or Bun
- A Jules API Key (`JULES_API_KEY` environment variable)

## Setup

1. Make sure you have installed the SDK dependencies in the project root.

2. Export your Jules API key:

```bash
export JULES_API_KEY="your-api-key-here"
```

## Running the Example

Using `bun`:

```bash
bun run index.ts
```

Using `npm` and `tsx` (or similar TypeScript runner):

```bash
npx tsx index.ts
```

## What it does

1. **Generation Session**: Creates a session asking the agent to write a simple Node.js HTTP server.
2. **Patch Extraction**: Waits for the session to complete and extracts the generated code as a GitPatch.
3. **Review Session**: Creates a second session, providing the original prompt, the generated GitPatch, and instructions to evaluate if the code meets the goals and adheres to best practices.
4. **Result**: Outputs the review findings from the second agent.
28 changes: 28 additions & 0 deletions packages/core/examples/gitpatch-goals/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: gitpatch-goals-cli
description: Evaluates generated code via GitPatch against original goals and coding standards.
version: 1.0.0
---

# GitPatch Goals CLI

This CLI uses Jules sessions to simulate code generation, extracts the resulting code as a GitPatch, and feeds it to a second Jules session to evaluate if it successfully met the original prompt's goals and adheres to coding standards.

## Usage Guidelines for AI Agents

When invoking this CLI, adhere to the following best practices:

1. **Schema Introspection**: You can introspect the required arguments and schema at runtime by passing `--describe`.
```bash
bun run index.ts --describe
```

2. **Context Window Discipline**: Use `--json` for predictable, deterministic, machine-readable output. Avoid parsing raw terminal stdout.
```bash
bun run index.ts --prompt "Create an API" --json
```

3. **Input Hardening**: Before executing mutations or relying on long-running APIs (like creating Jules Sessions), validate your payload using the `--dry-run` flag to ensure the CLI safely accepts your arguments without executing side effects.
```bash
bun run index.ts --prompt "Create an API" --dry-run
```
36 changes: 36 additions & 0 deletions packages/core/examples/gitpatch-goals/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { jules, SessionClient } from '@google/jules-sdk';

/**
* Initiates the generation session and streams progress updates back
* to the caller until the session completes.
*/
export async function generateCode(prompt: string): Promise<SessionClient> {
console.error('--- Step 1: Initiating Code Generation Session ---');

// Repoless sessions don't always create the resource instantly,
// we must await the outcome state for streaming to reliably start without 404ing activities
const session = await jules.session({ prompt });

console.error(`Generation Session created! ID: ${session.id}`);
console.error('Streaming agent progress...\n');

try {
for await (const activity of session.stream()) {
if (activity.type === 'progressUpdated') {
console.error(`[Generation] ${activity.title}`);
} else if (activity.type === 'agentMessaged') {
console.error(`[Generation Agent]: ${activity.message.substring(0, 100)}...`);
} else if (activity.type === 'sessionCompleted') {
console.error('[Generation] Session complete.');
} else if (activity.type === 'sessionFailed') {
console.error('[Generation] Session failed.');
}
}
} catch (err) {
// A 404 indicates the activities sub-collection might not be ready yet.
// The safest fallback is waiting for the result.
console.error('[Generation] Streaming not available yet. Waiting for completion...');
}

return session;
}
102 changes: 102 additions & 0 deletions packages/core/examples/gitpatch-goals/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { ReviewInput, ReviewResult, ReviewSpec } from './spec.js';
import { generateCode } from './generate.js';
import { extractGitPatch } from './patch.js';
import { reviewCode } from './review.js';

/**
* Orchestrator Handler that runs the generation and review workflow.
* Uses Typed Service Contract implementation to encapsulate errors.
*/
export class ReviewHandler implements ReviewSpec {
async execute(input: ReviewInput): Promise<ReviewResult> {
try {
if (input.dryRun) {
console.error('--- DRY RUN ENABLED: Simulating Code Generation & Review ---');
return {
success: true,
data: {
reviewMessage: '[DRY RUN] Generated code successfully met the original goals.',
patchSize: 42,
},
};
}

// 1. Generation phase
const genSession = await generateCode(input.prompt);
const genOutcome = await genSession.result();

if (genOutcome.state !== 'completed') {
return {
success: false,
error: {
code: 'GENERATION_FAILED',
message: `Generation session failed with state: ${genOutcome.state}`,
recoverable: false,
},
};
}

// 2. Patch extraction phase
const gitPatch = extractGitPatch(genOutcome);

if (!gitPatch) {
return {
success: false,
error: {
code: 'NO_PATCH_FOUND',
message: 'Failed to extract GitPatch from generation session.',
recoverable: false,
},
};
}

// 3. Review phase
const reviewOutcome = await reviewCode(input.prompt, gitPatch);

if (reviewOutcome.state !== 'completed') {
return {
success: false,
error: {
code: 'REVIEW_FAILED',
message: `Review session failed with state: ${reviewOutcome.state}`,
recoverable: false,
},
};
}

// 4. Result Formatting
let reviewMessage = 'No final message provided by the review agent.';
const activities = reviewOutcome.activities ?? [];
const agentMessages = activities.filter((a) => a.type === 'agentMessaged');

if (agentMessages.length > 0) {
reviewMessage = agentMessages[agentMessages.length - 1].message;
} else {
const files = reviewOutcome.generatedFiles();
if (files.size > 0) {
reviewMessage = '';
for (const [filename, content] of files.entries()) {
reviewMessage += `\nFile: ${filename}\n${content.content}\n`;
}
}
}

return {
success: true,
data: {
reviewMessage,
patchSize: gitPatch.length,
},
};
} catch (error) {
return {
success: false,
error: {
code: 'UNKNOWN_ERROR',
message: error instanceof Error ? error.message : String(error),
recoverable: false,
},
};
}
}
}
80 changes: 80 additions & 0 deletions packages/core/examples/gitpatch-goals/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { defineCommand, runMain } from 'citty';
import { zodToJsonSchema } from 'zod-to-json-schema';
import { ReviewInputSchema } from './spec.js';
import { ReviewHandler } from './handler.js';

// ============================================================================
// CLI CONFIGURATION (citty)
// ============================================================================

const main = defineCommand({
meta: {
name: 'gitpatch-goals',
description: 'Generates code and reviews it via GitPatch against original goals.',
},
args: {
prompt: {
type: 'string',
description: 'The initial prompt/goal for code generation',
default:
'Create a simple Node.js HTTP server that listens on port 8080 and serves "Hello, World!".',
alias: 'p',
},
json: {
type: 'boolean',
description: 'Output the result as JSON',
default: false,
},
'dry-run': {
type: 'boolean',
description: 'Simulates the command without making API calls',
default: false,
},
describe: {
type: 'boolean',
description: 'Prints the JSON schema for this command and exits',
default: false,
},
},
async run({ args }) {
if (args.describe) {
const schema = zodToJsonSchema(ReviewInputSchema, 'ReviewInput');
console.log(JSON.stringify(schema, null, 2));
process.exit(0);
}

if (!process.env.JULES_API_KEY) {
console.error('Error: JULES_API_KEY environment variable is missing.');
process.exit(1);
}

const parseResult = ReviewInputSchema.safeParse({
prompt: args.prompt,
dryRun: args['dry-run'],
});
if (!parseResult.success) {
console.error('Invalid input:', parseResult.error.format());
process.exit(1);
}

const handler = new ReviewHandler();
const result = await handler.execute(parseResult.data);

if (args.json) {
console.log(JSON.stringify(result, null, 2));
} else {
if (result.success) {
console.log('\n======================================================');
console.log('REVIEW RESULTS');
console.log('======================================================\n');
console.log(result.data.reviewMessage);
} else {
console.error('\nFAILED:', result.error.message);
}
}

process.exit(result.success ? 0 : 1);
},
});

runMain(main);
18 changes: 18 additions & 0 deletions packages/core/examples/gitpatch-goals/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "gitpatch-goals",
"version": "1.0.0",
"description": "Example demonstrating how to evaluate generated code using a GitPatch.",
"main": "index.ts",
"scripts": {
"start": "bun run index.ts"
},
"dependencies": {
"@google/jules-sdk": "workspace:*",
"citty": "^0.1.6",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.24.1"
},
"devDependencies": {
"bun-types": "^1.1.8"
}
}
Loading
Loading