mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
fix: address PR comments and complete prompt centralization
- Fix inline type imports in defaults.ts (move to top-level imports) - Update ideation-service.ts to use centralized prompts from settings - Update generate-title.ts to use centralized prompts - Update validate-issue.ts to use centralized prompts - Clean up validation-schema.ts (prompts already centralized) - Minor server index cleanup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -219,7 +219,7 @@ app.get('/api/health/detailed', createDetailedHandler());
|
||||
app.use('/api/fs', createFsRoutes(events));
|
||||
app.use('/api/agent', createAgentRoutes(agentService, events));
|
||||
app.use('/api/sessions', createSessionsRoutes(agentService));
|
||||
app.use('/api/features', createFeaturesRoutes(featureLoader));
|
||||
app.use('/api/features', createFeaturesRoutes(featureLoader, settingsService));
|
||||
app.use('/api/auto-mode', createAutoModeRoutes(autoModeService));
|
||||
app.use('/api/enhance-prompt', createEnhancePromptRoutes(settingsService));
|
||||
app.use('/api/worktree', createWorktreeRoutes(events, settingsService));
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import { Router } from 'express';
|
||||
import { FeatureLoader } from '../../services/feature-loader.js';
|
||||
import type { SettingsService } from '../../services/settings-service.js';
|
||||
import { validatePathParams } from '../../middleware/validate-paths.js';
|
||||
import { createListHandler } from './routes/list.js';
|
||||
import { createGetHandler } from './routes/get.js';
|
||||
@@ -15,7 +16,10 @@ import { createDeleteHandler } from './routes/delete.js';
|
||||
import { createAgentOutputHandler, createRawOutputHandler } from './routes/agent-output.js';
|
||||
import { createGenerateTitleHandler } from './routes/generate-title.js';
|
||||
|
||||
export function createFeaturesRoutes(featureLoader: FeatureLoader): Router {
|
||||
export function createFeaturesRoutes(
|
||||
featureLoader: FeatureLoader,
|
||||
settingsService?: SettingsService
|
||||
): Router {
|
||||
const router = Router();
|
||||
|
||||
router.post('/list', validatePathParams('projectPath'), createListHandler(featureLoader));
|
||||
@@ -35,7 +39,7 @@ export function createFeaturesRoutes(featureLoader: FeatureLoader): Router {
|
||||
router.post('/delete', validatePathParams('projectPath'), createDeleteHandler(featureLoader));
|
||||
router.post('/agent-output', createAgentOutputHandler(featureLoader));
|
||||
router.post('/raw-output', createRawOutputHandler(featureLoader));
|
||||
router.post('/generate-title', createGenerateTitleHandler());
|
||||
router.post('/generate-title', createGenerateTitleHandler(settingsService));
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import type { Request, Response } from 'express';
|
||||
import { createLogger } from '@automaker/utils';
|
||||
import { CLAUDE_MODEL_MAP } from '@automaker/model-resolver';
|
||||
import { simpleQuery } from '../../../providers/simple-query-service.js';
|
||||
import type { SettingsService } from '../../../services/settings-service.js';
|
||||
import { getPromptCustomization } from '../../../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('GenerateTitle');
|
||||
|
||||
@@ -26,16 +28,9 @@ interface GenerateTitleErrorResponse {
|
||||
error: string;
|
||||
}
|
||||
|
||||
const SYSTEM_PROMPT = `You are a title generator. Your task is to create a concise, descriptive title (5-10 words max) for a software feature based on its description.
|
||||
|
||||
Rules:
|
||||
- Output ONLY the title, nothing else
|
||||
- Keep it short and action-oriented (e.g., "Add dark mode toggle", "Fix login validation")
|
||||
- Start with a verb when possible (Add, Fix, Update, Implement, Create, etc.)
|
||||
- No quotes, periods, or extra formatting
|
||||
- Capture the essence of the feature in a scannable way`;
|
||||
|
||||
export function createGenerateTitleHandler(): (req: Request, res: Response) => Promise<void> {
|
||||
export function createGenerateTitleHandler(
|
||||
settingsService?: SettingsService
|
||||
): (req: Request, res: Response) => Promise<void> {
|
||||
return async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
const { description } = req.body as GenerateTitleRequestBody;
|
||||
@@ -61,11 +56,15 @@ export function createGenerateTitleHandler(): (req: Request, res: Response) => P
|
||||
|
||||
logger.info(`Generating title for description: ${trimmedDescription.substring(0, 50)}...`);
|
||||
|
||||
// Get customized prompts from settings
|
||||
const prompts = await getPromptCustomization(settingsService, '[GenerateTitle]');
|
||||
const systemPrompt = prompts.titleGeneration.systemPrompt;
|
||||
|
||||
const userPrompt = `Generate a concise title for this feature:\n\n${trimmedDescription}`;
|
||||
|
||||
// Use simpleQuery - provider abstraction handles all the streaming/extraction
|
||||
const result = await simpleQuery({
|
||||
prompt: `${SYSTEM_PROMPT}\n\n${userPrompt}`,
|
||||
prompt: `${systemPrompt}\n\n${userPrompt}`,
|
||||
model: CLAUDE_MODEL_MAP.haiku,
|
||||
cwd: process.cwd(),
|
||||
maxTurns: 1,
|
||||
|
||||
@@ -30,11 +30,11 @@ import { writeValidation } from '../../../lib/validation-storage.js';
|
||||
import { streamingQuery } from '../../../providers/simple-query-service.js';
|
||||
import {
|
||||
issueValidationSchema,
|
||||
ISSUE_VALIDATION_SYSTEM_PROMPT,
|
||||
buildValidationPrompt,
|
||||
ValidationComment,
|
||||
ValidationLinkedPR,
|
||||
} from './validation-schema.js';
|
||||
import { getPromptCustomization } from '../../../lib/settings-helpers.js';
|
||||
import {
|
||||
trySetValidationRunning,
|
||||
clearValidationStatus,
|
||||
@@ -117,13 +117,17 @@ async function runValidation(
|
||||
|
||||
let responseText = '';
|
||||
|
||||
// Get customized prompts from settings
|
||||
const prompts = await getPromptCustomization(settingsService, '[ValidateIssue]');
|
||||
const issueValidationSystemPrompt = prompts.issueValidation.systemPrompt;
|
||||
|
||||
// Determine if we should use structured output (Claude/Codex support it, Cursor/OpenCode don't)
|
||||
const useStructuredOutput = isClaudeModel(model) || isCodexModel(model);
|
||||
|
||||
// Build the final prompt - for Cursor, include system prompt and JSON schema instructions
|
||||
let finalPrompt = basePrompt;
|
||||
if (!useStructuredOutput) {
|
||||
finalPrompt = `${ISSUE_VALIDATION_SYSTEM_PROMPT}
|
||||
finalPrompt = `${issueValidationSystemPrompt}
|
||||
|
||||
CRITICAL INSTRUCTIONS:
|
||||
1. DO NOT write any files. Return the JSON in your response only.
|
||||
@@ -167,7 +171,7 @@ ${basePrompt}`;
|
||||
prompt: finalPrompt,
|
||||
model: model as string,
|
||||
cwd: projectPath,
|
||||
systemPrompt: useStructuredOutput ? ISSUE_VALIDATION_SYSTEM_PROMPT : undefined,
|
||||
systemPrompt: useStructuredOutput ? issueValidationSystemPrompt : undefined,
|
||||
abortController,
|
||||
thinkingLevel: effectiveThinkingLevel,
|
||||
reasoningEffort: effectiveReasoningEffort,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* Issue Validation Schema and System Prompt
|
||||
* Issue Validation Schema and Prompt Building
|
||||
*
|
||||
* Defines the JSON schema for Claude's structured output and
|
||||
* the system prompt that guides the validation process.
|
||||
* helper functions for building validation prompts.
|
||||
*
|
||||
* Note: The system prompt is now centralized in @automaker/prompts
|
||||
* and accessed via getPromptCustomization() in validate-issue.ts
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -82,76 +85,6 @@ export const issueValidationSchema = {
|
||||
additionalProperties: false,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* System prompt that guides Claude in validating GitHub issues.
|
||||
* Instructs the model to use read-only tools to analyze the codebase.
|
||||
*/
|
||||
export const ISSUE_VALIDATION_SYSTEM_PROMPT = `You are an expert code analyst validating GitHub issues against a codebase.
|
||||
|
||||
Your task is to analyze a GitHub issue and determine if it's valid by scanning the codebase.
|
||||
|
||||
## Validation Process
|
||||
|
||||
1. **Read the issue carefully** - Understand what is being reported or requested
|
||||
2. **Search the codebase** - Use Glob to find relevant files by pattern, Grep to search for keywords
|
||||
3. **Examine the code** - Use Read to look at the actual implementation in relevant files
|
||||
4. **Check linked PRs** - If there are linked pull requests, use \`gh pr diff <PR_NUMBER>\` to review the changes
|
||||
5. **Form your verdict** - Based on your analysis, determine if the issue is valid
|
||||
|
||||
## Verdicts
|
||||
|
||||
- **valid**: The issue describes a real problem that exists in the codebase, or a clear feature request that can be implemented. The referenced files/components exist and the issue is actionable.
|
||||
|
||||
- **invalid**: The issue describes behavior that doesn't exist, references non-existent files or components, is based on a misunderstanding of the code, or the described "bug" is actually expected behavior.
|
||||
|
||||
- **needs_clarification**: The issue lacks sufficient detail to verify. Specify what additional information is needed in the missingInfo field.
|
||||
|
||||
## For Bug Reports, Check:
|
||||
- Do the referenced files/components exist?
|
||||
- Does the code match what the issue describes?
|
||||
- Is the described behavior actually a bug or expected?
|
||||
- Can you locate the code that would cause the reported issue?
|
||||
|
||||
## For Feature Requests, Check:
|
||||
- Does the feature already exist?
|
||||
- Is the implementation location clear?
|
||||
- Is the request technically feasible given the codebase structure?
|
||||
|
||||
## Analyzing Linked Pull Requests
|
||||
|
||||
When an issue has linked PRs (especially open ones), you MUST analyze them:
|
||||
|
||||
1. **Run \`gh pr diff <PR_NUMBER>\`** to see what changes the PR makes
|
||||
2. **Run \`gh pr view <PR_NUMBER>\`** to see PR description and status
|
||||
3. **Evaluate if the PR fixes the issue** - Does the diff address the reported problem?
|
||||
4. **Provide a recommendation**:
|
||||
- \`wait_for_merge\`: The PR appears to fix the issue correctly. No additional work needed - just wait for it to be merged.
|
||||
- \`pr_needs_work\`: The PR attempts to fix the issue but is incomplete or has problems.
|
||||
- \`no_pr\`: No relevant PR exists for this issue.
|
||||
|
||||
5. **Include prAnalysis in your response** with:
|
||||
- hasOpenPR: true/false
|
||||
- prFixesIssue: true/false (based on diff analysis)
|
||||
- prNumber: the PR number you analyzed
|
||||
- prSummary: brief description of what the PR changes
|
||||
- recommendation: one of the above values
|
||||
|
||||
## Response Guidelines
|
||||
|
||||
- **Always include relatedFiles** when you find relevant code
|
||||
- **Set bugConfirmed to true** only if you can definitively confirm a bug exists in the code
|
||||
- **Provide a suggestedFix** when you have a clear idea of how to address the issue
|
||||
- **Use missingInfo** when the verdict is needs_clarification to list what's needed
|
||||
- **Include prAnalysis** when there are linked PRs - this is critical for avoiding duplicate work
|
||||
- **Set estimatedComplexity** to help prioritize:
|
||||
- trivial: Simple text changes, one-line fixes
|
||||
- simple: Small changes to one file
|
||||
- moderate: Changes to multiple files or moderate logic changes
|
||||
- complex: Significant refactoring or new feature implementation
|
||||
- very_complex: Major architectural changes or cross-cutting concerns
|
||||
|
||||
Be thorough in your analysis but focus on files that are directly relevant to the issue.`;
|
||||
|
||||
/**
|
||||
* Comment data structure for validation prompt
|
||||
*/
|
||||
|
||||
@@ -41,6 +41,7 @@ import type { FeatureLoader } from './feature-loader.js';
|
||||
import { createChatOptions, validateWorkingDirectory } from '../lib/sdk-options.js';
|
||||
import { resolveModelString } from '@automaker/model-resolver';
|
||||
import { stripProviderPrefix } from '@automaker/types';
|
||||
import { getPromptCustomization } from '../lib/settings-helpers.js';
|
||||
|
||||
const logger = createLogger('IdeationService');
|
||||
|
||||
@@ -195,8 +196,12 @@ export class IdeationService {
|
||||
// Gather existing features and ideas to prevent duplicate suggestions
|
||||
const existingWorkContext = await this.gatherExistingWorkContext(projectPath);
|
||||
|
||||
// Get customized prompts from settings
|
||||
const prompts = await getPromptCustomization(this.settingsService, '[IdeationService]');
|
||||
|
||||
// Build system prompt for ideation
|
||||
const systemPrompt = this.buildIdeationSystemPrompt(
|
||||
prompts.ideation.ideationSystemPrompt,
|
||||
contextResult.formattedPrompt,
|
||||
activeSession.session.promptCategory,
|
||||
existingWorkContext
|
||||
@@ -645,8 +650,12 @@ export class IdeationService {
|
||||
// Gather existing features and ideas to prevent duplicates
|
||||
const existingWorkContext = await this.gatherExistingWorkContext(projectPath);
|
||||
|
||||
// Get customized prompts from settings
|
||||
const prompts = await getPromptCustomization(this.settingsService, '[IdeationService]');
|
||||
|
||||
// Build system prompt for structured suggestions
|
||||
const systemPrompt = this.buildSuggestionsSystemPrompt(
|
||||
prompts.ideation.suggestionsSystemPrompt,
|
||||
contextPrompt,
|
||||
category,
|
||||
count,
|
||||
@@ -721,8 +730,14 @@ export class IdeationService {
|
||||
|
||||
/**
|
||||
* Build system prompt for structured suggestion generation
|
||||
* @param basePrompt - The base system prompt from settings
|
||||
* @param contextFilesPrompt - Project context from loaded files
|
||||
* @param category - The idea category to focus on
|
||||
* @param count - Number of suggestions to generate
|
||||
* @param existingWorkContext - Context about existing features/ideas
|
||||
*/
|
||||
private buildSuggestionsSystemPrompt(
|
||||
basePrompt: string,
|
||||
contextFilesPrompt: string | undefined,
|
||||
category: IdeaCategory,
|
||||
count: number = 10,
|
||||
@@ -734,35 +749,18 @@ export class IdeationService {
|
||||
|
||||
const existingWorkSection = existingWorkContext ? `\n\n${existingWorkContext}` : '';
|
||||
|
||||
return `You are an AI product strategist helping brainstorm feature ideas for a software project.
|
||||
// Replace placeholder {{count}} if present, otherwise append count instruction
|
||||
let prompt = basePrompt;
|
||||
if (prompt.includes('{{count}}')) {
|
||||
prompt = prompt.replace(/\{\{count\}\}/g, String(count));
|
||||
} else {
|
||||
prompt += `\n\nGenerate exactly ${count} suggestions.`;
|
||||
}
|
||||
|
||||
IMPORTANT: You do NOT have access to any tools. You CANNOT read files, search code, or run commands.
|
||||
You must generate suggestions based ONLY on the project context provided below.
|
||||
Do NOT say "I'll analyze" or "Let me explore" - you cannot do those things.
|
||||
|
||||
Based on the project context and the user's prompt, generate exactly ${count} creative and actionable feature suggestions.
|
||||
|
||||
YOUR RESPONSE MUST BE ONLY A JSON ARRAY - nothing else. No explanation, no preamble, no markdown code fences.
|
||||
|
||||
Each suggestion must have this structure:
|
||||
{
|
||||
"title": "Short, actionable title (max 60 chars)",
|
||||
"description": "Clear description of what to build or improve (2-3 sentences)",
|
||||
"rationale": "Why this is valuable - the problem it solves or opportunity it creates",
|
||||
"priority": "high" | "medium" | "low"
|
||||
}
|
||||
return `${prompt}
|
||||
|
||||
Focus area: ${this.getCategoryDescription(category)}
|
||||
|
||||
Guidelines:
|
||||
- Generate exactly ${count} suggestions
|
||||
- Be specific and actionable - avoid vague ideas
|
||||
- Mix different priority levels (some high, some medium, some low)
|
||||
- Each suggestion should be independently implementable
|
||||
- Think creatively - include both obvious improvements and innovative ideas
|
||||
- Consider the project's domain and target users
|
||||
- IMPORTANT: Do NOT suggest features or ideas that already exist in the "Existing Features" or "Existing Ideas" sections below
|
||||
|
||||
${contextSection}${existingWorkSection}`;
|
||||
}
|
||||
|
||||
@@ -1269,30 +1267,11 @@ ${contextSection}${existingWorkSection}`;
|
||||
// ============================================================================
|
||||
|
||||
private buildIdeationSystemPrompt(
|
||||
basePrompt: string,
|
||||
contextFilesPrompt: string | undefined,
|
||||
category?: IdeaCategory,
|
||||
existingWorkContext?: string
|
||||
): string {
|
||||
const basePrompt = `You are an AI product strategist and UX expert helping brainstorm ideas for improving a software project.
|
||||
|
||||
Your role is to:
|
||||
- Analyze the codebase structure and patterns
|
||||
- Identify opportunities for improvement
|
||||
- Suggest actionable ideas with clear rationale
|
||||
- Consider user experience, technical feasibility, and business value
|
||||
- Be specific and reference actual files/components when possible
|
||||
|
||||
When suggesting ideas:
|
||||
1. Provide a clear, concise title
|
||||
2. Explain the problem or opportunity
|
||||
3. Describe the proposed solution
|
||||
4. Highlight the expected benefit
|
||||
5. Note any dependencies or considerations
|
||||
|
||||
IMPORTANT: Do NOT suggest features or ideas that already exist in the project. Check the "Existing Features" and "Existing Ideas" sections below to avoid duplicates.
|
||||
|
||||
Focus on practical, implementable suggestions that would genuinely improve the product.`;
|
||||
|
||||
const categoryContext = category
|
||||
? `\n\nFocus area: ${this.getCategoryDescription(category)}`
|
||||
: '';
|
||||
|
||||
@@ -19,6 +19,10 @@ import type {
|
||||
ResolvedTitleGenerationPrompts,
|
||||
ResolvedIssueValidationPrompts,
|
||||
ResolvedIdeationPrompts,
|
||||
ResolvedAppSpecPrompts,
|
||||
ResolvedContextDescriptionPrompts,
|
||||
ResolvedSuggestionsPrompts,
|
||||
ResolvedTaskExecutionPrompts,
|
||||
} from '@automaker/types';
|
||||
import { STATIC_PORT, SERVER_PORT } from '@automaker/types';
|
||||
|
||||
@@ -686,7 +690,7 @@ IMPORTANT: Do not ask for clarification. The specification is provided above. Ge
|
||||
/**
|
||||
* Default App Spec prompts (for project specification generation)
|
||||
*/
|
||||
export const DEFAULT_APP_SPEC_PROMPTS: import('@automaker/types').ResolvedAppSpecPrompts = {
|
||||
export const DEFAULT_APP_SPEC_PROMPTS: ResolvedAppSpecPrompts = {
|
||||
generateSpecSystemPrompt: DEFAULT_APP_SPEC_GENERATE_SYSTEM_PROMPT,
|
||||
structuredSpecInstructions: DEFAULT_APP_SPEC_STRUCTURED_INSTRUCTIONS,
|
||||
generateFeaturesFromSpecPrompt: DEFAULT_GENERATE_FEATURES_FROM_SPEC_PROMPT,
|
||||
@@ -709,11 +713,10 @@ Respond with ONLY the description text, no additional formatting, preamble, or e
|
||||
/**
|
||||
* Default Context Description prompts (for file/image descriptions)
|
||||
*/
|
||||
export const DEFAULT_CONTEXT_DESCRIPTION_PROMPTS: import('@automaker/types').ResolvedContextDescriptionPrompts =
|
||||
{
|
||||
describeFilePrompt: DEFAULT_DESCRIBE_FILE_PROMPT,
|
||||
describeImagePrompt: DEFAULT_DESCRIBE_IMAGE_PROMPT,
|
||||
};
|
||||
export const DEFAULT_CONTEXT_DESCRIPTION_PROMPTS: ResolvedContextDescriptionPrompts = {
|
||||
describeFilePrompt: DEFAULT_DESCRIBE_FILE_PROMPT,
|
||||
describeImagePrompt: DEFAULT_DESCRIBE_IMAGE_PROMPT,
|
||||
};
|
||||
|
||||
/**
|
||||
* ========================================================================
|
||||
@@ -743,7 +746,7 @@ The response will be automatically formatted as structured JSON.`;
|
||||
/**
|
||||
* Default Suggestions prompts (for features, refactoring, security, performance)
|
||||
*/
|
||||
export const DEFAULT_SUGGESTIONS_PROMPTS: import('@automaker/types').ResolvedSuggestionsPrompts = {
|
||||
export const DEFAULT_SUGGESTIONS_PROMPTS: ResolvedSuggestionsPrompts = {
|
||||
featuresPrompt: DEFAULT_SUGGESTIONS_FEATURES_PROMPT,
|
||||
refactoringPrompt: DEFAULT_SUGGESTIONS_REFACTORING_PROMPT,
|
||||
securityPrompt: DEFAULT_SUGGESTIONS_SECURITY_PROMPT,
|
||||
@@ -930,18 +933,17 @@ Format your response as a structured markdown document.`;
|
||||
/**
|
||||
* Default Task Execution prompts (for Auto Mode task execution, learning extraction)
|
||||
*/
|
||||
export const DEFAULT_TASK_EXECUTION_PROMPTS: import('@automaker/types').ResolvedTaskExecutionPrompts =
|
||||
{
|
||||
taskPromptTemplate: DEFAULT_TASK_PROMPT_TEMPLATE,
|
||||
implementationInstructions: DEFAULT_IMPLEMENTATION_INSTRUCTIONS,
|
||||
playwrightVerificationInstructions: DEFAULT_PLAYWRIGHT_VERIFICATION_INSTRUCTIONS,
|
||||
learningExtractionSystemPrompt: DEFAULT_LEARNING_EXTRACTION_SYSTEM_PROMPT,
|
||||
learningExtractionUserPromptTemplate: DEFAULT_LEARNING_EXTRACTION_USER_TEMPLATE,
|
||||
planRevisionTemplate: DEFAULT_PLAN_REVISION_TEMPLATE,
|
||||
continuationAfterApprovalTemplate: DEFAULT_CONTINUATION_AFTER_APPROVAL_TEMPLATE,
|
||||
resumeFeatureTemplate: DEFAULT_RESUME_FEATURE_TEMPLATE,
|
||||
projectAnalysisPrompt: DEFAULT_PROJECT_ANALYSIS_PROMPT,
|
||||
};
|
||||
export const DEFAULT_TASK_EXECUTION_PROMPTS: ResolvedTaskExecutionPrompts = {
|
||||
taskPromptTemplate: DEFAULT_TASK_PROMPT_TEMPLATE,
|
||||
implementationInstructions: DEFAULT_IMPLEMENTATION_INSTRUCTIONS,
|
||||
playwrightVerificationInstructions: DEFAULT_PLAYWRIGHT_VERIFICATION_INSTRUCTIONS,
|
||||
learningExtractionSystemPrompt: DEFAULT_LEARNING_EXTRACTION_SYSTEM_PROMPT,
|
||||
learningExtractionUserPromptTemplate: DEFAULT_LEARNING_EXTRACTION_USER_TEMPLATE,
|
||||
planRevisionTemplate: DEFAULT_PLAN_REVISION_TEMPLATE,
|
||||
continuationAfterApprovalTemplate: DEFAULT_CONTINUATION_AFTER_APPROVAL_TEMPLATE,
|
||||
resumeFeatureTemplate: DEFAULT_RESUME_FEATURE_TEMPLATE,
|
||||
projectAnalysisPrompt: DEFAULT_PROJECT_ANALYSIS_PROMPT,
|
||||
};
|
||||
|
||||
/**
|
||||
* ========================================================================
|
||||
|
||||
Reference in New Issue
Block a user