diff --git a/CHANGELOG.md b/CHANGELOG.md index c6fe5c5..c73745c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## v4 — Tooling Improvements + +- Stronger auto-fix engine with detailed skip reporting +- Monorepo-aware package summaries in CLI and PR comments +- HTML report improvements +- Conservative rules: no-debugger, no-empty-catch, no-useless-return +- Diagnostics-backed analysis via ts-diagnostics rule +- Richer complexity warnings with per-function contributor breakdown +- Export workflow improvements and human summary quality enforcement +- Lightweight repo-pack-latest export mode +- Deploy readiness improvements for the web UI + ## v3 — Platform - Custom rule authoring API (`defineRule()`) diff --git a/ai/scripts/generate-repomix-exports.ts b/ai/scripts/generate-repomix-exports.ts index 1a751e7..962b37c 100644 --- a/ai/scripts/generate-repomix-exports.ts +++ b/ai/scripts/generate-repomix-exports.ts @@ -147,20 +147,20 @@ function categorizeFiles(files: string[]): Map { return groups; } -// Polished, outcome-focused bullet templates per area +// Polished, outcome-focused bullet templates per area (≥8 words, verb-first) const AREA_BULLET_TEMPLATES: Record = { - core: 'Improved the core analysis engine for better detection accuracy', - cli: 'Enhanced the CLI tool for a smoother command-line experience', - shared: 'Refined shared type definitions across packages', - 'vscode-extension': 'Enhanced the VS Code extension for a better in-editor experience', - web: 'Polished the web UI for a more intuitive analysis workflow', - docs: 'Kept documentation aligned with the latest codebase changes', - ai: 'Improved milestone export generation so summaries are cleaner and more reliable', - workflow: 'Strengthened CI/CD pipeline for more reliable automated checks', - examples: 'Updated example fixtures to reflect current rule coverage', - screenshots: 'Refreshed screenshots and demo automation', - root: 'Updated root project configuration for consistency', - other: 'Improved project tooling and configuration', + core: 'Expanded rule coverage with stronger detection accuracy and richer diagnostics', + cli: 'Enhanced the command-line interface for a smoother developer experience', + shared: 'Refined shared type definitions to improve consistency across all packages', + 'vscode-extension': 'Improved the VS Code extension for faster in-editor feedback', + web: 'Polished the web interface for a more intuitive analysis workflow', + docs: 'Updated documentation to reflect the latest codebase improvements and conventions', + ai: 'Strengthened the export workflow so summaries are cleaner and more reliable', + workflow: 'Improved the CI pipeline to catch more issues before code ships', + examples: 'Updated example fixtures to demonstrate current rule coverage and patterns', + screenshots: 'Refreshed screenshots and demo automation for accurate visual documentation', + root: 'Updated root project configuration for better workspace consistency', + other: 'Improved project tooling and developer workflow configuration', }; // Banned patterns — bullets containing any of these are considered noisy/internal @@ -283,6 +283,22 @@ function formatGroupedFiles(files: string[]): string { return lines.join('\n').trim(); } +/** Check whether a bullet meets quality standards for human-readable output. */ +function isQualityBullet(bullet: string): boolean { + const trimmed = bullet.trim(); + // Must start with a verb (capital letter followed by lowercase) + if (!/^[A-Z][a-z]/.test(trimmed)) return false; + // Must be at least 8 words + if (trimmed.split(/\s+/).length < 8) return false; + // Must not contain file names (e.g. foo.ts, bar.tsx, baz.md) + if (/\b\w+\.\w{1,4}\b/.test(trimmed) && /\.(ts|tsx|js|jsx|md|json|yml|yaml|css|html)/.test(trimmed)) return false; + // Must not contain colon-prefixed commit text (e.g. "feat: ...") + if (/^[a-z]+(\([^)]*\))?:/i.test(trimmed)) return false; + // Must not contain backticks + if (trimmed.includes('`')) return false; + return true; +} + /** Validate that all bullets pass quality rules. Returns list of failing bullets. */ function validateBullets(bullets: string[]): string[] { const failures: string[] = []; @@ -290,6 +306,7 @@ function validateBullets(bullets: string[]): string[] { if (isBannedBullet(b)) failures.push(`BANNED: "${b}"`); if (b.trim().length === 0) failures.push('EMPTY bullet'); if (isTruncatedBullet(b)) failures.push(`TRUNCATED: "${b}"`); + if (!isQualityBullet(b)) failures.push(`LOW_QUALITY: "${b}"`); } // Check duplicates const seen = new Set(); @@ -344,7 +361,7 @@ function generateHumanSummary(pr: PRInfo, files: string[], _commits: string): st let bullets = candidates.slice(0, 5); // Remove any that still fail validation individually - bullets = bullets.filter(b => !isBannedBullet(b) && b.trim().length > 0 && !isTruncatedBullet(b)); + bullets = bullets.filter(b => !isBannedBullet(b) && b.trim().length > 0 && !isTruncatedBullet(b) && isQualityBullet(b)); // Deduplicate (case-insensitive) const deduped: string[] = []; @@ -363,9 +380,9 @@ function generateHumanSummary(pr: PRInfo, files: string[], _commits: string): st bullets = buildAreaBullets(files); // Always ensure at least 3 const fallbacks = [ - 'Improved milestone export generation so summaries are cleaner and more reliable', - 'Kept workflow documentation aligned with the automated export pipeline', - 'Strengthened export quality checks so only clean, polished summaries are produced', + 'Strengthened the export workflow so summaries are cleaner and more reliable', + 'Updated documentation to reflect the latest codebase improvements and conventions', + 'Improved developer feedback with clearer diagnostics and auto-fix reporting', ]; for (const fb of fallbacks) { if (bullets.length >= 3) break; @@ -435,7 +452,7 @@ function validateSummaryContent(content: string): void { process.exit(1); } - // Check each bullet against banned patterns and truncation + // Check each bullet against banned patterns, truncation, and quality for (const bullet of bulletLines) { if (bullet.length === 0) { console.error('\nERROR: Human Summary contains an empty bullet'); @@ -449,6 +466,10 @@ function validateSummaryContent(content: string): void { console.error(`\nERROR: Human Summary bullet appears truncated: "${bullet}"`); process.exit(1); } + if (!isQualityBullet(bullet)) { + console.error(`\nERROR: Human Summary bullet fails quality check (must start with verb, ≥8 words, no file names, no commit prefixes): "${bullet}"`); + process.exit(1); + } } // Check for duplicate bullets @@ -630,9 +651,9 @@ if (bulletErrors.length > 0) { humanBullets = buildAreaBullets(milestoneFiles); // Ensure 3–5 range const fallbacks = [ - 'Improved milestone export generation so summaries are cleaner and more reliable', - 'Kept workflow documentation aligned with the automated export pipeline', - 'Strengthened export quality checks so only clean, polished summaries are produced', + 'Strengthened the export workflow so summaries are cleaner and more reliable', + 'Updated documentation to reflect the latest codebase improvements and conventions', + 'Improved developer feedback with clearer diagnostics and auto-fix reporting', ]; for (const fb of fallbacks) { if (humanBullets.length >= 3) break; diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 867e9db..ffb2f36 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -14,6 +14,7 @@ export function App() { handleAnalyze, selectIssue, exportMarkdown, + loadSampleProject, } = useAppState(); return ( @@ -39,6 +40,7 @@ export function App() { report={state.report} selectedIssue={state.selectedIssue} onSelectIssue={selectIssue} + onLoadSample={loadSampleProject} /> diff --git a/apps/web/src/components/MainPanel.tsx b/apps/web/src/components/MainPanel.tsx index 0f2f4b9..4a2b622 100644 --- a/apps/web/src/components/MainPanel.tsx +++ b/apps/web/src/components/MainPanel.tsx @@ -5,6 +5,7 @@ interface MainPanelProps { report: AnalysisReport | null; selectedIssue: Issue | null; onSelectIssue: (issue: Issue | null) => void; + onLoadSample: () => void; } const SEVERITY_LABELS: Record = { @@ -19,11 +20,13 @@ const SEVERITY_COLORS: Record = { info: '#2196f3', }; -export function MainPanel({ report, selectedIssue, onSelectIssue }: MainPanelProps) { +export function MainPanel({ report, selectedIssue, onSelectIssue, onLoadSample }: MainPanelProps) { const [filter, setFilter] = useState('all'); const [search, setSearch] = useState(''); const [expandedId, setExpandedId] = useState(null); + const hasFolderPicker = typeof window !== 'undefined' && 'showDirectoryPicker' in window; + if (!report) { return (
@@ -35,11 +38,22 @@ export function MainPanel({ report, selectedIssue, onSelectIssue }: MainPanelPro code quality improvements — with proposed fixes, severity scoring, and exportable reports.

- Select a folder and click Analyze to get started. + How to run an analysis: Click Select Folder (Chrome/Edge) + or Upload Folder (any browser) to load a project. Pick directories in the + sidebar, then click Analyze.

+ {!hasFolderPicker && ( +

+ This browser does not support file system analysis features. + Use Chrome or Edge for the best experience, or use the Upload Folder fallback. +

+ )}

- This UI is currently in Preview — features are under active development. + This app is currently in Preview — features are under active development.

+
diff --git a/apps/web/src/useAppState.ts b/apps/web/src/useAppState.ts index 08cf944..6bbb1e8 100644 --- a/apps/web/src/useAppState.ts +++ b/apps/web/src/useAppState.ts @@ -4,6 +4,55 @@ import { buildDirectoryTree, pickDefaultDirs, analyzeCodebase, buildMarkdownRepo import type { DirEntry } from '@inspectorepo/core'; import { selectFolderViaAPI, readUploadedFiles, processFiles } from './folder-reader'; +// Small inline sample for "Try with sample project" +const SAMPLE_FILES: VirtualFile[] = [ + { + path: 'src/utils.ts', + content: [ + 'import { Logger } from "./logger";', + '', + 'export function getUser(data: any) {', + ' if (data && data.user && data.user.name) {', + ' return data.user.name;', + ' }', + ' return null;', + '}', + '', + 'export function isActive(flag: boolean) {', + ' if (flag === true) {', + ' return true;', + ' }', + ' return false;', + '}', + ].join('\n'), + }, + { + path: 'src/process.ts', + content: [ + 'export function processItems(items: number[]) {', + ' if (items.length > 0) {', + ' for (let i = 0; i < items.length; i++) {', + ' if (items[i] > 0) {', + ' if (items[i] > 10) {', + ' for (let j = 0; j < items[i]; j++) {', + ' if (j % 2 === 0) {', + ' const val = j > 5 ? j * 2 : j;', + ' if (val > 3 && val < 100 || val === 0) {', + ' console.log(val);', + ' }', + ' }', + ' }', + ' }', + ' }', + ' }', + ' }', + ' debugger;', + ' return;', + '}', + ].join('\n'), + }, +]; + export interface AppState { folderName: string | null; allFiles: VirtualFile[]; @@ -116,6 +165,10 @@ export function useAppState() { const canAnalyze = state.folderName !== null && state.selectedDirs.length > 0; + const loadSampleProject = useCallback(() => { + loadFolder('sample-project', SAMPLE_FILES); + }, [loadFolder]); + // Dev-only: expose loader for E2E / screenshot automation useEffect(() => { if (import.meta.env.DEV) { @@ -132,5 +185,6 @@ export function useAppState() { handleAnalyze, selectIssue, exportMarkdown, + loadSampleProject, }; } diff --git a/packages/core/src/analyzer.test.ts b/packages/core/src/analyzer.test.ts index 79a3805..61b09b0 100644 --- a/packages/core/src/analyzer.test.ts +++ b/packages/core/src/analyzer.test.ts @@ -262,7 +262,7 @@ describe('complexity-hotspot rule', () => { expect(issue.suggestion.details).toMatch(/if statement/); expect(issue.suggestion.details).toMatch(/Complexity score: \d+/); // Suggestion should be specific, not generic - expect(issue.suggestion.summary).toMatch(/Consider:/); + expect(issue.suggestion.summary).toMatch(/Suggested improvements:/); }); it('includes contributor counts in details', () => { diff --git a/packages/core/src/config.test.ts b/packages/core/src/config.test.ts index ebd5fcb..b957b07 100644 --- a/packages/core/src/config.test.ts +++ b/packages/core/src/config.test.ts @@ -38,6 +38,10 @@ describe('mergeConfig', () => { const result = mergeConfig(null); expect(result['unused-imports']).toBe('warn'); expect(result['early-return']).toBe('warn'); + expect(result['no-debugger']).toBe('warn'); + expect(result['no-empty-catch']).toBe('warn'); + expect(result['no-useless-return']).toBe('warn'); + expect(result['ts-diagnostics']).toBe('off'); }); it('overrides defaults with loaded config', () => { diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 30f951a..ddede46 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -18,6 +18,10 @@ const DEFAULT_CONFIG: RuleConfig = { 'optional-chaining': 'warn', 'boolean-simplification': 'warn', 'early-return': 'warn', + 'no-debugger': 'warn', + 'no-empty-catch': 'warn', + 'no-useless-return': 'warn', + 'ts-diagnostics': 'off', }; /** diff --git a/packages/core/src/rules/complexity-hotspot.ts b/packages/core/src/rules/complexity-hotspot.ts index f248cd8..30578b6 100644 --- a/packages/core/src/rules/complexity-hotspot.ts +++ b/packages/core/src/rules/complexity-hotspot.ts @@ -118,7 +118,7 @@ function buildSuggestion(b: ComplexityBreakdown): string { const tips: string[] = []; if (top.includes('nested conditionals') || top.includes('ternaries')) { - tips.push('replace nested conditionals with early returns'); + tips.push('replace nested conditions with early returns'); } if (top.includes('loops')) { tips.push('extract loop bodies into helper functions'); @@ -136,7 +136,7 @@ function buildSuggestion(b: ComplexityBreakdown): string { tips.push('extract helper functions to reduce complexity'); } - return `Consider: ${tips.join('; ')}.`; + return `Suggested improvements: ${tips.join('; ')}.`; } export const complexityHotspotRule: Rule = { @@ -203,7 +203,7 @@ export const complexityHotspotRule: Rule = { }, suggestion: { summary: buildSuggestion(breakdown), - details: `Complexity score: ${score} (threshold: ${COMPLEXITY_THRESHOLD}). Contributors: ${contributors}${nestingNote}.`, + details: `Complexity score: ${score} (threshold: ${COMPLEXITY_THRESHOLD}). Primary contributors: ${contributors}${nestingNote}.`, }, }); }