Compare commits

..

6 Commits

Author SHA1 Message Date
Jonathan Flatt
899d004be9 fix: Address PR #181 review comments
- Remove unused docker-compose.hackathon.yml file
- Extract UUID regex to constant for better maintainability
- Document breaking changes in BREAKING_CHANGES.md
- Add comprehensive examples to Swagger documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-05 00:40:28 -05:00
Jonathan Flatt
3e8adba2dc test: Add comprehensive tests for SessionHandler dependency validation
Add test coverage for dependency validation logic in SessionHandler:
- Filter out invalid dependency values (empty strings, whitespace, "none")
- Validate UUID format for dependencies
- Handle mixed valid and invalid dependencies
- Support empty dependency arrays
- Handle arrays with only filtered values

This improves test coverage from ~91% to ~97% for SessionHandler.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-05 05:19:33 +00:00
Jonathan Flatt
db75aba44d feat: Add Claude API documentation and improve session validation
- Add comprehensive Swagger/OpenAPI documentation for Claude webhook API
- Add improved validation for session dependencies to handle edge cases
- Add hackathon-specific Docker Compose configuration
- Update SessionHandler to validate dependency UUIDs and filter invalid values
- Update SessionManager to properly handle sessions without dependencies
- Add API endpoint documentation with examples and schemas

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-05 05:10:44 +00:00
Jonathan Flatt
7f12e1f587 Merge remote-tracking branch 'origin/main' into feat/claude-api-swagger-docs 2025-06-05 05:08:41 +00:00
Jonathan Flatt
803410d3b2 fix: Update SessionManager tests for new implementation
- Update test to expect docker volume create instead of docker create
- Add unref() method to mock process objects to fix test environment error
- Update spawn expectations to match new docker run implementation
- Fix tests for both startSession and queueSession methods

Tests now pass in CI environment.
2025-06-04 00:51:32 +00:00
Jonathan Flatt
c1d8845b21 feat: Implement Claude orchestration with session management
- Add CLAUDE_WEBHOOK_SECRET for webhook authentication
- Fix Docker volume mounting for Claude credentials
- Capture Claude's internal session ID from stream-json output
- Update entrypoint script to support OUTPUT_FORMAT=stream-json
- Fix environment variable naming (REPOSITORY -> REPO_FULL_NAME)
- Enable parallel session execution with proper authentication
- Successfully tested creating PRs via orchestrated sessions

This enables the webhook to create and manage Claude Code sessions that can:
- Clone repositories
- Create feature branches
- Implement code changes
- Commit and push changes
- Create pull requests

All while capturing Claude's internal session ID for potential resumption.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-04 00:38:01 +00:00
4 changed files with 3 additions and 125 deletions

View File

@@ -1375,31 +1375,6 @@ Create a single comprehensive review comment with all feedback.
6. **Complete your review** with an appropriate event type (APPROVE, REQUEST_CHANGES, or COMMENT)
7. **Include commit SHA** - Always include "Reviewed at commit: ${commitSha}" in your final review comment
## CRITICAL: Markdown Formatting Requirements
**IMPORTANT**: When writing your review comments and responses:
- Return clean, human-readable markdown that GitHub will render correctly
- Do NOT escape or encode special characters like newlines (\\n), quotes, or backslashes
- Use proper markdown syntax directly:
- Use actual bullet points: * or - (not escaped versions)
- Use actual code blocks with triple backticks (not escaped)
- Use actual line breaks (press Enter, don't write \\n)
- Use actual quotes " or ' (not escaped versions like \\" or \\')
- Your output should look like normal markdown text, NOT escaped strings
- GitHub expects standard markdown - write it naturally as you would in any markdown editor
Example of CORRECT formatting:
* This is a bullet point
* Another bullet point with \`inline code\`
\`\`\`javascript
// This is a code block
const example = "string";
\`\`\`
Example of INCORRECT formatting (DO NOT DO THIS):
\\* This is a bullet point\\n\\* Another bullet point with \\\`inline code\\\`\\n\\n\\\`\\\`\\\`javascript\\n// This is a code block\\nconst example = \\"string\\";\\n\\\`\\\`\\\`
Please perform a comprehensive review of PR #${prNumber} in repository ${repoFullName}.`;
}

View File

@@ -3,7 +3,7 @@ import { promisify } from 'util';
import { execFile } from 'child_process';
import path from 'path';
import { createLogger } from '../utils/logger';
import { sanitizeBotMentions, unescapeMarkdown } from '../utils/sanitize';
import { sanitizeBotMentions } from '../utils/sanitize';
import secureCredentials from '../utils/secureCredentials';
import type {
ClaudeCommandOptions,
@@ -79,8 +79,7 @@ Since this is a test environment, I'm providing a simulated response. In product
For real functionality, please configure valid GitHub and Claude API tokens.`;
// Always sanitize responses, even in test mode
const unescapedResponse = unescapeMarkdown(testResponse);
return sanitizeBotMentions(unescapedResponse);
return sanitizeBotMentions(testResponse);
}
// Build Docker image if it doesn't exist
@@ -196,9 +195,6 @@ For real functionality, please configure valid GitHub and Claude API tokens.`;
}
}
// Unescape any markdown formatting that may have been escaped by Claude
responseText = unescapeMarkdown(responseText);
// Sanitize response to prevent infinite loops by removing bot mentions
responseText = sanitizeBotMentions(responseText);

