Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`:
Expand Down
64 changes: 42 additions & 22 deletions ai/scripts/generate-repomix-exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,20 +147,20 @@ function categorizeFiles(files: string[]): Map<string, string[]> {
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<string, string> = {
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
Expand Down Expand Up @@ -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: ...")
Expand All @@ -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[] = [];
Expand Down Expand Up @@ -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));
Expand All @@ -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;
Expand All @@ -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 ---
Expand Down Expand Up @@ -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;
Expand Down
67 changes: 67 additions & 0 deletions packages/core/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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 = {
Expand All @@ -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', () => {
Expand All @@ -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');
});
});
Loading