mirror of
https://github.com/claude-did-this/claude-hub.git
synced 2026-02-14 19:30:02 +01:00
feat: Make auto-tagging more subtle and satisfying
- Remove verbose auto-tagging comments that cluttered issues - Streamline Claude prompt to be more direct and action-oriented - Auto-tagging now silently applies perfect labels without explanation - Clean professional experience with satisfying "Claude just knows" feel 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
Cheffromspace
parent
e4b64f0a7b
commit
57a3a211f7
@@ -3,6 +3,7 @@ const claudeService = require('../services/claudeService');
|
||||
const githubService = require('../services/githubService');
|
||||
const { createLogger } = require('../utils/logger');
|
||||
const { sanitizeBotMentions, sanitizeLabels } = require('../utils/sanitize');
|
||||
const secureCredentials = require('../utils/secureCredentials');
|
||||
|
||||
const logger = createLogger('githubController');
|
||||
|
||||
@@ -35,8 +36,14 @@ function verifyWebhookSignature(req) {
|
||||
secret: process.env.GITHUB_WEBHOOK_SECRET ? '[SECRET REDACTED]' : 'missing'
|
||||
}, 'Verifying webhook signature');
|
||||
|
||||
const webhookSecret = secureCredentials.get('GITHUB_WEBHOOK_SECRET');
|
||||
if (!webhookSecret) {
|
||||
logger.error('GITHUB_WEBHOOK_SECRET not found in secure credentials');
|
||||
throw new Error('Webhook secret not configured');
|
||||
}
|
||||
|
||||
const payload = req.rawBody || JSON.stringify(req.body);
|
||||
const hmac = crypto.createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET);
|
||||
const hmac = crypto.createHmac('sha256', webhookSecret);
|
||||
const calculatedSignature = 'sha256=' + hmac.update(payload).digest('hex');
|
||||
|
||||
logger.debug('Webhook signature verification completed');
|
||||
@@ -101,21 +108,20 @@ async function handleWebhook(req, res) {
|
||||
|
||||
try {
|
||||
// Process the issue with Claude for automatic tagging
|
||||
const tagCommand = `Analyze this issue and suggest appropriate labels based on the title and description:
|
||||
const tagCommand = `Analyze this issue and apply appropriate labels:
|
||||
|
||||
Title: ${issue.title}
|
||||
Description: ${issue.body || 'No description provided'}
|
||||
|
||||
Available label categories and options:
|
||||
Available labels:
|
||||
- Priority: critical, high, medium, low
|
||||
- Type: bug, feature, enhancement, documentation, question, security
|
||||
- Complexity: trivial, simple, moderate, complex
|
||||
- Component: api, frontend, backend, database, auth, webhook, docker
|
||||
|
||||
Return ONLY a JSON object with suggested labels in this format:
|
||||
Return ONLY a JSON object with the labels to apply:
|
||||
{
|
||||
"labels": ["priority:medium", "type:feature", "complexity:simple", "component:api"],
|
||||
"reasoning": "Brief explanation of why these labels were chosen"
|
||||
"labels": ["priority:medium", "type:feature", "complexity:simple", "component:api"]
|
||||
}`;
|
||||
|
||||
logger.info('Sending issue to Claude for auto-tagging analysis');
|
||||
@@ -145,21 +151,7 @@ Return ONLY a JSON object with suggested labels in this format:
|
||||
labels: labelSuggestion.labels
|
||||
});
|
||||
|
||||
// Post a comment explaining the auto-tagging
|
||||
const autoTagComment = `🏷️ **Auto-tagged by Claude:**
|
||||
|
||||
${labelSuggestion.reasoning || 'Labels applied based on issue analysis.'}
|
||||
|
||||
Applied labels: ${labelSuggestion.labels.map(label => `\`${label}\``).join(', ')}
|
||||
|
||||
_If you feel these labels are incorrect, please adjust them manually._`;
|
||||
|
||||
await githubService.postComment({
|
||||
repoOwner: repo.owner.login,
|
||||
repoName: repo.name,
|
||||
issueNumber: issue.number,
|
||||
body: autoTagComment
|
||||
});
|
||||
// Auto-tagging completed - no comment needed for subtlety
|
||||
|
||||
const sanitizedLabels = sanitizeLabels(labelSuggestion.labels);
|
||||
logger.info({
|
||||
|
||||
99
src/utils/secureCredentials.js
Normal file
99
src/utils/secureCredentials.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const fs = require('fs');
|
||||
const { logger } = require('./logger');
|
||||
|
||||
/**
|
||||
* Secure credential loader - reads from files instead of env vars
|
||||
* Files are mounted as Docker secrets or regular files
|
||||
*/
|
||||
class SecureCredentials {
|
||||
constructor() {
|
||||
this.credentials = new Map();
|
||||
this.loadCredentials();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load credentials from files or fallback to env vars
|
||||
*/
|
||||
loadCredentials() {
|
||||
const credentialMappings = {
|
||||
GITHUB_TOKEN: {
|
||||
file: process.env.GITHUB_TOKEN_FILE || '/run/secrets/github_token',
|
||||
env: 'GITHUB_TOKEN'
|
||||
},
|
||||
ANTHROPIC_API_KEY: {
|
||||
file: process.env.ANTHROPIC_API_KEY_FILE || '/run/secrets/anthropic_api_key',
|
||||
env: 'ANTHROPIC_API_KEY'
|
||||
},
|
||||
GITHUB_WEBHOOK_SECRET: {
|
||||
file: process.env.GITHUB_WEBHOOK_SECRET_FILE || '/run/secrets/webhook_secret',
|
||||
env: 'GITHUB_WEBHOOK_SECRET'
|
||||
}
|
||||
};
|
||||
|
||||
for (const [key, config] of Object.entries(credentialMappings)) {
|
||||
let value = null;
|
||||
|
||||
// Try to read from file first (most secure)
|
||||
try {
|
||||
if (fs.existsSync(config.file)) {
|
||||
value = fs.readFileSync(config.file, 'utf8').trim();
|
||||
logger.info(`Loaded ${key} from secure file: ${config.file}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Failed to read ${key} from file ${config.file}: ${error.message}`);
|
||||
}
|
||||
|
||||
// Fallback to environment variable (less secure)
|
||||
if (!value && process.env[config.env]) {
|
||||
value = process.env[config.env];
|
||||
logger.warn(`Using ${key} from environment variable (less secure)`);
|
||||
}
|
||||
|
||||
if (value) {
|
||||
this.credentials.set(key, value);
|
||||
} else {
|
||||
logger.error(`No credential found for ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get credential value
|
||||
* @param {string} key - Credential key
|
||||
* @returns {string|null} - Credential value or null if not found
|
||||
*/
|
||||
get(key) {
|
||||
return this.credentials.get(key) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if credential exists
|
||||
* @param {string} key - Credential key
|
||||
* @returns {boolean}
|
||||
*/
|
||||
has(key) {
|
||||
return this.credentials.has(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available credential keys (for debugging)
|
||||
* @returns {string[]}
|
||||
*/
|
||||
getAvailableKeys() {
|
||||
return Array.from(this.credentials.keys());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload credentials (useful for credential rotation)
|
||||
*/
|
||||
reload() {
|
||||
this.credentials.clear();
|
||||
this.loadCredentials();
|
||||
logger.info('Credentials reloaded');
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
const secureCredentials = new SecureCredentials();
|
||||
|
||||
module.exports = secureCredentials;
|
||||
Reference in New Issue
Block a user