mirror of
https://github.com/claude-did-this/claude-hub.git
synced 2026-02-15 03:31:47 +01:00
Compare commits
3 Commits
fix/pr-rev
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7eea4e302e | ||
|
|
3c8aebced8 | ||
|
|
c067efa13e |
@@ -1,7 +1,7 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# Build stage - compile TypeScript and prepare production files
|
||||
FROM node:24-slim AS builder
|
||||
FROM node:25-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -21,7 +21,7 @@ RUN npm run build
|
||||
COPY . .
|
||||
|
||||
# Production dependency stage - smaller layer for dependencies
|
||||
FROM node:24-slim AS prod-deps
|
||||
FROM node:25-slim AS prod-deps
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -32,7 +32,7 @@ COPY package*.json ./
|
||||
RUN npm ci --omit=dev && npm cache clean --force
|
||||
|
||||
# Test stage - includes dev dependencies and test files
|
||||
FROM node:24-slim AS test
|
||||
FROM node:25-slim AS test
|
||||
|
||||
# Set shell with pipefail option
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
@@ -58,7 +58,7 @@ ENV NODE_ENV=test
|
||||
CMD ["npm", "run", "test:unit"]
|
||||
|
||||
# Production stage - minimal runtime image
|
||||
FROM node:24-slim AS production
|
||||
FROM node:25-slim AS production
|
||||
|
||||
# Set shell with pipefail option for better error handling
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24
|
||||
FROM node:25
|
||||
|
||||
# Install dependencies for interactive session
|
||||
RUN apt update && apt install -y \
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:24
|
||||
FROM node:25
|
||||
|
||||
# Install dependencies
|
||||
RUN apt update && apt install -y less \
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# First stage: Interactive setup for Claude Code authentication
|
||||
FROM node:24
|
||||
FROM node:25
|
||||
|
||||
# Install Claude Code
|
||||
RUN npm install -g @anthropic-ai/claude-code
|
||||
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -9,7 +9,6 @@
|
||||
"version": "0.1.1",
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^22.0.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"axios": "^1.6.2",
|
||||
"body-parser": "^2.2.0",
|
||||
"commander": "^14.0.0",
|
||||
@@ -22,14 +21,15 @@
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.3",
|
||||
"@babel/core": "^7.27.4",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@jest/globals": "^30.0.0-beta.3",
|
||||
"@types/body-parser": "^1.19.5",
|
||||
"@types/body-parser": "^1.19.6",
|
||||
"@types/express": "^5.0.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.15.23",
|
||||
"@types/supertest": "^6.0.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.33.0",
|
||||
"@typescript-eslint/parser": "^8.33.0",
|
||||
"babel-jest": "^29.7.0",
|
||||
@@ -86,20 +86,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.3.tgz",
|
||||
"integrity": "sha512-hyrN8ivxfvJ4i0fIJuV4EOlV0WDMz5Ui4StRTgVaAvWeiRCilXgwVvxJKtFQ3TKtHgJscB2YiXKGNJuVwhQMtA==",
|
||||
"version": "7.27.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
|
||||
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.27.3",
|
||||
"@babel/helper-compilation-targets": "^7.27.2",
|
||||
"@babel/helper-module-transforms": "^7.27.3",
|
||||
"@babel/helpers": "^7.27.3",
|
||||
"@babel/parser": "^7.27.3",
|
||||
"@babel/helpers": "^7.27.4",
|
||||
"@babel/parser": "^7.27.4",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/traverse": "^7.27.3",
|
||||
"@babel/traverse": "^7.27.4",
|
||||
"@babel/types": "^7.27.3",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"debug": "^4.1.0",
|
||||
@@ -366,10 +367,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helpers": {
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.3.tgz",
|
||||
"integrity": "sha512-h/eKy9agOya1IGuLaZ9tEUgz+uIRXcbtOhRtUyyMf8JFmn1iT13vnl/IGVWSkdOCG/pC57U4S1jnAabAavTMwg==",
|
||||
"version": "7.27.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz",
|
||||
"integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.27.3"
|
||||
@@ -379,10 +381,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.3.tgz",
|
||||
"integrity": "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw==",
|
||||
"version": "7.27.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
|
||||
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.27.3"
|
||||
},
|
||||
@@ -1636,14 +1639,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.3.tgz",
|
||||
"integrity": "sha512-lId/IfN/Ye1CIu8xG7oKBHXd2iNb2aW1ilPszzGcJug6M8RCKfVNcYhpI5+bMvFYjK7lXIM0R+a+6r8xhHp2FQ==",
|
||||
"version": "7.27.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
|
||||
"integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
"@babel/generator": "^7.27.3",
|
||||
"@babel/parser": "^7.27.3",
|
||||
"@babel/parser": "^7.27.4",
|
||||
"@babel/template": "^7.27.2",
|
||||
"@babel/types": "^7.27.3",
|
||||
"debug": "^4.3.1",
|
||||
@@ -3108,10 +3112,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
"integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
|
||||
"version": "1.19.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
|
||||
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/connect": "*",
|
||||
"@types/node": "*"
|
||||
@@ -3315,6 +3320,7 @@
|
||||
"version": "10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz",
|
||||
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/yargs": {
|
||||
|
||||
@@ -48,10 +48,10 @@
|
||||
"uuid": "^11.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.27.3",
|
||||
"@babel/core": "^7.27.4",
|
||||
"@babel/preset-env": "^7.27.2",
|
||||
"@jest/globals": "^30.0.0-beta.3",
|
||||
"@types/body-parser": "^1.19.5",
|
||||
"@types/body-parser": "^1.19.6",
|
||||
"@types/express": "^5.0.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.15.23",
|
||||
|
||||
@@ -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}.`;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user