diff --git a/src/agents/base.ts b/src/agents/base.ts index 081d0d4a..dc8bad31 100644 --- a/src/agents/base.ts +++ b/src/agents/base.ts @@ -316,6 +316,7 @@ function createAgentBuilderWithGadgets( .withRetry(getRetryConfig(llmistLogger)) .withCompaction(getCompactionConfig(agentType)) .withTrailingMessage(getIterationTrailingMessage(agentType)) + .withTextOnlyHandler('acknowledge') .withHooks({ observers: createObserverHooks({ model: ctx.model, @@ -349,7 +350,12 @@ async function injectSyntheticCalls( // Call the actual gadget to generate output (respects .gitignore by default) // Use maxDepth=5 to give agents better visibility into nested structures const listDirGadget = new ListDirectory(); - const listDirParams = { directoryPath: '.', maxDepth: 5, includeGitIgnored: false }; + const listDirParams = { + comment: 'Pre-fetching codebase structure for context', + directoryPath: '.', + maxDepth: 5, + includeGitIgnored: false, + }; const listDirResult = listDirGadget.execute(listDirParams); recordSyntheticInvocationId(trackingContext, 'gc_dir'); builder = builder.withSyntheticGadgetCall( @@ -377,7 +383,7 @@ async function injectSyntheticCalls( recordSyntheticInvocationId(trackingContext, invocationId); builder = builder.withSyntheticGadgetCall( 'ReadFile', - { filePath: file.path }, + { comment: `Pre-fetching ${file.path} for project context`, filePath: file.path }, file.content, invocationId, ); @@ -385,24 +391,31 @@ async function injectSyntheticCalls( // Inject AU understanding if enabled (gives agent immediate codebase context) if (auEnabled) { - const auListResult = (await auList.execute({ path: '.', maxDepth: 5 })) as string; + const auListResult = (await auList.execute({ + comment: 'Pre-fetching AU entries for context', + path: '.', + maxDepth: 5, + })) as string; // Only inject if there's actual content if (auListResult && !auListResult.includes('No AU entries found')) { recordSyntheticInvocationId(trackingContext, 'gc_au_list'); builder = builder.withSyntheticGadgetCall( 'AUList', - { path: '.', maxDepth: 5 }, + { comment: 'Pre-fetching AU entries for context', path: '.', maxDepth: 5 }, auListResult, 'gc_au_list', ); // Also inject root-level understanding for high-level context - const auReadResult = (await auRead.execute({ paths: '.' })) as string; + const auReadResult = (await auRead.execute({ + comment: 'Pre-fetching root-level understanding', + paths: '.', + })) as string; if (auReadResult && !auReadResult.includes('No understanding exists yet')) { recordSyntheticInvocationId(trackingContext, 'gc_au_read'); builder = builder.withSyntheticGadgetCall( 'AURead', - { paths: '.' }, + { comment: 'Pre-fetching root-level understanding', paths: '.' }, auReadResult, 'gc_au_read', ); diff --git a/src/agents/respond-to-review.ts b/src/agents/respond-to-review.ts index 04cce8a8..9098c34c 100644 --- a/src/agents/respond-to-review.ts +++ b/src/agents/respond-to-review.ts @@ -308,7 +308,12 @@ async function injectReviewSyntheticCalls( // Inject directory listing as synthetic ListDirectory call (first for codebase orientation) // Call the actual gadget to generate output (respects .gitignore by default) const listDirGadget = new ListDirectory(); - const listDirParams = { directoryPath: '.', maxDepth: 3, includeGitIgnored: false }; + const listDirParams = { + comment: 'Pre-fetching codebase structure for context', + directoryPath: '.', + maxDepth: 3, + includeGitIgnored: false, + }; const listDirResult = listDirGadget.execute(listDirParams); recordSyntheticInvocationId(trackingContext, 'gc_dir'); builder = builder.withSyntheticGadgetCall( @@ -322,7 +327,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, 'gc_pr_details'); builder = builder.withSyntheticGadgetCall( 'GetPRDetails', - { owner, repo, prNumber }, + { comment: 'Pre-fetching PR details for context', owner, repo, prNumber }, ctx.prDetailsFormatted, 'gc_pr_details', ); @@ -331,7 +336,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, 'gc_pr_comments'); builder = builder.withSyntheticGadgetCall( 'GetPRComments', - { owner, repo, prNumber }, + { comment: 'Pre-fetching review comments to address', owner, repo, prNumber }, ctx.commentsFormatted, 'gc_pr_comments', ); @@ -340,7 +345,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, 'gc_pr_diff'); builder = builder.withSyntheticGadgetCall( 'GetPRDiff', - { owner, repo, prNumber }, + { comment: 'Pre-fetching PR diff for context', owner, repo, prNumber }, ctx.diffFormatted, 'gc_pr_diff', ); @@ -352,7 +357,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, invocationId); builder = builder.withSyntheticGadgetCall( 'ReadFile', - { filePath: file.path }, + { comment: `Pre-fetching ${file.path} for project context`, filePath: file.path }, file.content, invocationId, ); @@ -360,24 +365,30 @@ async function injectReviewSyntheticCalls( // Inject AU understanding if enabled (gives agent immediate codebase context) if (auEnabled) { - const auListResult = (await auList.execute({ path: '.' })) as string; + const auListResult = (await auList.execute({ + comment: 'Pre-fetching AU entries for context', + path: '.', + })) as string; // Only inject if there's actual content if (auListResult && !auListResult.includes('No AU entries found')) { recordSyntheticInvocationId(trackingContext, 'gc_au_list'); builder = builder.withSyntheticGadgetCall( 'AUList', - { path: '.' }, + { comment: 'Pre-fetching AU entries for context', path: '.' }, auListResult, 'gc_au_list', ); // Also inject root-level understanding for high-level context - const auReadResult = (await auRead.execute({ paths: '.' })) as string; + const auReadResult = (await auRead.execute({ + comment: 'Pre-fetching root-level understanding', + paths: '.', + })) as string; if (auReadResult && !auReadResult.includes('No understanding exists yet')) { recordSyntheticInvocationId(trackingContext, 'gc_au_read'); builder = builder.withSyntheticGadgetCall( 'AURead', - { paths: '.' }, + { comment: 'Pre-fetching root-level understanding', paths: '.' }, auReadResult, 'gc_au_read', ); diff --git a/src/agents/review.ts b/src/agents/review.ts index ae4762b5..6f7ea810 100644 --- a/src/agents/review.ts +++ b/src/agents/review.ts @@ -299,7 +299,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, 'gc_pr_details'); builder = builder.withSyntheticGadgetCall( 'GetPRDetails', - { owner, repo, prNumber }, + { comment: 'Pre-fetching PR details for review context', owner, repo, prNumber }, ctx.prDetailsFormatted, 'gc_pr_details', ); @@ -308,7 +308,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, 'gc_pr_diff'); builder = builder.withSyntheticGadgetCall( 'GetPRDiff', - { owner, repo, prNumber }, + { comment: 'Pre-fetching PR diff for code review', owner, repo, prNumber }, ctx.diffFormatted, 'gc_pr_diff', ); @@ -317,7 +317,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, 'gc_pr_checks'); builder = builder.withSyntheticGadgetCall( 'GetPRChecks', - { owner, repo, prNumber }, + { comment: 'Pre-fetching CI check status for review', owner, repo, prNumber }, ctx.checkStatusFormatted, 'gc_pr_checks', ); @@ -329,7 +329,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, invocationId); builder = builder.withSyntheticGadgetCall( 'ReadFile', - { filePath: file.path }, + { comment: `Pre-fetching ${file.path} for project context`, filePath: file.path }, file.content, invocationId, ); @@ -342,7 +342,7 @@ async function injectReviewSyntheticCalls( recordSyntheticInvocationId(trackingContext, invocationId); builder = builder.withSyntheticGadgetCall( 'ReadFile', - { filePath: file.path }, + { comment: `Pre-fetching ${file.path} for review`, filePath: file.path }, `path=${file.path}\n\n${file.content}`, invocationId, ); @@ -350,24 +350,30 @@ async function injectReviewSyntheticCalls( // Inject AU understanding if enabled (gives agent immediate codebase context) if (auEnabled) { - const auListResult = (await auList.execute({ path: '.' })) as string; + const auListResult = (await auList.execute({ + comment: 'Pre-fetching AU entries for context', + path: '.', + })) as string; // Only inject if there's actual content if (auListResult && !auListResult.includes('No AU entries found')) { recordSyntheticInvocationId(trackingContext, 'gc_au_list'); builder = builder.withSyntheticGadgetCall( 'AUList', - { path: '.' }, + { comment: 'Pre-fetching AU entries for context', path: '.' }, auListResult, 'gc_au_list', ); // Also inject root-level understanding for high-level context - const auReadResult = (await auRead.execute({ paths: '.' })) as string; + const auReadResult = (await auRead.execute({ + comment: 'Pre-fetching root-level understanding', + paths: '.', + })) as string; if (auReadResult && !auReadResult.includes('No understanding exists yet')) { recordSyntheticInvocationId(trackingContext, 'gc_au_read'); builder = builder.withSyntheticGadgetCall( 'AURead', - { paths: '.' }, + { comment: 'Pre-fetching root-level understanding', paths: '.' }, auReadResult, 'gc_au_read', ); diff --git a/src/agents/utils/agentLoop.ts b/src/agents/utils/agentLoop.ts index 2003de24..9b897a8c 100644 --- a/src/agents/utils/agentLoop.ts +++ b/src/agents/utils/agentLoop.ts @@ -151,6 +151,11 @@ async function handleGadgetCallEvent( logContext.isSynthetic = true; } + // Include the comment field (unabbreviated) for observability + if (parameters?.comment) { + logContext.comment = parameters.comment; + } + addGadgetSpecificLogContext(logContext, gadgetName, parameters); log.info('[Gadget]', logContext); } diff --git a/src/config/hintConfig.ts b/src/config/hintConfig.ts index f764e9ba..e26aa7a6 100644 --- a/src/config/hintConfig.ts +++ b/src/config/hintConfig.ts @@ -1,4 +1,5 @@ import type { TrailingMessage } from 'llmist'; +import { formatTodoList, loadTodos } from '../gadgets/todo/storage.js'; /** * Agent-specific batch hints. @@ -32,6 +33,28 @@ function getAgentHint(agentType?: string): string { return AGENT_HINTS.default; } +/** + * Format the iteration status line with appropriate urgency indicator. + */ +function formatIterationStatus( + iteration: number, + maxIterations: number, + batchHint: string, +): string { + const remaining = maxIterations - iteration; + const percent = Math.round((iteration / maxIterations) * 100); + + if (percent >= 80) { + return `🚨 Iteration ${iteration}/${maxIterations} (${percent}% used, ${remaining} remaining) - ${batchHint}`; + } + + if (percent >= 50) { + return `āš ļø Iteration ${iteration}/${maxIterations} (${percent}% used, ${remaining} remaining) - ${batchHint}`; + } + + return `Iteration ${iteration}/${maxIterations} (${percent}% used, ${remaining} remaining) - ${batchHint}`; +} + /** * Get trailing message function for iteration tracking. * @@ -39,6 +62,7 @@ function getAgentHint(agentType?: string): string { * - Always shows current iteration, remaining count, and percentage * - Adds urgency indicator when running low on iterations * - Includes agent-specific batch processing hints + * - For implementation agent: includes current todo list for visibility * * Trailing messages are ephemeral - they appear in each request but don't * persist to conversation history, keeping context clean. @@ -50,17 +74,17 @@ export function getIterationTrailingMessage(agentType?: string): TrailingMessage const batchHint = getAgentHint(agentType); return (ctx) => { - const remaining = ctx.maxIterations - ctx.iteration; - const percent = Math.round((ctx.iteration / ctx.maxIterations) * 100); - - if (percent >= 80) { - return `🚨 Iteration ${ctx.iteration}/${ctx.maxIterations} (${percent}% used, ${remaining} remaining) - ${batchHint}`; - } + const iterationStatus = formatIterationStatus(ctx.iteration, ctx.maxIterations, batchHint); - if (percent >= 50) { - return `āš ļø Iteration ${ctx.iteration}/${ctx.maxIterations} (${percent}% used, ${remaining} remaining) - ${batchHint}`; + // For implementation agent, include the current todo list + if (agentType === 'implementation') { + const todos = loadTodos(); + if (todos.length > 0) { + const todoListFormatted = formatTodoList(todos); + return `${iterationStatus}\n\n## Current Progress\n\n${todoListFormatted}`; + } } - return `Iteration ${ctx.iteration}/${ctx.maxIterations} (${percent}% used, ${remaining} remaining) - ${batchHint}`; + return iterationStatus; }; } diff --git a/src/gadgets/EditFile.ts b/src/gadgets/EditFile.ts index c78974b7..41f1b636 100644 --- a/src/gadgets/EditFile.ts +++ b/src/gadgets/EditFile.ts @@ -60,6 +60,7 @@ Each call provides immediate feedback, allowing you to adjust subsequent edits.` timeoutMs: 30000, maxConcurrent: 1, // Sequential execution to prevent race conditions on file writes schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), filePath: z.string().describe('Path to the file to edit (relative or absolute)'), search: z.string().describe('The content to search for in the file'), replace: z.string().describe('The content to replace it with (empty string to delete)'), @@ -67,6 +68,7 @@ Each call provides immediate feedback, allowing you to adjust subsequent edits.` examples: [ { params: { + comment: 'Increasing timeout from 1s to 5s to fix test flakiness', filePath: 'src/config.ts', search: 'timeout: 1000', replace: 'timeout: 5000', @@ -103,6 +105,7 @@ No lint issues found.`, }, { params: { + comment: 'Updating retry constant per requirements', filePath: 'src/constants.ts', search: 'MAX_RETRIES = 3', replace: 'MAX_RETRIES = 5', @@ -150,6 +153,7 @@ No lint issues found.`, }, { params: { + comment: 'Enabling feature flag for new functionality', filePath: 'src/data.json', search: '"enabled": false', replace: '"enabled": true', diff --git a/src/gadgets/ListDirectory.ts b/src/gadgets/ListDirectory.ts index bdca53e3..76c89775 100644 --- a/src/gadgets/ListDirectory.ts +++ b/src/gadgets/ListDirectory.ts @@ -251,6 +251,7 @@ Allowed paths: - Current working directory and subdirectories - /tmp directory`, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), directoryPath: z.string().default('.').describe('Path to the directory to list'), maxDepth: z .number() @@ -266,13 +267,23 @@ Allowed paths: }), examples: [ { - params: { directoryPath: '.', maxDepth: 1, includeGitIgnored: false }, + params: { + comment: 'Getting overview of project structure', + directoryPath: '.', + maxDepth: 1, + includeGitIgnored: false, + }, output: 'path=. maxDepth=1 includeGitIgnored=false\n\n#T|N|S|A\nD|src|0|2h\nD|tests|0|1d\nF|package.json|2841|3h', comment: 'List current directory (excluding gitignored files)', }, { - params: { directoryPath: 'src', maxDepth: 2, includeGitIgnored: true }, + params: { + comment: 'Exploring src directory to find component files', + directoryPath: 'src', + maxDepth: 2, + includeGitIgnored: true, + }, output: 'path=src maxDepth=2 includeGitIgnored=true\n\n#T|N|S|A\nD|components|0|1d\nF|index.ts|512|1h', comment: 'List src directory including all files', diff --git a/src/gadgets/ReadFile.ts b/src/gadgets/ReadFile.ts index 0f9b0ed9..a9309601 100644 --- a/src/gadgets/ReadFile.ts +++ b/src/gadgets/ReadFile.ts @@ -57,6 +57,7 @@ Allowed paths: - Current working directory and subdirectories - /tmp directory (for test logs, build artifacts, etc.)`, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), filePath: z .string() .describe( @@ -65,12 +66,15 @@ Allowed paths: }), examples: [ { - params: { filePath: 'package.json' }, + params: { + comment: 'Reading config to understand project structure', + filePath: 'package.json', + }, output: 'path=package.json\n\n{\n "name": "my-project",\n "version": "1.0.0"\n ...\n}', comment: 'Read a JSON config file', }, { - params: { filePath: '/tmp/test.log' }, + params: { comment: 'Checking test output for failures', filePath: '/tmp/test.log' }, output: 'path=/tmp/test.log\n\n[Test output...]', comment: 'Read a test log from /tmp', }, diff --git a/src/gadgets/Sleep.ts b/src/gadgets/Sleep.ts index d19e0fdf..56e668b9 100644 --- a/src/gadgets/Sleep.ts +++ b/src/gadgets/Sleep.ts @@ -23,36 +23,35 @@ export class Sleep extends Gadget({ - Don't use for long waits (>30s) - prefer polling with shorter sleeps`, timeoutMs: 65000, // Allow up to 60s sleep + 5s buffer schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), seconds: z.number().min(0.1).max(60).describe('Duration to sleep in seconds (0.1 to 60)'), - reason: z.string().optional().describe('Optional reason for the wait (for logging clarity)'), }), examples: [ { - params: { seconds: 3, reason: 'waiting for dev server to start' }, - output: 'Slept for 3 seconds (waiting for dev server to start)', + params: { comment: 'Waiting for dev server to start', seconds: 3 }, + output: 'Slept for 3 seconds', comment: 'Wait for a dev server after starting it with tmux', }, { - params: { seconds: 1 }, + params: { comment: 'Brief pause between operations', seconds: 1 }, output: 'Slept for 1 second', comment: 'Brief pause between operations', }, { - params: { seconds: 5, reason: 'database initialization' }, - output: 'Slept for 5 seconds (database initialization)', + params: { comment: 'Waiting for database initialization', seconds: 5 }, + output: 'Slept for 5 seconds', comment: 'Wait for database to be ready', }, ], }) { override async execute(params: this['params']): Promise { - const { seconds, reason } = params; + const { seconds } = params; // Sleep for the specified duration await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); const duration = seconds === 1 ? '1 second' : `${seconds} seconds`; - const reasonText = reason ? ` (${reason})` : ''; - return `Slept for ${duration}${reasonText}`; + return `Slept for ${duration}`; } } diff --git a/src/gadgets/WriteFile.ts b/src/gadgets/WriteFile.ts index f53b1196..5c8c412e 100644 --- a/src/gadgets/WriteFile.ts +++ b/src/gadgets/WriteFile.ts @@ -61,17 +61,26 @@ Allowed paths: - Current working directory and subdirectories - /tmp directory`, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), filePath: z.string().describe('Path to the file to write (relative or absolute)'), content: z.string().describe('Content to write to the file'), }), examples: [ { - params: { filePath: 'output.txt', content: 'Hello, World!' }, + params: { + comment: 'Creating output file for test results', + filePath: 'output.txt', + content: 'Hello, World!', + }, output: 'path=output.txt\n\nWrote 13 bytes', comment: 'Write a simple text file (no diagnostics for non-TS files)', }, { - params: { filePath: 'src/utils/helper.ts', content: 'export function helper() {}' }, + params: { + comment: 'Adding new helper utility function', + filePath: 'src/utils/helper.ts', + content: 'export function helper() {}', + }, output: `path=src/utils/helper.ts Wrote 27 bytes (created directory: src/utils) diff --git a/src/gadgets/github/CreatePR.ts b/src/gadgets/github/CreatePR.ts index 9c7bb986..cc474b3d 100644 --- a/src/gadgets/github/CreatePR.ts +++ b/src/gadgets/github/CreatePR.ts @@ -24,6 +24,7 @@ NOTE: Pre-commit and pre-push hooks may run tests which can take time. If hooks fail or timeout, the full output will be shown.`, timeoutMs: 240000, // 4 minutes - hooks may run test suites schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), owner: z.string().describe('The repository owner (username or organization)'), repo: z.string().describe('The repository name'), title: z @@ -48,6 +49,7 @@ If hooks fail or timeout, the full output will be shown.`, examples: [ { params: { + comment: 'Creating PR for completed auth feature', owner: 'acme', repo: 'myapp', title: 'feat: add user authentication', @@ -59,6 +61,7 @@ If hooks fail or timeout, the full output will be shown.`, }, { params: { + comment: 'Creating draft PR for early feedback', owner: 'acme', repo: 'myapp', title: 'fix: resolve null pointer in checkout', @@ -72,6 +75,7 @@ If hooks fail or timeout, the full output will be shown.`, }, { params: { + comment: 'Creating PR - already committed and pushed', owner: 'acme', repo: 'myapp', title: 'chore: update dependencies', diff --git a/src/gadgets/github/CreatePRReview.ts b/src/gadgets/github/CreatePRReview.ts index 10dc8002..91fb2b83 100644 --- a/src/gadgets/github/CreatePRReview.ts +++ b/src/gadgets/github/CreatePRReview.ts @@ -8,6 +8,7 @@ export class CreatePRReview extends Gadget({ 'Submit a code review on a GitHub pull request. Use this to approve, request changes, or comment on the PR.', timeoutMs: 30000, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), owner: z.string().describe('The repository owner (username or organization)'), repo: z.string().describe('The repository name'), prNumber: z.number().describe('The pull request number'), @@ -29,6 +30,7 @@ export class CreatePRReview extends Gadget({ examples: [ { params: { + comment: 'Approving PR after thorough review', owner: 'acme', repo: 'myapp', prNumber: 42, @@ -39,6 +41,7 @@ export class CreatePRReview extends Gadget({ }, { params: { + comment: 'Requesting changes for identified issues', owner: 'acme', repo: 'myapp', prNumber: 42, diff --git a/src/gadgets/github/GetPRChecks.ts b/src/gadgets/github/GetPRChecks.ts index 9b53673a..5925871d 100644 --- a/src/gadgets/github/GetPRChecks.ts +++ b/src/gadgets/github/GetPRChecks.ts @@ -57,6 +57,7 @@ export class GetPRChecks extends Gadget({ 'Get the CI check status for a GitHub pull request. Shows all workflow runs and their status/conclusion.', timeoutMs: 30000, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), owner: z.string().describe('The repository owner (username or organization)'), repo: z.string().describe('The repository name'), prNumber: z.number().describe('The pull request number'), @@ -64,6 +65,7 @@ export class GetPRChecks extends Gadget({ examples: [ { params: { + comment: 'Checking CI status before merge', owner: 'acme', repo: 'myapp', prNumber: 42, diff --git a/src/gadgets/github/GetPRComments.ts b/src/gadgets/github/GetPRComments.ts index 47686047..5444ef9e 100644 --- a/src/gadgets/github/GetPRComments.ts +++ b/src/gadgets/github/GetPRComments.ts @@ -8,6 +8,7 @@ export class GetPRComments extends Gadget({ 'Get all review comments on a GitHub pull request. Use this to understand what feedback has been given.', timeoutMs: 30000, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), owner: z.string().describe('The repository owner (username or organization)'), repo: z.string().describe('The repository name'), prNumber: z.number().describe('The pull request number'), @@ -15,6 +16,7 @@ export class GetPRComments extends Gadget({ examples: [ { params: { + comment: 'Fetching review comments to understand feedback', owner: 'acme', repo: 'myapp', prNumber: 42, diff --git a/src/gadgets/github/GetPRDetails.ts b/src/gadgets/github/GetPRDetails.ts index 4e2d29c7..1724768c 100644 --- a/src/gadgets/github/GetPRDetails.ts +++ b/src/gadgets/github/GetPRDetails.ts @@ -8,6 +8,7 @@ export class GetPRDetails extends Gadget({ 'Get details about a GitHub pull request including title, description, and branch info.', timeoutMs: 30000, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), owner: z.string().describe('The repository owner (username or organization)'), repo: z.string().describe('The repository name'), prNumber: z.number().describe('The pull request number'), @@ -15,6 +16,7 @@ export class GetPRDetails extends Gadget({ examples: [ { params: { + comment: 'Fetching PR details to understand changes', owner: 'acme', repo: 'myapp', prNumber: 42, diff --git a/src/gadgets/github/GetPRDiff.ts b/src/gadgets/github/GetPRDiff.ts index 14b31aa2..e2de6ace 100644 --- a/src/gadgets/github/GetPRDiff.ts +++ b/src/gadgets/github/GetPRDiff.ts @@ -8,6 +8,7 @@ export class GetPRDiff extends Gadget({ 'Get the unified diff of all file changes in a GitHub pull request. Shows each file with additions, deletions, and the patch content.', timeoutMs: 30000, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), owner: z.string().describe('The repository owner (username or organization)'), repo: z.string().describe('The repository name'), prNumber: z.number().describe('The pull request number'), @@ -15,6 +16,7 @@ export class GetPRDiff extends Gadget({ examples: [ { params: { + comment: 'Reviewing file changes for code review', owner: 'acme', repo: 'myapp', prNumber: 42, diff --git a/src/gadgets/github/ReplyToReviewComment.ts b/src/gadgets/github/ReplyToReviewComment.ts index 4296e593..be45f858 100644 --- a/src/gadgets/github/ReplyToReviewComment.ts +++ b/src/gadgets/github/ReplyToReviewComment.ts @@ -8,6 +8,7 @@ export class ReplyToReviewComment extends Gadget({ 'Reply to a specific review comment on a GitHub pull request. Use this to acknowledge feedback and explain what was fixed.', timeoutMs: 30000, schema: z.object({ + comment: z.string().min(1).describe('Brief rationale for this gadget call'), owner: z.string().describe('The repository owner (username or organization)'), repo: z.string().describe('The repository name'), prNumber: z.number().describe('The pull request number'), @@ -17,6 +18,7 @@ export class ReplyToReviewComment extends Gadget({ examples: [ { params: { + comment: 'Responding to review feedback about edge cases', owner: 'acme', repo: 'myapp', prNumber: 42, diff --git a/src/gadgets/tmux.ts b/src/gadgets/tmux.ts index b3827b09..366cde8f 100644 --- a/src/gadgets/tmux.ts +++ b/src/gadgets/tmux.ts @@ -628,6 +628,7 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor schema: z.discriminatedUnion('action', [ z.object({ action: z.literal('start'), + comment: z.string().min(1).describe('Brief rationale for this gadget call'), session: sessionNameSchema.describe("Unique session name (e.g., 'test-run', 'npm-install')"), command: z .string() @@ -641,6 +642,7 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor }), z.object({ action: z.literal('send'), + comment: z.string().min(1).describe('Brief rationale for this gadget call'), session: sessionNameSchema.describe('Target session name'), keys: z.string().describe("Keys or command to send (e.g., 'npm test' or 'C-c' for Ctrl+C)"), enter: z.coerce @@ -650,6 +652,7 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor }), z.object({ action: z.literal('capture'), + comment: z.string().min(1).describe('Brief rationale for this gadget call'), session: sessionNameSchema.describe('Session to capture output from'), lines: z.coerce .number() @@ -661,19 +664,28 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor }), z.object({ action: z.literal('list'), + comment: z.string().min(1).describe('Brief rationale for this gadget call'), }), z.object({ action: z.literal('kill'), + comment: z.string().min(1).describe('Brief rationale for this gadget call'), session: sessionNameSchema.describe('Session to terminate'), }), z.object({ action: z.literal('exists'), + comment: z.string().min(1).describe('Brief rationale for this gadget call'), session: sessionNameSchema.describe('Session name to check'), }), ]), examples: [ { - params: { action: 'start', session: 'test-run', command: 'npm test', wait: 120000 }, + params: { + action: 'start', + comment: 'Running unit tests to verify changes', + session: 'test-run', + command: 'npm test', + wait: 120000, + }, output: 'session=test-run status=exited exit_code=0\n\n> project@1.0.0 test\n> vitest run\n\nāœ“ 15 tests passed', comment: 'Run tests - command completed within 120s wait period', @@ -681,6 +693,7 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor { params: { action: 'start', + comment: 'Running lint and tests in sequence', session: 'pipeline', command: 'npm run lint && npm test', wait: 120000, @@ -691,6 +704,7 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor { params: { action: 'start', + comment: 'Testing frontend package separately', session: 'frontend-test', command: 'pnpm test', cwd: 'packages/frontend', @@ -702,6 +716,7 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor { params: { action: 'start', + comment: 'Starting E2E test suite', session: 'e2e-tests', command: 'npm run test:e2e', wait: 120000, @@ -711,28 +726,40 @@ Commands are interpreted by bash, so pipes, &&, ||, redirects, and globs all wor comment: 'Long-running E2E tests still running after 120s - use capture to monitor', }, { - params: { action: 'capture', session: 'npm-install', lines: 25 }, + params: { + action: 'capture', + comment: 'Checking install progress', + session: 'npm-install', + lines: 25, + }, output: 'session=npm-install lines=25\n\nadded 874 packages in 45s', comment: 'Check output from running session', }, { - params: { action: 'send', session: 'dev-server', keys: 'C-c', enter: false }, + params: { + action: 'send', + comment: 'Stopping dev server', + session: 'dev-server', + keys: 'C-c', + enter: false, + }, output: "session=dev-server status=sent\n\nSent keys to session 'dev-server': C-c", comment: 'Send Ctrl+C to stop a process', }, { - params: { action: 'kill', session: 'npm-install' }, + params: { action: 'kill', comment: 'Cleaning up completed session', session: 'npm-install' }, output: "session=npm-install status=killed\n\nSession 'npm-install' terminated", comment: 'Terminate a session', }, { - params: { action: 'list' }, + params: { action: 'list', comment: 'Checking for running sessions' }, output: 'sessions=2\n\ntest-run: npm (running)\nnpm-install: npm (running)', comment: 'List all active tmux sessions', }, { params: { action: 'start', + comment: 'Creating PR for OAuth feature', session: 'create-pr', command: "gh pr create --title 'feat(auth): add OAuth login' --body 'Implements OAuth flow'", diff --git a/src/gadgets/todo/TodoUpsert.ts b/src/gadgets/todo/TodoUpsert.ts index ab79478e..45a4b04b 100644 --- a/src/gadgets/todo/TodoUpsert.ts +++ b/src/gadgets/todo/TodoUpsert.ts @@ -130,7 +130,9 @@ Returns the full todo list after the operation.`, }), examples: [ { - params: { content: 'Read and understand the Trello card requirements' }, + params: { + content: 'Read and understand the Trello card requirements', + }, output: 'āž• Created todo #1.\n\nšŸ“‹ Todo List\n Progress: 0/1 done, 0 in progress, 1 pending\n\n⬜ #1 [pending]: Read and understand the Trello card requirements', comment: 'Create a new todo item', diff --git a/src/gadgets/trello/PostTrelloComment.ts b/src/gadgets/trello/PostTrelloComment.ts index e7532e5a..2e63e560 100644 --- a/src/gadgets/trello/PostTrelloComment.ts +++ b/src/gadgets/trello/PostTrelloComment.ts @@ -9,14 +9,13 @@ export class PostTrelloComment extends Gadget({ timeoutMs: 30000, schema: z.object({ cardId: z.string().describe('The Trello card ID'), - comment: z.string().describe('The comment text to post (supports markdown)'), + text: z.string().describe('The comment text to post (supports markdown)'), }), examples: [ { params: { cardId: 'abc123', - comment: - 'šŸ“‹ **Brief Ready for Review**\n\nI have analyzed the codebase and updated the card description.', + text: 'šŸ“‹ **Brief Ready for Review**\n\nI have analyzed the codebase and updated the card description.', }, comment: 'Post a status update to the card', }, @@ -24,7 +23,7 @@ export class PostTrelloComment extends Gadget({ }) { override async execute(params: this['params']): Promise { try { - await trelloClient.addComment(params.cardId, params.comment); + await trelloClient.addComment(params.cardId, params.text); return 'Comment posted successfully'; } catch (error) { return formatGadgetError('posting comment', error);