mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-10 02:24:35 +08:00
feat: Add project guidelines support and enhance project overview rendering
This commit is contained in:
@@ -110,6 +110,34 @@ interface SessionReviewData {
|
|||||||
findings: Array<Finding & { dimension: string }>;
|
findings: Array<Finding & { dimension: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProjectGuidelines {
|
||||||
|
conventions: {
|
||||||
|
coding_style: string[];
|
||||||
|
naming_patterns: string[];
|
||||||
|
file_structure: string[];
|
||||||
|
documentation: string[];
|
||||||
|
};
|
||||||
|
constraints: {
|
||||||
|
architecture: string[];
|
||||||
|
tech_stack: string[];
|
||||||
|
performance: string[];
|
||||||
|
security: string[];
|
||||||
|
};
|
||||||
|
quality_rules: Array<{ rule: string; scope: string; enforced_by?: string }>;
|
||||||
|
learnings: Array<{
|
||||||
|
date: string;
|
||||||
|
session_id?: string;
|
||||||
|
insight: string;
|
||||||
|
context?: string;
|
||||||
|
category?: string;
|
||||||
|
}>;
|
||||||
|
_metadata?: {
|
||||||
|
created_at: string;
|
||||||
|
updated_at?: string;
|
||||||
|
version: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface ProjectOverview {
|
interface ProjectOverview {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
description: string;
|
description: string;
|
||||||
@@ -144,6 +172,7 @@ interface ProjectOverview {
|
|||||||
analysis_timestamp: string | null;
|
analysis_timestamp: string | null;
|
||||||
analysis_mode: string;
|
analysis_mode: string;
|
||||||
};
|
};
|
||||||
|
guidelines: ProjectGuidelines | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,11 +185,13 @@ export async function aggregateData(sessions: ScanSessionsResult, workflowDir: s
|
|||||||
// Initialize cache manager
|
// Initialize cache manager
|
||||||
const cache = createDashboardCache(workflowDir);
|
const cache = createDashboardCache(workflowDir);
|
||||||
|
|
||||||
// Prepare paths to watch for changes
|
// Prepare paths to watch for changes (includes both new dual files and legacy)
|
||||||
const watchPaths = [
|
const watchPaths = [
|
||||||
join(workflowDir, 'active'),
|
join(workflowDir, 'active'),
|
||||||
join(workflowDir, 'archives'),
|
join(workflowDir, 'archives'),
|
||||||
join(workflowDir, 'project.json'),
|
join(workflowDir, 'project-tech.json'),
|
||||||
|
join(workflowDir, 'project-guidelines.json'),
|
||||||
|
join(workflowDir, 'project.json'), // Legacy support
|
||||||
...sessions.active.map(s => s.path),
|
...sessions.active.map(s => s.path),
|
||||||
...sessions.archived.map(s => s.path)
|
...sessions.archived.map(s => s.path)
|
||||||
];
|
];
|
||||||
@@ -516,12 +547,19 @@ function sortTaskIds(a: string, b: string): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load project overview from project.json
|
* Load project overview from project-tech.json and project-guidelines.json
|
||||||
|
* Supports dual file structure with backward compatibility for legacy project.json
|
||||||
* @param workflowDir - Path to .workflow directory
|
* @param workflowDir - Path to .workflow directory
|
||||||
* @returns Project overview data or null if not found
|
* @returns Project overview data or null if not found
|
||||||
*/
|
*/
|
||||||
function loadProjectOverview(workflowDir: string): ProjectOverview | null {
|
function loadProjectOverview(workflowDir: string): ProjectOverview | null {
|
||||||
const projectFile = join(workflowDir, 'project.json');
|
const techFile = join(workflowDir, 'project-tech.json');
|
||||||
|
const guidelinesFile = join(workflowDir, 'project-guidelines.json');
|
||||||
|
const legacyFile = join(workflowDir, 'project.json');
|
||||||
|
|
||||||
|
// Check for new dual file structure first, fallback to legacy
|
||||||
|
const useLegacy = !existsSync(techFile) && existsSync(legacyFile);
|
||||||
|
const projectFile = useLegacy ? legacyFile : techFile;
|
||||||
|
|
||||||
if (!existsSync(projectFile)) {
|
if (!existsSync(projectFile)) {
|
||||||
console.log(`Project file not found at: ${projectFile}`);
|
console.log(`Project file not found at: ${projectFile}`);
|
||||||
@@ -532,15 +570,59 @@ function loadProjectOverview(workflowDir: string): ProjectOverview | null {
|
|||||||
const fileContent = readFileSync(projectFile, 'utf8');
|
const fileContent = readFileSync(projectFile, 'utf8');
|
||||||
const projectData = JSON.parse(fileContent) as Record<string, unknown>;
|
const projectData = JSON.parse(fileContent) as Record<string, unknown>;
|
||||||
|
|
||||||
console.log(`Successfully loaded project overview: ${projectData.project_name || 'Unknown'}`);
|
console.log(`Successfully loaded project overview: ${projectData.project_name || 'Unknown'} (${useLegacy ? 'legacy' : 'tech'})`);
|
||||||
|
|
||||||
|
// Parse tech data (compatible with both legacy and new structure)
|
||||||
const overview = projectData.overview as Record<string, unknown> | undefined;
|
const overview = projectData.overview as Record<string, unknown> | undefined;
|
||||||
const technologyStack = overview?.technology_stack as Record<string, unknown[]> | undefined;
|
const technologyAnalysis = projectData.technology_analysis as Record<string, unknown> | undefined;
|
||||||
const architecture = overview?.architecture as Record<string, unknown> | undefined;
|
const developmentStatus = projectData.development_status as Record<string, unknown> | undefined;
|
||||||
const developmentIndex = projectData.development_index as Record<string, unknown[]> | undefined;
|
|
||||||
const statistics = projectData.statistics as Record<string, unknown> | undefined;
|
// Support both old and new schema field names
|
||||||
|
const technologyStack = (overview?.technology_stack || technologyAnalysis?.technology_stack) as Record<string, unknown[]> | undefined;
|
||||||
|
const architecture = (overview?.architecture || technologyAnalysis?.architecture) as Record<string, unknown> | undefined;
|
||||||
|
const developmentIndex = (projectData.development_index || developmentStatus?.development_index) as Record<string, unknown[]> | undefined;
|
||||||
|
const statistics = (projectData.statistics || developmentStatus?.statistics) as Record<string, unknown> | undefined;
|
||||||
const metadata = projectData._metadata as Record<string, unknown> | undefined;
|
const metadata = projectData._metadata as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
// Load guidelines from separate file if exists
|
||||||
|
let guidelines: ProjectGuidelines | null = null;
|
||||||
|
if (existsSync(guidelinesFile)) {
|
||||||
|
try {
|
||||||
|
const guidelinesContent = readFileSync(guidelinesFile, 'utf8');
|
||||||
|
const guidelinesData = JSON.parse(guidelinesContent) as Record<string, unknown>;
|
||||||
|
|
||||||
|
const conventions = guidelinesData.conventions as Record<string, string[]> | undefined;
|
||||||
|
const constraints = guidelinesData.constraints as Record<string, string[]> | undefined;
|
||||||
|
|
||||||
|
guidelines = {
|
||||||
|
conventions: {
|
||||||
|
coding_style: conventions?.coding_style || [],
|
||||||
|
naming_patterns: conventions?.naming_patterns || [],
|
||||||
|
file_structure: conventions?.file_structure || [],
|
||||||
|
documentation: conventions?.documentation || []
|
||||||
|
},
|
||||||
|
constraints: {
|
||||||
|
architecture: constraints?.architecture || [],
|
||||||
|
tech_stack: constraints?.tech_stack || [],
|
||||||
|
performance: constraints?.performance || [],
|
||||||
|
security: constraints?.security || []
|
||||||
|
},
|
||||||
|
quality_rules: (guidelinesData.quality_rules as Array<{ rule: string; scope: string; enforced_by?: string }>) || [],
|
||||||
|
learnings: (guidelinesData.learnings as Array<{
|
||||||
|
date: string;
|
||||||
|
session_id?: string;
|
||||||
|
insight: string;
|
||||||
|
context?: string;
|
||||||
|
category?: string;
|
||||||
|
}>) || [],
|
||||||
|
_metadata: guidelinesData._metadata as ProjectGuidelines['_metadata'] | undefined
|
||||||
|
};
|
||||||
|
console.log(`Successfully loaded project guidelines`);
|
||||||
|
} catch (guidelinesErr) {
|
||||||
|
console.error(`Failed to parse project-guidelines.json:`, (guidelinesErr as Error).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projectName: (projectData.project_name as string) || 'Unknown',
|
projectName: (projectData.project_name as string) || 'Unknown',
|
||||||
description: (overview?.description as string) || '',
|
description: (overview?.description as string) || '',
|
||||||
@@ -574,10 +656,11 @@ function loadProjectOverview(workflowDir: string): ProjectOverview | null {
|
|||||||
initialized_by: (metadata?.initialized_by as string) || 'unknown',
|
initialized_by: (metadata?.initialized_by as string) || 'unknown',
|
||||||
analysis_timestamp: (metadata?.analysis_timestamp as string) || null,
|
analysis_timestamp: (metadata?.analysis_timestamp as string) || null,
|
||||||
analysis_mode: (metadata?.analysis_mode as string) || 'unknown'
|
analysis_mode: (metadata?.analysis_mode as string) || 'unknown'
|
||||||
}
|
},
|
||||||
|
guidelines
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to parse project.json at ${projectFile}:`, (err as Error).message);
|
console.error(`Failed to parse project file at ${projectFile}:`, (err as Error).message);
|
||||||
console.error('Error stack:', (err as Error).stack);
|
console.error('Error stack:', (err as Error).stack);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,9 @@ function renderProjectOverview() {
|
|||||||
${renderDevelopmentIndex(project.developmentIndex)}
|
${renderDevelopmentIndex(project.developmentIndex)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Guidelines -->
|
||||||
|
${renderProjectGuidelines(project.guidelines)}
|
||||||
|
|
||||||
<!-- Statistics -->
|
<!-- Statistics -->
|
||||||
<div class="bg-card border border-border rounded-lg p-6">
|
<div class="bg-card border border-border rounded-lg p-6">
|
||||||
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
|
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
|
||||||
@@ -248,3 +251,153 @@ function renderDevelopmentIndex(devIndex) {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderProjectGuidelines(guidelines) {
|
||||||
|
if (!guidelines) {
|
||||||
|
return `
|
||||||
|
<div class="bg-card border border-border rounded-lg p-6 mb-6">
|
||||||
|
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
|
||||||
|
<i data-lucide="scroll-text" class="w-5 h-5"></i> Project Guidelines
|
||||||
|
</h3>
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
No guidelines configured. Run <code class="px-2 py-1 bg-muted rounded text-xs font-mono">/session:solidify</code> to add project constraints and conventions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count total items
|
||||||
|
const conventionCount = Object.values(guidelines.conventions || {}).flat().length;
|
||||||
|
const constraintCount = Object.values(guidelines.constraints || {}).flat().length;
|
||||||
|
const rulesCount = (guidelines.quality_rules || []).length;
|
||||||
|
const learningsCount = (guidelines.learnings || []).length;
|
||||||
|
const totalCount = conventionCount + constraintCount + rulesCount + learningsCount;
|
||||||
|
|
||||||
|
if (totalCount === 0) {
|
||||||
|
return `
|
||||||
|
<div class="bg-card border border-border rounded-lg p-6 mb-6">
|
||||||
|
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
|
||||||
|
<i data-lucide="scroll-text" class="w-5 h-5"></i> Project Guidelines
|
||||||
|
</h3>
|
||||||
|
<p class="text-muted-foreground text-sm">
|
||||||
|
Guidelines file exists but is empty. Run <code class="px-2 py-1 bg-muted rounded text-xs font-mono">/session:solidify</code> to add project constraints and conventions.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="bg-card border border-border rounded-lg p-6 mb-6">
|
||||||
|
<h3 class="text-lg font-semibold text-foreground mb-4 flex items-center gap-2">
|
||||||
|
<i data-lucide="scroll-text" class="w-5 h-5"></i> Project Guidelines
|
||||||
|
<span class="text-xs px-2 py-0.5 bg-primary-light text-primary rounded-full">${totalCount} items</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Conventions -->
|
||||||
|
${renderGuidelinesSection('Conventions', guidelines.conventions, 'book-marked', 'bg-success-light text-success', [
|
||||||
|
{ key: 'coding_style', label: 'Coding Style' },
|
||||||
|
{ key: 'naming_patterns', label: 'Naming Patterns' },
|
||||||
|
{ key: 'file_structure', label: 'File Structure' },
|
||||||
|
{ key: 'documentation', label: 'Documentation' }
|
||||||
|
])}
|
||||||
|
|
||||||
|
<!-- Constraints -->
|
||||||
|
${renderGuidelinesSection('Constraints', guidelines.constraints, 'shield-alert', 'bg-destructive/10 text-destructive', [
|
||||||
|
{ key: 'architecture', label: 'Architecture' },
|
||||||
|
{ key: 'tech_stack', label: 'Tech Stack' },
|
||||||
|
{ key: 'performance', label: 'Performance' },
|
||||||
|
{ key: 'security', label: 'Security' }
|
||||||
|
])}
|
||||||
|
|
||||||
|
<!-- Quality Rules -->
|
||||||
|
${renderQualityRules(guidelines.quality_rules)}
|
||||||
|
|
||||||
|
<!-- Learnings -->
|
||||||
|
${renderLearnings(guidelines.learnings)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderGuidelinesSection(title, data, icon, badgeClass, categories) {
|
||||||
|
if (!data) return '';
|
||||||
|
|
||||||
|
const items = categories.flatMap(cat => (data[cat.key] || []).map(item => ({ category: cat.label, value: item })));
|
||||||
|
if (items.length === 0) return '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
|
||||||
|
<i data-lucide="${icon}" class="w-4 h-4"></i>
|
||||||
|
<span>${title}</span>
|
||||||
|
<span class="text-xs px-2 py-0.5 ${badgeClass} rounded-full">${items.length}</span>
|
||||||
|
</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
${items.slice(0, 8).map(item => `
|
||||||
|
<div class="flex items-start gap-3 p-3 bg-background border border-border rounded-lg">
|
||||||
|
<span class="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded whitespace-nowrap">${escapeHtml(item.category)}</span>
|
||||||
|
<span class="text-sm text-foreground">${escapeHtml(item.value)}</span>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
${items.length > 8 ? `<div class="text-sm text-muted-foreground text-center py-2">... and ${items.length - 8} more</div>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderQualityRules(rules) {
|
||||||
|
if (!rules || rules.length === 0) return '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
|
||||||
|
<i data-lucide="check-square" class="w-4 h-4"></i>
|
||||||
|
<span>Quality Rules</span>
|
||||||
|
<span class="text-xs px-2 py-0.5 bg-warning-light text-warning rounded-full">${rules.length}</span>
|
||||||
|
</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
${rules.slice(0, 6).map(rule => `
|
||||||
|
<div class="p-3 bg-background border border-border rounded-lg">
|
||||||
|
<div class="flex items-start justify-between mb-1">
|
||||||
|
<span class="text-sm text-foreground font-medium">${escapeHtml(rule.rule)}</span>
|
||||||
|
${rule.enforced_by ? `<span class="text-xs px-2 py-0.5 bg-muted text-muted-foreground rounded">${escapeHtml(rule.enforced_by)}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
<span class="text-xs text-muted-foreground">Scope: ${escapeHtml(rule.scope)}</span>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
${rules.length > 6 ? `<div class="text-sm text-muted-foreground text-center py-2">... and ${rules.length - 6} more</div>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLearnings(learnings) {
|
||||||
|
if (!learnings || learnings.length === 0) return '';
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div>
|
||||||
|
<h4 class="text-sm font-semibold text-foreground mb-3 flex items-center gap-2">
|
||||||
|
<i data-lucide="lightbulb" class="w-4 h-4"></i>
|
||||||
|
<span>Session Learnings</span>
|
||||||
|
<span class="text-xs px-2 py-0.5 bg-accent text-accent-foreground rounded-full">${learnings.length}</span>
|
||||||
|
</h4>
|
||||||
|
<div class="space-y-2">
|
||||||
|
${learnings.slice(0, 5).map(learning => `
|
||||||
|
<div class="p-3 bg-background border border-border rounded-lg border-l-4 border-l-primary">
|
||||||
|
<div class="flex items-start justify-between mb-2">
|
||||||
|
<span class="text-sm text-foreground">${escapeHtml(learning.insight)}</span>
|
||||||
|
<span class="text-xs text-muted-foreground whitespace-nowrap ml-2">${formatDate(learning.date)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-xs">
|
||||||
|
${learning.category ? `<span class="px-2 py-0.5 bg-muted text-muted-foreground rounded">${escapeHtml(learning.category)}</span>` : ''}
|
||||||
|
${learning.session_id ? `<span class="px-2 py-0.5 bg-primary-light text-primary rounded font-mono">${escapeHtml(learning.session_id)}</span>` : ''}
|
||||||
|
</div>
|
||||||
|
${learning.context ? `<p class="text-xs text-muted-foreground mt-2">${escapeHtml(learning.context)}</p>` : ''}
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
${learnings.length > 5 ? `<div class="text-sm text-muted-foreground text-center py-2">... and ${learnings.length - 5} more</div>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ src/codexlens/semantic/vector_store.py
|
|||||||
src/codexlens/storage/__init__.py
|
src/codexlens/storage/__init__.py
|
||||||
src/codexlens/storage/dir_index.py
|
src/codexlens/storage/dir_index.py
|
||||||
src/codexlens/storage/file_cache.py
|
src/codexlens/storage/file_cache.py
|
||||||
|
src/codexlens/storage/global_index.py
|
||||||
src/codexlens/storage/index_tree.py
|
src/codexlens/storage/index_tree.py
|
||||||
src/codexlens/storage/migration_manager.py
|
src/codexlens/storage/migration_manager.py
|
||||||
src/codexlens/storage/path_mapper.py
|
src/codexlens/storage/path_mapper.py
|
||||||
@@ -64,6 +65,8 @@ tests/test_enrichment.py
|
|||||||
tests/test_entities.py
|
tests/test_entities.py
|
||||||
tests/test_errors.py
|
tests/test_errors.py
|
||||||
tests/test_file_cache.py
|
tests/test_file_cache.py
|
||||||
|
tests/test_global_index.py
|
||||||
|
tests/test_global_symbol_index.py
|
||||||
tests/test_hybrid_chunker.py
|
tests/test_hybrid_chunker.py
|
||||||
tests/test_hybrid_search_e2e.py
|
tests/test_hybrid_search_e2e.py
|
||||||
tests/test_incremental_indexing.py
|
tests/test_incremental_indexing.py
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-code-workflow",
|
"name": "claude-code-workflow",
|
||||||
"version": "6.3.9",
|
"version": "6.3.10",
|
||||||
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "ccw/src/index.js",
|
"main": "ccw/src/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user