diff --git a/CHANGELOG.md b/CHANGELOG.md index c73745c..61146ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## v5 — Summary Polish and Config Coverage + +- Improved human summary generator with complete sentence enforcement and deploy-readiness detection +- Expanded config test coverage to verify all rules are wired into defaults, presets, and registry +- Added deployment readiness section to README +- Strengthened export workflow bullet validation with period-ending requirement + ## v4 — Tooling Improvements - Stronger auto-fix engine with detailed skip reporting diff --git a/README.md b/README.md index 84b3379..300eeaa 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,17 @@ Download the `inspectorepo-report` artifact from the Actions tab to see the full - [ ] Dependency graph and cascade analysis - [ ] Performance profiling for large codebases +## Deployment Readiness + +The InspectoRepo web UI is currently in **Preview**. It is stable enough for local experimentation and demo purposes but is not yet deployed as a hosted service. + +- **Browser support:** The folder picker requires the File System Access API (Chrome or Edge). Other browsers can use the Upload Folder fallback or the "Try with sample project" button. +- **Onboarding:** The app shows a clear About section explaining how to run an analysis, with browser capability detection and a sample project for instant exploration. +- **Empty state:** When no issues are found, the UI displays a friendly confirmation with the analysis score. +- **Preview badge:** A visible "Preview" badge in the top bar signals the product is under active development. + +For production deployment, the web app is a standard Vite/React build (`npm run build`) and can be served from any static hosting provider. + ## Custom Rule API Extend InspectoRepo with your own rules using `defineRule()`: diff --git a/ai/scripts/generate-repomix-exports.ts b/ai/scripts/generate-repomix-exports.ts index 42635f8..68bd82f 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 (≥8 words, verb-first) +// Polished, outcome-focused bullet templates per area (≥8 words, verb-first, complete sentences) const AREA_BULLET_TEMPLATES: Record = { - 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 overall developer workflow configuration quality', + 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 overall developer workflow configuration quality.', }; // Banned patterns — bullets containing any of these are considered noisy/internal @@ -290,6 +290,8 @@ function isQualityBullet(bullet: string): boolean { if (!/^[A-Z][a-z]/.test(trimmed)) return false; // Must be at least 8 words if (trimmed.split(/\s+/).length < 8) return false; + // Must end with a period (complete sentence) + if (!trimmed.endsWith('.')) 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: ...") @@ -299,6 +301,19 @@ function isQualityBullet(bullet: string): boolean { return true; } +/** Ensure a bullet ends with a period. */ +function ensurePeriod(bullet: string): string { + const trimmed = bullet.trim(); + return trimmed.endsWith('.') ? trimmed : `${trimmed}.`; +} + +/** Check if any changed files are in apps/web/. */ +function hasWebChanges(files: string[]): boolean { + return files.some(f => f.startsWith('apps/web/')); +} + +const DEPLOY_READINESS_BULLET = 'Improved deploy readiness in the web application with clearer onboarding and browser capability detection.' + /** Validate that all bullets pass quality rules. Returns list of failing bullets. */ function validateBullets(bullets: string[]): string[] { const failures: string[] = []; @@ -357,8 +372,13 @@ function generateHumanSummary(pr: PRInfo, files: string[], _commits: string): st } } - // --- Phase 3: Trim to 3–5 and validate --- - let bullets = candidates.slice(0, 5); + // --- Phase 2b: Inject deploy-readiness bullet if web files changed --- + if (hasWebChanges(files) && !candidates.some(c => c.toLowerCase().includes('deploy readiness'))) { + candidates.push(DEPLOY_READINESS_BULLET); + } + + // --- Phase 3: Ensure complete sentences, trim to 3–5 and validate --- + let bullets = candidates.map(ensurePeriod).slice(0, 5); // Remove any that still fail validation individually bullets = bullets.filter(b => !isBannedBullet(b) && b.trim().length > 0 && !isTruncatedBullet(b) && isQualityBullet(b)); @@ -380,9 +400,9 @@ function generateHumanSummary(pr: PRInfo, files: string[], _commits: string): st bullets = buildAreaBullets(files); // Always ensure at least 3 const fallbacks = [ - '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', + '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; @@ -392,7 +412,7 @@ function generateHumanSummary(pr: PRInfo, files: string[], _commits: string): st } } - return bullets.slice(0, 5); + return bullets.map(ensurePeriod).slice(0, 5); } // --- Summary content validation --- @@ -651,9 +671,9 @@ if (bulletErrors.length > 0) { humanBullets = buildAreaBullets(milestoneFiles); // Ensure 3–5 range const fallbacks = [ - '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', + '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/packages/core/src/config.test.ts b/packages/core/src/config.test.ts index b957b07..f916e50 100644 --- a/packages/core/src/config.test.ts +++ b/packages/core/src/config.test.ts @@ -6,6 +6,19 @@ import { cliRulesToConfig, } from './config.js'; import { allRules } from './rules/index.js'; +import { resolvePreset, getPresetNames } from './presets.js'; + +const ALL_RULE_IDS = [ + 'unused-imports', + 'complexity-hotspot', + 'optional-chaining', + 'boolean-simplification', + 'early-return', + 'no-debugger', + 'no-empty-catch', + 'no-useless-return', + 'ts-diagnostics', +]; describe('parseConfig', () => { it('parses a valid config', () => { @@ -51,6 +64,39 @@ describe('mergeConfig', () => { }); }); +describe('DEFAULT_CONFIG contains all rules', () => { + it('has every registered rule in defaults', () => { + const defaults = mergeConfig(null); + for (const id of ALL_RULE_IDS) { + expect(defaults).toHaveProperty(id); + expect(['error', 'warn', 'off']).toContain(defaults[id]); + } + }); + + it('rule registry matches expected rule IDs', () => { + const registryIds = allRules.map((r) => r.id).sort(); + expect(registryIds).toEqual([...ALL_RULE_IDS].sort()); + }); + + it('no rule exists in registry but not in DEFAULT_CONFIG', () => { + const defaults = mergeConfig(null); + for (const rule of allRules) { + expect(defaults).toHaveProperty(rule.id); + } + }); +}); + +describe('presets produce valid configs', () => { + it.each(getPresetNames())('preset "%s" contains all rule IDs', (presetName) => { + const config = resolvePreset(presetName); + expect(config).not.toBeNull(); + for (const id of ALL_RULE_IDS) { + expect(config).toHaveProperty(id); + expect(['error', 'warn', 'off']).toContain(config![id]); + } + }); +}); + describe('filterRulesByConfig', () => { it('removes rules set to off', () => { const config = { @@ -77,6 +123,18 @@ describe('filterRulesByConfig', () => { const rule = filtered.find((r) => r.id === 'unused-imports'); expect(rule?.severity).toBe('error'); }); + + it('returns only active rules from a full config', () => { + const defaults = mergeConfig(null); + const filtered = filterRulesByConfig(allRules, defaults); + // ts-diagnostics is 'off' in defaults, so it should be excluded + expect(filtered.find((r) => r.id === 'ts-diagnostics')).toBeUndefined(); + // All other rules should be present + for (const rule of allRules) { + if (rule.id === 'ts-diagnostics') continue; + expect(filtered.find((r) => r.id === rule.id)).toBeDefined(); + } + }); }); describe('cliRulesToConfig', () => { @@ -95,4 +153,13 @@ describe('cliRulesToConfig', () => { expect(config['early-return']).toBe('warn'); expect(config['unused-imports']).toBe('off'); }); + + it('sets all rules to off or warn', () => { + const ids = allRules.map((r) => r.id); + const config = cliRulesToConfig('no-debugger', ids); + for (const id of ids) { + expect(['warn', 'off']).toContain(config[id]); + } + expect(config['no-debugger']).toBe('warn'); + }); });