mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: Enhance JSON streaming parsing and UI updates
- Added a function to parse JSON streaming content in core-memory.js, extracting readable text from messages. - Updated memory detail view to utilize the new parsing function for content and summary. - Introduced an enableReview option in rules-manager.js, allowing users to toggle review functionality in rule creation. - Simplified skill creation modal in skills-manager.js by removing generation type selection UI. - Improved CLI executor to handle tool calls for file writing, ensuring proper output parsing. - Adjusted CLI command tests to set timeout to 0 for immediate execution. - Updated file watcher to implement a true debounce mechanism and added a pending queue status for UI updates. - Enhanced watcher manager to handle queue changes and provide JSON output for better integration with TypeScript backend. - Established TypeScript naming conventions documentation to standardize code style across the project.
This commit is contained in:
@@ -453,7 +453,8 @@ ${memory.content}
|
||||
category: 'internal'
|
||||
});
|
||||
|
||||
const summary = result.stdout?.trim() || 'Failed to generate summary';
|
||||
// Use parsedOutput (extracted text from stream JSON) instead of raw stdout
|
||||
const summary = result.parsedOutput?.trim() || result.stdout?.trim() || 'Failed to generate summary';
|
||||
|
||||
// Update memory with summary
|
||||
const stmt = this.db.prepare(`
|
||||
|
||||
@@ -613,7 +613,7 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
id: syncId
|
||||
});
|
||||
|
||||
if (!result.success || !result.execution?.output) {
|
||||
if (!result.success) {
|
||||
return {
|
||||
error: 'CLI execution failed',
|
||||
details: result.execution?.error || 'No output received',
|
||||
@@ -621,10 +621,13 @@ export async function handleClaudeRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
};
|
||||
}
|
||||
|
||||
// Extract CLI output
|
||||
const cliOutput = typeof result.execution.output === 'string'
|
||||
? result.execution.output
|
||||
: result.execution.output.stdout || '';
|
||||
// Extract CLI output - prefer parsedOutput (extracted text from stream JSON)
|
||||
let cliOutput = result.parsedOutput || '';
|
||||
if (!cliOutput && result.execution?.output) {
|
||||
cliOutput = typeof result.execution.output === 'string'
|
||||
? result.execution.output
|
||||
: result.execution.output.stdout || '';
|
||||
}
|
||||
|
||||
if (!cliOutput || cliOutput.trim().length === 0) {
|
||||
return { error: 'CLI returned empty output', status: 500 };
|
||||
|
||||
@@ -39,11 +39,32 @@ interface WatcherConfig {
|
||||
debounce_ms: number;
|
||||
}
|
||||
|
||||
interface PendingQueueStatus {
|
||||
file_count: number;
|
||||
files: string[];
|
||||
countdown_seconds: number;
|
||||
last_event_time: number | null;
|
||||
}
|
||||
|
||||
interface IndexResultDetail {
|
||||
files_indexed: number;
|
||||
files_removed: number;
|
||||
symbols_added: number;
|
||||
symbols_removed: number;
|
||||
files_success: string[];
|
||||
files_failed: string[];
|
||||
errors: string[];
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface WatcherStats {
|
||||
running: boolean;
|
||||
root_path: string;
|
||||
events_processed: number;
|
||||
start_time: Date | null;
|
||||
pending_queue: PendingQueueStatus | null;
|
||||
last_index_result: IndexResultDetail | null;
|
||||
index_history: IndexResultDetail[];
|
||||
}
|
||||
|
||||
interface ActiveWatcher {
|
||||
@@ -58,13 +79,12 @@ const WATCHER_CONFIG_FILE = path.join(WATCHER_CONFIG_DIR, 'watchers.json');
|
||||
// Active watchers Map: normalized_path -> { process, stats }
|
||||
const activeWatchers = new Map<string, ActiveWatcher>();
|
||||
|
||||
/**
|
||||
* Normalize path for consistent key usage
|
||||
* - Convert to absolute path
|
||||
|
||||
// Flag to ensure watchers are initialized only once
|
||||
let watchersInitialized = false;
|
||||
|
||||
/**
|
||||
* Normalize path for consistent key usage
|
||||
* - Convert to absolute path
|
||||
* - Convert to lowercase on Windows
|
||||
* - Use forward slashes
|
||||
*/
|
||||
@@ -183,7 +203,10 @@ async function startWatcherProcess(
|
||||
running: true,
|
||||
root_path: targetPath,
|
||||
events_processed: 0,
|
||||
start_time: new Date()
|
||||
start_time: new Date(),
|
||||
pending_queue: null,
|
||||
last_index_result: null,
|
||||
index_history: []
|
||||
};
|
||||
|
||||
// Register in activeWatchers Map
|
||||
@@ -201,16 +224,66 @@ async function startWatcherProcess(
|
||||
});
|
||||
}
|
||||
|
||||
// Handle process output for event counting
|
||||
// Handle process output for JSON parsing and event counting
|
||||
if (childProcess.stdout) {
|
||||
childProcess.stdout.on('data', (data: Buffer) => {
|
||||
const output = data.toString();
|
||||
const matches = output.match(/Processed \d+ events?/g);
|
||||
if (matches) {
|
||||
const watcher = activeWatchers.get(normalizedPath);
|
||||
if (watcher) {
|
||||
watcher.stats.events_processed += matches.length;
|
||||
const watcher = activeWatchers.get(normalizedPath);
|
||||
if (!watcher) return;
|
||||
|
||||
// Process output line by line for reliable JSON parsing
|
||||
// (handles nested arrays/objects that simple regex can't match)
|
||||
const lines = output.split('\n');
|
||||
let hasIndexResult = false;
|
||||
|
||||
for (const line of lines) {
|
||||
// Parse [QUEUE_STATUS] JSON
|
||||
if (line.includes('[QUEUE_STATUS]')) {
|
||||
const jsonStart = line.indexOf('{');
|
||||
if (jsonStart !== -1) {
|
||||
try {
|
||||
const queueStatus: PendingQueueStatus = JSON.parse(line.slice(jsonStart));
|
||||
watcher.stats.pending_queue = queueStatus;
|
||||
broadcastToClients({
|
||||
type: 'CODEXLENS_WATCHER_QUEUE_UPDATE',
|
||||
payload: { path: targetPath, queue: queueStatus }
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[CodexLens] Failed to parse queue status:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse [INDEX_RESULT] JSON
|
||||
if (line.includes('[INDEX_RESULT]')) {
|
||||
const jsonStart = line.indexOf('{');
|
||||
if (jsonStart !== -1) {
|
||||
try {
|
||||
const indexResult: IndexResultDetail = JSON.parse(line.slice(jsonStart));
|
||||
watcher.stats.last_index_result = indexResult;
|
||||
watcher.stats.index_history.push(indexResult);
|
||||
if (watcher.stats.index_history.length > 10) {
|
||||
watcher.stats.index_history.shift();
|
||||
}
|
||||
watcher.stats.events_processed += indexResult.files_indexed + indexResult.files_removed;
|
||||
watcher.stats.pending_queue = null;
|
||||
hasIndexResult = true;
|
||||
|
||||
broadcastToClients({
|
||||
type: 'CODEXLENS_WATCHER_INDEX_COMPLETE',
|
||||
payload: { path: targetPath, result: indexResult }
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[CodexLens] Failed to parse index result:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy event counting (fallback)
|
||||
const matches = output.match(/Processed \d+ events?/g);
|
||||
if (matches && !hasIndexResult) {
|
||||
watcher.stats.events_processed += matches.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2111,6 +2184,68 @@ export async function handleCodexLensRoutes(ctx: RouteContext): Promise<boolean>
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Get Pending Queue Status
|
||||
if (pathname === '/api/codexlens/watch/queue' && req.method === 'GET') {
|
||||
const queryPath = url.searchParams.get('path');
|
||||
const targetPath = queryPath || initialPath;
|
||||
const normalizedPath = normalizePath(targetPath);
|
||||
const watcher = activeWatchers.get(normalizedPath);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
queue: watcher?.stats.pending_queue || { file_count: 0, files: [], countdown_seconds: 0, last_event_time: null }
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Flush Pending Queue (Immediate Index)
|
||||
if (pathname === '/api/codexlens/watch/flush' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
const { path: watchPath } = body;
|
||||
const targetPath = watchPath || initialPath;
|
||||
const normalizedPath = normalizePath(targetPath);
|
||||
|
||||
const watcher = activeWatchers.get(normalizedPath);
|
||||
if (!watcher) {
|
||||
return { success: false, error: 'Watcher not running for this path', status: 400 };
|
||||
}
|
||||
|
||||
try {
|
||||
// Create flush.signal file to trigger immediate indexing
|
||||
const signalDir = path.join(targetPath, '.codexlens');
|
||||
const signalFile = path.join(signalDir, 'flush.signal');
|
||||
|
||||
if (!fs.existsSync(signalDir)) {
|
||||
fs.mkdirSync(signalDir, { recursive: true });
|
||||
}
|
||||
fs.writeFileSync(signalFile, Date.now().toString());
|
||||
|
||||
return { success: true, message: 'Flush signal sent' };
|
||||
} catch (err: any) {
|
||||
return { success: false, error: err.message, status: 500 };
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Get Index History
|
||||
if (pathname === '/api/codexlens/watch/history' && req.method === 'GET') {
|
||||
const queryPath = url.searchParams.get('path');
|
||||
const limitParam = url.searchParams.get('limit');
|
||||
const limit = limitParam ? parseInt(limitParam, 10) : 10;
|
||||
const targetPath = queryPath || initialPath;
|
||||
const normalizedPath = normalizePath(targetPath);
|
||||
const watcher = activeWatchers.get(normalizedPath);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
history: watcher?.stats.index_history?.slice(-limit) || []
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -392,10 +392,11 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
category: 'insight'
|
||||
});
|
||||
|
||||
// Try to parse JSON from response
|
||||
// Try to parse JSON from response - use parsedOutput (extracted text) instead of raw stdout
|
||||
let insights: { patterns: any[]; suggestions: any[] } = { patterns: [], suggestions: [] };
|
||||
if (result.stdout) {
|
||||
let outputText = result.stdout;
|
||||
const cliOutput = result.parsedOutput || result.stdout || '';
|
||||
if (cliOutput) {
|
||||
let outputText = cliOutput;
|
||||
|
||||
// Strip markdown code blocks if present
|
||||
const codeBlockMatch = outputText.match(/```(?:json)?\s*([\s\S]*?)```/);
|
||||
@@ -415,14 +416,14 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
console.error('[insights/analyze] JSON parse error:', e);
|
||||
// Return raw output if JSON parse fails
|
||||
insights = {
|
||||
patterns: [{ type: 'raw_analysis', description: result.stdout.substring(0, 500), occurrences: 1, severity: 'low', suggestion: '' }],
|
||||
patterns: [{ type: 'raw_analysis', description: cliOutput.substring(0, 500), occurrences: 1, severity: 'low', suggestion: '' }],
|
||||
suggestions: []
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// No JSON found, wrap raw output
|
||||
insights = {
|
||||
patterns: [{ type: 'raw_analysis', description: result.stdout.substring(0, 500), occurrences: 1, severity: 'low', suggestion: '' }],
|
||||
patterns: [{ type: 'raw_analysis', description: cliOutput.substring(0, 500), occurrences: 1, severity: 'low', suggestion: '' }],
|
||||
suggestions: []
|
||||
};
|
||||
}
|
||||
@@ -439,7 +440,7 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
promptCount: prompts.length,
|
||||
patterns: insights.patterns,
|
||||
suggestions: insights.suggestions,
|
||||
rawOutput: result.stdout || '',
|
||||
rawOutput: cliOutput,
|
||||
executionId: result.execution?.id,
|
||||
lang
|
||||
});
|
||||
@@ -981,23 +982,26 @@ RULES: Be concise. Focus on practical understanding. Include function signatures
|
||||
id: syncId
|
||||
});
|
||||
|
||||
if (result.success && result.execution?.output) {
|
||||
// Extract stdout from output object with proper serialization
|
||||
const output = result.execution.output;
|
||||
if (typeof output === 'string') {
|
||||
cliOutput = output;
|
||||
} else if (output && typeof output === 'object') {
|
||||
// Handle object output - extract stdout or serialize the object
|
||||
if (output.stdout && typeof output.stdout === 'string') {
|
||||
cliOutput = output.stdout;
|
||||
} else if (output.stderr && typeof output.stderr === 'string') {
|
||||
cliOutput = output.stderr;
|
||||
} else {
|
||||
// Last resort: serialize the entire object as JSON
|
||||
cliOutput = JSON.stringify(output, null, 2);
|
||||
if (result.success) {
|
||||
// Prefer parsedOutput (extracted text from stream JSON) over raw execution output
|
||||
if (result.parsedOutput) {
|
||||
cliOutput = result.parsedOutput;
|
||||
} else if (result.execution?.output) {
|
||||
// Fallback to execution.output
|
||||
const output = result.execution.output;
|
||||
if (typeof output === 'string') {
|
||||
cliOutput = output;
|
||||
} else if (output && typeof output === 'object') {
|
||||
// Handle object output - extract stdout or serialize the object
|
||||
if (output.stdout && typeof output.stdout === 'string') {
|
||||
cliOutput = output.stdout;
|
||||
} else if (output.stderr && typeof output.stderr === 'string') {
|
||||
cliOutput = output.stderr;
|
||||
} else {
|
||||
// Last resort: serialize the entire object as JSON
|
||||
cliOutput = JSON.stringify(output, null, 2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cliOutput = '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -221,6 +221,310 @@ function deleteRule(ruleName, location, projectPath) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer rule context from file name and subdirectory for better prompt generation
|
||||
* @param {string} fileName - Rule file name
|
||||
* @param {string} subdirectory - Optional subdirectory path
|
||||
* @param {string} location - 'project' or 'user'
|
||||
* @returns {Object} Inferred context
|
||||
*/
|
||||
function inferRuleContext(fileName: string, subdirectory: string, location: string) {
|
||||
const normalizedName = fileName.replace(/\.md$/i, '').toLowerCase();
|
||||
const normalizedSubdir = (subdirectory || '').toLowerCase();
|
||||
|
||||
// Rule category inference from file name and subdirectory
|
||||
const categories = {
|
||||
coding: ['coding', 'code', 'style', 'format', 'lint', 'convention'],
|
||||
testing: ['test', 'spec', 'jest', 'vitest', 'mocha', 'coverage'],
|
||||
security: ['security', 'auth', 'permission', 'access', 'secret', 'credential'],
|
||||
architecture: ['arch', 'design', 'pattern', 'structure', 'module', 'layer'],
|
||||
documentation: ['doc', 'comment', 'readme', 'jsdoc', 'api-doc'],
|
||||
performance: ['perf', 'performance', 'optimize', 'cache', 'memory'],
|
||||
workflow: ['workflow', 'ci', 'cd', 'deploy', 'build', 'release'],
|
||||
tooling: ['tool', 'cli', 'script', 'npm', 'yarn', 'pnpm'],
|
||||
error: ['error', 'exception', 'handling', 'logging', 'debug']
|
||||
};
|
||||
|
||||
let inferredCategory = 'general';
|
||||
let inferredKeywords: string[] = [];
|
||||
|
||||
for (const [category, keywords] of Object.entries(categories)) {
|
||||
for (const keyword of keywords) {
|
||||
if (normalizedName.includes(keyword) || normalizedSubdir.includes(keyword)) {
|
||||
inferredCategory = category;
|
||||
inferredKeywords = keywords;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (inferredCategory !== 'general') break;
|
||||
}
|
||||
|
||||
// Scope inference from location
|
||||
const scopeHint = location === 'project'
|
||||
? 'This rule applies to the current project only'
|
||||
: 'This rule applies globally to all projects';
|
||||
|
||||
// Technology hints from file name
|
||||
const techPatterns = {
|
||||
typescript: ['ts', 'typescript', 'tsc'],
|
||||
javascript: ['js', 'javascript', 'node'],
|
||||
react: ['react', 'jsx', 'tsx', 'component'],
|
||||
vue: ['vue', 'vuex', 'pinia'],
|
||||
python: ['python', 'py', 'pip', 'poetry'],
|
||||
rust: ['rust', 'cargo', 'rs'],
|
||||
go: ['go', 'golang', 'mod'],
|
||||
java: ['java', 'maven', 'gradle', 'spring']
|
||||
};
|
||||
|
||||
let inferredTech: string | null = null;
|
||||
for (const [tech, patterns] of Object.entries(techPatterns)) {
|
||||
if (patterns.some(p => normalizedName.includes(p) || normalizedSubdir.includes(p))) {
|
||||
inferredTech = tech;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
category: inferredCategory,
|
||||
keywords: inferredKeywords,
|
||||
scopeHint,
|
||||
technology: inferredTech,
|
||||
isConditional: normalizedSubdir.length > 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build structured prompt for rule generation
|
||||
* @param {Object} params
|
||||
* @returns {string} Structured prompt
|
||||
*/
|
||||
function buildStructuredRulePrompt(params: {
|
||||
description: string;
|
||||
fileName: string;
|
||||
subdirectory: string;
|
||||
location: string;
|
||||
context: ReturnType<typeof inferRuleContext>;
|
||||
enableReview?: boolean;
|
||||
}) {
|
||||
const { description, fileName, subdirectory, location, context, enableReview } = params;
|
||||
|
||||
// Build category-specific guidance
|
||||
const categoryGuidance = {
|
||||
coding: 'Focus on code style, naming conventions, and formatting rules. Include specific examples of correct and incorrect patterns.',
|
||||
testing: 'Emphasize test structure, coverage expectations, mocking strategies, and assertion patterns.',
|
||||
security: 'Highlight security best practices, input validation, authentication requirements, and sensitive data handling.',
|
||||
architecture: 'Define module boundaries, dependency rules, layer responsibilities, and design pattern usage.',
|
||||
documentation: 'Specify documentation requirements, comment styles, and API documentation standards.',
|
||||
performance: 'Address caching strategies, optimization guidelines, resource management, and performance metrics.',
|
||||
workflow: 'Define CI/CD requirements, deployment procedures, and release management rules.',
|
||||
tooling: 'Specify tool configurations, script conventions, and dependency management rules.',
|
||||
error: 'Define error handling patterns, logging requirements, and exception management.',
|
||||
general: 'Provide clear, actionable guidelines that Claude can follow consistently.'
|
||||
};
|
||||
|
||||
const guidance = categoryGuidance[context.category] || categoryGuidance.general;
|
||||
|
||||
// Build technology-specific hint
|
||||
const techHint = context.technology
|
||||
? `\nTECHNOLOGY CONTEXT: This rule is for ${context.technology} development. Use ${context.technology}-specific best practices and terminology.`
|
||||
: '';
|
||||
|
||||
// Build subdirectory context
|
||||
const subdirHint = subdirectory
|
||||
? `\nORGANIZATION: This rule will be placed in the "${subdirectory}" subdirectory, indicating its category/scope.`
|
||||
: '';
|
||||
|
||||
// Build review instruction if enabled
|
||||
const reviewInstruction = enableReview
|
||||
? `\n\nAFTER GENERATION:
|
||||
- Verify the rule is specific and actionable
|
||||
- Check for ambiguous language that could be misinterpreted
|
||||
- Ensure examples are clear and relevant
|
||||
- Validate markdown formatting is correct`
|
||||
: '';
|
||||
|
||||
return `PURPOSE: Generate a high-quality Claude Code memory rule that will guide Claude's behavior when working in this codebase
|
||||
SUCCESS CRITERIA: The rule must be (1) specific and actionable, (2) include concrete examples, (3) avoid ambiguous language, (4) follow Claude Code rule format
|
||||
|
||||
TASK:
|
||||
• Parse the user's description to identify core requirements
|
||||
• Infer additional context from file name "${fileName}" and category "${context.category}"
|
||||
• Generate structured markdown content with clear instructions
|
||||
• Include DO and DON'T examples where appropriate
|
||||
• ${context.isConditional ? 'Consider if frontmatter paths are needed for conditional activation' : 'Create as a global rule'}
|
||||
|
||||
MODE: write
|
||||
|
||||
RULE CATEGORY: ${context.category}
|
||||
CATEGORY GUIDANCE: ${guidance}
|
||||
${techHint}
|
||||
${subdirHint}
|
||||
SCOPE: ${context.scopeHint}
|
||||
|
||||
EXPECTED OUTPUT FORMAT:
|
||||
\`\`\`markdown
|
||||
${context.isConditional ? `---
|
||||
paths: [specific/path/patterns/**/*]
|
||||
---
|
||||
|
||||
` : ''}# Rule Title
|
||||
|
||||
Brief description of what this rule enforces.
|
||||
|
||||
## Guidelines
|
||||
|
||||
1. **First guideline** - Explanation
|
||||
2. **Second guideline** - Explanation
|
||||
|
||||
## Examples
|
||||
|
||||
### ✅ Correct
|
||||
\`\`\`language
|
||||
// Good example
|
||||
\`\`\`
|
||||
|
||||
### ❌ Incorrect
|
||||
\`\`\`language
|
||||
// Bad example
|
||||
\`\`\`
|
||||
|
||||
## Exceptions
|
||||
|
||||
- When this rule may not apply
|
||||
\`\`\`
|
||||
|
||||
USER DESCRIPTION:
|
||||
${description}
|
||||
|
||||
FILE NAME: ${fileName}
|
||||
${subdirectory ? `SUBDIRECTORY: ${subdirectory}` : ''}
|
||||
${reviewInstruction}
|
||||
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt) | Generate ONLY the rule content in markdown | No additional commentary | Do NOT use any tools | Output raw markdown text directly | write=CREATE`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build structured prompt for code extraction
|
||||
* @param {Object} params
|
||||
* @returns {string} Structured prompt
|
||||
*/
|
||||
function buildExtractPrompt(params: {
|
||||
extractScope: string;
|
||||
extractFocus: string;
|
||||
fileName: string;
|
||||
subdirectory: string;
|
||||
context: ReturnType<typeof inferRuleContext>;
|
||||
}) {
|
||||
const { extractScope, extractFocus, fileName, subdirectory, context } = params;
|
||||
|
||||
const scope = extractScope || '**/*';
|
||||
const focus = extractFocus || 'naming conventions, error handling, code structure, patterns';
|
||||
|
||||
return `PURPOSE: Extract and document coding conventions from the existing codebase to create a Claude Code memory rule
|
||||
SUCCESS CRITERIA: The rule must reflect ACTUAL patterns found in the code, not theoretical best practices
|
||||
|
||||
TASK:
|
||||
• Scan files matching "${scope}" for recurring patterns
|
||||
• Identify ${focus.split(',').length} or more distinct conventions
|
||||
• Document each pattern with real code examples from the codebase
|
||||
• Create actionable rules based on observed practices
|
||||
• Note any inconsistencies found (optional section)
|
||||
|
||||
MODE: analysis
|
||||
|
||||
ANALYSIS SCOPE: @${scope}
|
||||
FOCUS AREAS: ${focus}
|
||||
|
||||
EXTRACTION STRATEGY:
|
||||
1. **Pattern Recognition**: Look for repeated code structures, naming patterns, file organization
|
||||
2. **Consistency Check**: Identify which patterns are consistently followed vs. occasionally violated
|
||||
3. **Frequency Analysis**: Prioritize patterns that appear most frequently
|
||||
4. **Context Awareness**: Consider why certain patterns are used (performance, readability, etc.)
|
||||
|
||||
EXPECTED OUTPUT FORMAT:
|
||||
\`\`\`markdown
|
||||
# ${fileName.replace(/\.md$/i, '')} Conventions
|
||||
|
||||
Conventions extracted from codebase analysis of \`${scope}\`.
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
- **Pattern name**: Description with example
|
||||
\`\`\`language
|
||||
// Actual code from codebase
|
||||
\`\`\`
|
||||
|
||||
## Code Structure
|
||||
|
||||
- **Pattern name**: Description with example
|
||||
|
||||
## Error Handling
|
||||
|
||||
- **Pattern name**: Description with example
|
||||
|
||||
## Notes
|
||||
|
||||
- Any inconsistencies or variations observed
|
||||
\`\`\`
|
||||
|
||||
FILE NAME: ${fileName}
|
||||
${subdirectory ? `SUBDIRECTORY: ${subdirectory}` : ''}
|
||||
INFERRED CATEGORY: ${context.category}
|
||||
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | Extract REAL patterns from code | Include actual code snippets as examples | Do NOT use any tools | Output raw markdown text directly | analysis=READ-ONLY`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build review prompt for validating and improving generated rules
|
||||
* @param {string} content - Generated rule content to review
|
||||
* @param {string} fileName - Target file name
|
||||
* @param {Object} context - Inferred context
|
||||
* @returns {string} Review prompt
|
||||
*/
|
||||
function buildReviewPrompt(
|
||||
content: string,
|
||||
fileName: string,
|
||||
context: ReturnType<typeof inferRuleContext>
|
||||
) {
|
||||
return `PURPOSE: Review and improve a Claude Code memory rule for quality, clarity, and actionability
|
||||
SUCCESS CRITERIA: Output an improved version that is (1) more specific, (2) includes better examples, (3) has no ambiguous language
|
||||
|
||||
TASK:
|
||||
• Analyze the rule for clarity and specificity
|
||||
• Check if guidelines are actionable (Claude can follow them)
|
||||
• Verify examples are concrete and helpful
|
||||
• Remove any ambiguous or vague language
|
||||
• Ensure markdown formatting is correct
|
||||
• Improve structure if needed
|
||||
• Keep the core intent and requirements intact
|
||||
|
||||
MODE: write
|
||||
|
||||
REVIEW CRITERIA:
|
||||
1. **Specificity**: Each guideline should be specific enough to follow without interpretation
|
||||
2. **Actionability**: Guidelines should tell Claude exactly what to do or not do
|
||||
3. **Examples**: Good and bad examples should be clearly different and illustrative
|
||||
4. **Consistency**: Formatting and style should be consistent throughout
|
||||
5. **Completeness**: All necessary aspects of the rule should be covered
|
||||
6. **Conciseness**: No unnecessary verbosity or repetition
|
||||
|
||||
RULE CATEGORY: ${context.category}
|
||||
FILE NAME: ${fileName}
|
||||
|
||||
ORIGINAL RULE CONTENT:
|
||||
\`\`\`markdown
|
||||
${content}
|
||||
\`\`\`
|
||||
|
||||
EXPECTED OUTPUT:
|
||||
- Output ONLY the improved rule content in markdown format
|
||||
- Do NOT include any commentary, explanation, or meta-text
|
||||
- If the original is already high quality, return it unchanged
|
||||
- Preserve any frontmatter (---paths---) if present
|
||||
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt) | Output ONLY improved markdown content | No additional text | Do NOT use any tools | Output raw markdown text directly | write=CREATE`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate rule content via CLI tool
|
||||
* @param {Object} params
|
||||
@@ -233,6 +537,7 @@ function deleteRule(ruleName, location, projectPath) {
|
||||
* @param {string} params.location - 'project' or 'user'
|
||||
* @param {string} params.subdirectory - Optional subdirectory
|
||||
* @param {string} params.projectPath - Project root path
|
||||
* @param {boolean} params.enableReview - Optional: enable secondary review
|
||||
* @returns {Object}
|
||||
*/
|
||||
async function generateRuleViaCLI(params) {
|
||||
@@ -246,47 +551,47 @@ async function generateRuleViaCLI(params) {
|
||||
fileName,
|
||||
location,
|
||||
subdirectory,
|
||||
projectPath
|
||||
projectPath,
|
||||
enableReview
|
||||
} = params;
|
||||
|
||||
let prompt = '';
|
||||
let mode = 'analysis';
|
||||
let workingDir = projectPath;
|
||||
|
||||
// Infer context from file name and subdirectory
|
||||
const context = inferRuleContext(fileName, subdirectory || '', location);
|
||||
|
||||
// Build prompt based on generation type
|
||||
if (generationType === 'description') {
|
||||
mode = 'write';
|
||||
prompt = `PURPOSE: Generate Claude Code memory rule from description to guide Claude's behavior
|
||||
TASK: • Analyze rule requirements • Generate markdown content with clear instructions
|
||||
MODE: write
|
||||
EXPECTED: Complete rule content in markdown format
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt) | Follow Claude Code rule format | Use frontmatter for conditional rules if paths specified | write=CREATE
|
||||
|
||||
RULE DESCRIPTION:
|
||||
${description}
|
||||
|
||||
FILE NAME: ${fileName}`;
|
||||
prompt = buildStructuredRulePrompt({
|
||||
description,
|
||||
fileName,
|
||||
subdirectory: subdirectory || '',
|
||||
location,
|
||||
context,
|
||||
enableReview
|
||||
});
|
||||
} else if (generationType === 'template') {
|
||||
mode = 'write';
|
||||
prompt = `PURPOSE: Generate Claude Code rule from template type
|
||||
TASK: • Create rule based on ${templateType} template • Generate structured markdown content
|
||||
MODE: write
|
||||
EXPECTED: Complete rule content in markdown format following template structure
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt) | Follow Claude Code rule format | Use ${templateType} template patterns | write=CREATE
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt) | Follow Claude Code rule format | Use ${templateType} template patterns | Do NOT use any tools | Output raw markdown text directly | write=CREATE
|
||||
|
||||
TEMPLATE TYPE: ${templateType}
|
||||
FILE NAME: ${fileName}`;
|
||||
} else if (generationType === 'extract') {
|
||||
mode = 'analysis';
|
||||
prompt = `PURPOSE: Extract coding rules from existing codebase to document patterns and conventions
|
||||
TASK: • Analyze code patterns in specified scope • Extract common conventions • Identify best practices
|
||||
MODE: analysis
|
||||
CONTEXT: @${extractScope || '**/*'}
|
||||
EXPECTED: Rule content based on codebase analysis with examples
|
||||
RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) | Focus on actual patterns found | Include code examples | analysis=READ-ONLY
|
||||
|
||||
ANALYSIS SCOPE: ${extractScope || '**/*'}
|
||||
FOCUS AREAS: ${extractFocus || 'naming conventions, error handling, code structure'}`;
|
||||
prompt = buildExtractPrompt({
|
||||
extractScope: extractScope || '',
|
||||
extractFocus: extractFocus || '',
|
||||
fileName,
|
||||
subdirectory: subdirectory || '',
|
||||
context
|
||||
});
|
||||
} else {
|
||||
return { error: `Unknown generation type: ${generationType}` };
|
||||
}
|
||||
@@ -308,8 +613,15 @@ FOCUS AREAS: ${extractFocus || 'naming conventions, error handling, code structu
|
||||
};
|
||||
}
|
||||
|
||||
// Extract generated content from stdout
|
||||
const generatedContent = result.stdout.trim();
|
||||
// Extract generated content - prefer parsedOutput (extracted text from stream JSON)
|
||||
let generatedContent = (result.parsedOutput || result.stdout || '').trim();
|
||||
|
||||
// Remove markdown code block wrapper if present (e.g., ```markdown...```)
|
||||
if (generatedContent.startsWith('```markdown')) {
|
||||
generatedContent = generatedContent.replace(/^```markdown\s*\n?/, '').replace(/\n?```\s*$/, '');
|
||||
} else if (generatedContent.startsWith('```')) {
|
||||
generatedContent = generatedContent.replace(/^```\w*\s*\n?/, '').replace(/\n?```\s*$/, '');
|
||||
}
|
||||
|
||||
if (!generatedContent) {
|
||||
return {
|
||||
@@ -319,6 +631,40 @@ FOCUS AREAS: ${extractFocus || 'naming conventions, error handling, code structu
|
||||
};
|
||||
}
|
||||
|
||||
// Optional review step - verify and improve the generated rule
|
||||
let reviewResult = null;
|
||||
if (enableReview) {
|
||||
const reviewPrompt = buildReviewPrompt(generatedContent, fileName, context);
|
||||
|
||||
const reviewExecution = await executeCliTool({
|
||||
tool: 'claude',
|
||||
prompt: reviewPrompt,
|
||||
mode: 'write',
|
||||
cd: workingDir,
|
||||
timeout: 300000, // 5 minutes for review
|
||||
category: 'internal'
|
||||
});
|
||||
|
||||
if (reviewExecution.success) {
|
||||
let reviewedContent = (reviewExecution.parsedOutput || reviewExecution.stdout || '').trim();
|
||||
// Remove markdown code block wrapper if present
|
||||
if (reviewedContent.startsWith('```markdown')) {
|
||||
reviewedContent = reviewedContent.replace(/^```markdown\s*\n?/, '').replace(/\n?```\s*$/, '');
|
||||
} else if (reviewedContent.startsWith('```')) {
|
||||
reviewedContent = reviewedContent.replace(/^```\w*\s*\n?/, '').replace(/\n?```\s*$/, '');
|
||||
}
|
||||
// Only use reviewed content if it's valid and different
|
||||
if (reviewedContent.length > 50 && reviewedContent !== generatedContent) {
|
||||
generatedContent = reviewedContent;
|
||||
reviewResult = {
|
||||
reviewed: true,
|
||||
originalLength: (result.parsedOutput || result.stdout || '').trim().length,
|
||||
reviewedLength: reviewedContent.length
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the rule using the generated content
|
||||
const createResult = await createRule({
|
||||
fileName,
|
||||
@@ -333,7 +679,8 @@ FOCUS AREAS: ${extractFocus || 'naming conventions, error handling, code structu
|
||||
success: createResult.success || false,
|
||||
...createResult,
|
||||
generatedContent,
|
||||
executionId: result.conversation?.id
|
||||
executionId: result.conversation?.id,
|
||||
review: reviewResult
|
||||
};
|
||||
} catch (error) {
|
||||
return { error: (error as Error).message };
|
||||
|
||||
@@ -483,7 +483,7 @@ Create a new Claude Code skill with the following specifications:
|
||||
if (!result.success) {
|
||||
return {
|
||||
error: `CLI generation failed: ${result.stderr || 'Unknown error'}`,
|
||||
stdout: result.stdout,
|
||||
stdout: result.parsedOutput || result.stdout,
|
||||
stderr: result.stderr
|
||||
};
|
||||
}
|
||||
@@ -493,7 +493,7 @@ Create a new Claude Code skill with the following specifications:
|
||||
if (!validation.valid) {
|
||||
return {
|
||||
error: `Generated skill is invalid: ${validation.errors.join(', ')}`,
|
||||
stdout: result.stdout,
|
||||
stdout: result.parsedOutput || result.stdout,
|
||||
stderr: result.stderr
|
||||
};
|
||||
}
|
||||
@@ -503,7 +503,7 @@ Create a new Claude Code skill with the following specifications:
|
||||
skillName: validation.skillInfo.name,
|
||||
location,
|
||||
path: targetPath,
|
||||
stdout: result.stdout,
|
||||
stdout: result.parsedOutput || result.stdout,
|
||||
stderr: result.stderr
|
||||
};
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user