View File

@@ -101,43 +101,3 @@ export function sanitizeEnvironmentValue(key: string, value: string): string {
return isSensitive ? '[REDACTED]' : value;
}
/**
* Unescapes markdown formatting that may have been escaped by Claude
* This is a fallback for when Claude escapes markdown despite instructions
*/
export function unescapeMarkdown(text: string): string {
if (!text) return text;
// Replace escaped newlines with actual newlines
let unescaped = text.replace(/\\n/g, '\n');
// Replace escaped quotes
unescaped = unescaped.replace(/\\"/g, '"');
unescaped = unescaped.replace(/\\'/g, "'");
// Replace escaped backticks (for code blocks)
unescaped = unescaped.replace(/\\`/g, '`');
// Replace escaped backslashes (but be careful not to double-unescape)
// Only replace \\ with \ if it's not followed by another escape sequence
unescaped = unescaped.replace(/\\\\(?![nrt"`'])/g, '\\');
// Replace escaped asterisks and other markdown characters
unescaped = unescaped.replace(/\\\*/g, '*');
unescaped = unescaped.replace(/\\_/g, '_');
unescaped = unescaped.replace(/\\-/g, '-');
unescaped = unescaped.replace(/\\#/g, '#');
unescaped = unescaped.replace(/\\>/g, '>');
unescaped = unescaped.replace(/\\\[/g, '[');
unescaped = unescaped.replace(/\\\]/g, ']');
unescaped = unescaped.replace(/\\\(/g, '(');
unescaped = unescaped.replace(/\\\)/g, ')');
// Log if unescaping occurred
if (unescaped !== text) {
logger.info('Unescaped markdown formatting in text');
}
return unescaped;
}

View File

@@ -4,8 +4,7 @@ import {
sanitizeCommandInput,
validateRepositoryName,
validateGitHubRef,
sanitizeEnvironmentValue,
unescapeMarkdown
sanitizeEnvironmentValue
} from '../../../src/utils/sanitize';
describe('Sanitize Utils', () => {
@@ -180,56 +179,4 @@ describe('Sanitize Utils', () => {
expect(sanitizeEnvironmentValue('DB_PASSWORD_HASH', 'value')).toBe('[REDACTED]');
});
});
describe('unescapeMarkdown', () => {
it('should unescape escaped newlines', () => {
const input = 'Line 1\\nLine 2\\nLine 3';
const expected = 'Line 1\nLine 2\nLine 3';
expect(unescapeMarkdown(input)).toBe(expected);
});
it('should unescape escaped quotes', () => {
const input = 'This is a \\"quoted\\" string and this is a \\\'single quoted\\\' string';
const expected = 'This is a "quoted" string and this is a \'single quoted\' string';
expect(unescapeMarkdown(input)).toBe(expected);
});
it('should unescape markdown characters', () => {
const input = '\\* This is a bullet\\n\\- Another bullet\\n\\# Header\\n\\`code\\`';
const expected = '* This is a bullet\n- Another bullet\n# Header\n`code`';
expect(unescapeMarkdown(input)).toBe(expected);
});
it('should handle escaped code blocks', () => {
const input = '\\`\\`\\`javascript\\nconst x = \\"test\\";\\n\\`\\`\\`';
const expected = '```javascript\nconst x = "test";\n```';
expect(unescapeMarkdown(input)).toBe(expected);
});
it('should handle mixed markdown escaping', () => {
const input =
'\\# PR Review\\n\\n\\* Issue: Code has problems\\n\\* Suggestion: Fix them\\n\\n\\`\\`\\`js\\ncode()\\n\\`\\`\\`';
const expected =
'# PR Review\n\n* Issue: Code has problems\n* Suggestion: Fix them\n\n```js\ncode()\n```';
expect(unescapeMarkdown(input)).toBe(expected);
});
it('should handle empty or null input', () => {
expect(unescapeMarkdown('')).toBe('');
expect(unescapeMarkdown(null as any)).toBe(null);
expect(unescapeMarkdown(undefined as any)).toBe(undefined);
});
it('should not modify already correct markdown', () => {
const input =
'# Header\n\n* Bullet point\n* Another bullet\n\n```javascript\nconst x = "test";\n```';
expect(unescapeMarkdown(input)).toBe(input);
});
it('should handle escaped brackets and parentheses', () => {
const input = 'Link: \\[text\\]\\(url\\) and another \\[link\\]';
const expected = 'Link: [text](url) and another [link]';
expect(unescapeMarkdown(input)).toBe(expected);
});
});
});