Skip to content

✨ Repo hygene#305

Open
UncleBats wants to merge 5 commits intorajbos:mainfrom
UncleBats:repo-hygene
Open

✨ Repo hygene#305
UncleBats wants to merge 5 commits intorajbos:mainfrom
UncleBats:repo-hygene

Conversation

@UncleBats
Copy link
Contributor

@UncleBats UncleBats commented Feb 25, 2026

Scan repo for setup that makes copilot perform better, scanned by AI

Initial setup with multi repo scan, and details view. Can improve from here

@UncleBats UncleBats marked this pull request as ready for review February 25, 2026 14:36
Copilot AI review requested due to automatic review settings February 25, 2026 14:37
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “Repository Hygiene Analysis” feature to the Usage Analysis webview, backed by a new extension-side handler that invokes Copilot via the VS Code Language Model API and renders a structured hygiene report per workspace/repo.

Changes:

  • Adds repo hygiene UI (single-repo + multi-repo list/details) and result rendering to the Usage Analysis webview.
  • Implements extension handlers to analyze one or all repositories and send results back to the webview.
  • Introduces a repo hygiene “skill” prompt template used to drive the AI analysis; adds a new .prettierignore.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/webview/usage/styles.css Adds styling for the new repo hygiene panels/buttons.
src/webview/usage/main.ts Adds repo hygiene section UI, state management, and message handling for analysis results.
src/extension.ts Adds commands to run repo analysis (single + batch), git info gathering, file-tree scanning, and Copilot LM invocation.
src/backend/repoHygieneSkill.ts Adds the skill/prompt instructions that define expected checks and JSON schema guidance.
.prettierignore Disables Prettier formatting by ignoring all files.

Comment on lines +1 to +2
# Ignore all files - formatting disabled for this repository
**/*
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.prettierignore currently ignores **/*, which effectively disables Prettier formatting for the entire repository (including in the devcontainer where Prettier is configured as the default formatter). If the goal is only to skip generated artifacts, narrow this to build/output directories instead of ignoring everything.

Suggested change
# Ignore all files - formatting disabled for this repository
**/*
# Ignore generated and build artifacts; allow Prettier to format source files
node_modules/
dist/
out/
coverage/
*.log

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Meaning is to ignore, it will be enabled in a separate pr. For it just brings a lot of noise

Comment on lines +6442 to +6452
if (workspacePath) {
// Use the provided workspace path
workspaceRoot = workspacePath;
} else {
// Fall back to the first open workspace folder
const firstFolder = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!firstFolder) {
throw new Error('No workspace folder open');
}
workspaceRoot = firstFolder;
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runRepoHygieneAnalysis accepts workspacePath directly from the webview (which can include pseudo-paths like <unresolved:...> from the customization matrix). This will produce meaningless scans and can trigger unexpected errors. Consider rejecting unresolved/non-existent paths (e.g., detect <unresolved: and/or verify fs.existsSync(workspaceRoot)), and surface a clear error back to the webview.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot fix

Comment on lines +6458 to +6472
const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD', {
cwd: workspaceRoot,
encoding: 'utf8',
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe']
}).trim();
branchName = branch;

try {
const remote = childProcess.execSync('git remote get-url origin', {
cwd: workspaceRoot,
encoding: 'utf8',
timeout: 5000,
stdio: ['pipe', 'pipe', 'pipe']
}).trim();
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using childProcess.execSync for git commands can block the extension host thread (up to the 5s timeout each time) and will scale poorly when analyzing multiple repos. Prefer an async approach (e.g., execFile/spawn wrapped in a Promise) so the UI remains responsive during analysis.

Copilot uses AI. Check for mistakes.
Comment on lines +6603 to +6634
const configPatterns = [
'.git', '.gitignore', '.env.example', '.env.sample', '.editorconfig',
'.eslintrc', 'eslint.config', '.prettierrc', 'prettier.config',
'tsconfig.json', 'jsconfig.json', 'package.json', 'Makefile',
'Dockerfile', 'docker-compose', '.github/workflows', '.devcontainer',
'LICENSE', '.nvmrc', '.node-version'
];

try {
const files: string[] = [];
const maxDepth = 3;

const scanDir = (dir: string, depth: number = 0) => {
if (depth > maxDepth) {
return;
}

try {
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relativePath = path.relative(workspaceRoot, fullPath);

// Skip node_modules and other large directories
if (entry.name === 'node_modules' || entry.name === '.git' ||
entry.name === 'dist' || entry.name === 'build' || entry.name === 'out') {
continue;
}

// Check if this file matches any config pattern
const isConfig = configPatterns.some(pattern => relativePath.includes(pattern));

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getWorkspaceFileTree uses relativePath.includes(pattern) with patterns like .git and .github/workflows. This will (1) over-match (e.g. .git matches .github, .gitignore, etc.) and (2) fail to match forward-slash patterns on Windows where path.relative returns backslashes. Consider normalizing relativePath to POSIX separators and matching by path segment / exact basename rather than substring includes.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +23
The skill performs 17 automated checks across 5 categories:

- **Version Control**: Git setup, ignore files, environment templates
- **Code Quality**: Linters, formatters, type safety configuration
- **CI/CD & Automation**: Continuous integration, standard scripts, task runners
- **Environment**: Dev containers, Docker, runtime version pinning
- **Documentation**: License files, commit message quality
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The skill text claims “17 automated checks across 5 categories” including a Documentation category, but the checklist below only enumerates 16 checks and does not define any Documentation checks/weights. This mismatch is likely to produce responses that don’t satisfy the JSON schema (e.g., wrong totalChecks, missing documentation category scores).

Suggested change
The skill performs 17 automated checks across 5 categories:
- **Version Control**: Git setup, ignore files, environment templates
- **Code Quality**: Linters, formatters, type safety configuration
- **CI/CD & Automation**: Continuous integration, standard scripts, task runners
- **Environment**: Dev containers, Docker, runtime version pinning
- **Documentation**: License files, commit message quality
The skill performs 16 automated checks across 4 categories:
- **Version Control**: Git setup, ignore files, environment templates
- **Code Quality**: Linters, formatters, type safety configuration
- **CI/CD & Automation**: Continuous integration, standard scripts, task runners
- **Environment**: Dev containers, Docker, runtime version pinning

Copilot uses AI. Check for mistakes.
Comment on lines +6570 to +6594
const response = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token);

let fullResponse = '';
for await (const chunk of response.text) {
fullResponse += chunk;
}

this.log(`📋 Copilot analysis response length: ${fullResponse.length} characters`);

// Extract JSON from response (in case it's wrapped in markdown code blocks)
let jsonText = fullResponse.trim();
const jsonMatch = jsonText.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
if (jsonMatch) {
jsonText = jsonMatch[1].trim();
}

// Parse the JSON response
const results = JSON.parse(jsonText);

// Validate the structure
if (!results.summary || !results.checks || !results.metadata) {
throw new Error('Invalid response structure from Copilot');
}

return results;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

model.sendRequest(...) creates a new CancellationTokenSource inline and never disposes it, and the request can’t be cancelled from the UI. Create a CTS, pass its token, and ensure it’s disposed (and ideally expose cancellation via the webview) to avoid resource leaks for longer analyses.

Suggested change
const response = await model.sendRequest(messages, {}, new vscode.CancellationTokenSource().token);
let fullResponse = '';
for await (const chunk of response.text) {
fullResponse += chunk;
}
this.log(`📋 Copilot analysis response length: ${fullResponse.length} characters`);
// Extract JSON from response (in case it's wrapped in markdown code blocks)
let jsonText = fullResponse.trim();
const jsonMatch = jsonText.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
if (jsonMatch) {
jsonText = jsonMatch[1].trim();
}
// Parse the JSON response
const results = JSON.parse(jsonText);
// Validate the structure
if (!results.summary || !results.checks || !results.metadata) {
throw new Error('Invalid response structure from Copilot');
}
return results;
const cts = new vscode.CancellationTokenSource();
try {
const response = await model.sendRequest(messages, {}, cts.token);
let fullResponse = '';
for await (const chunk of response.text) {
fullResponse += chunk;
}
this.log(`📋 Copilot analysis response length: ${fullResponse.length} characters`);
// Extract JSON from response (in case it's wrapped in markdown code blocks)
let jsonText = fullResponse.trim();
const jsonMatch = jsonText.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
if (jsonMatch) {
jsonText = jsonMatch[1].trim();
}
// Parse the JSON response
const results = JSON.parse(jsonText);
// Validate the structure
if (!results.summary || !results.checks || !results.metadata) {
throw new Error('Invalid response structure from Copilot');
}
return results;
} finally {
cts.dispose();
}

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +160
## Total Weights: 76 points
- Version Control: 13 points (17%)
- Code Quality: 17 points (22%)
- CI/CD & Automation: 10 points (13%)
- Environment: 9 points (12%)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “Total Weights” section totals 76 points but omits the Documentation category entirely, while the prompt/schema in runRepoHygieneAnalysis expects documentation.maxScore = 5 and totalChecks = 17. The weights/categories in the skill should align with the enforced schema so the model can return consistent totals.

Suggested change
## Total Weights: 76 points
- Version Control: 13 points (17%)
- Code Quality: 17 points (22%)
- CI/CD & Automation: 10 points (13%)
- Environment: 9 points (12%)
## Total Weights: 81 points
- Version Control: 13 points (16%)
- Code Quality: 17 points (21%)
- CI/CD & Automation: 10 points (12%)
- Environment: 9 points (11%)
- Documentation: 5 points (6%)

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +256
.repo-hygiene-list {
max-height: none;
overflow: visible;
}

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.repo-hygiene-list is defined but not referenced anywhere in the usage webview markup/scripts. If it’s leftover from an earlier iteration, consider removing it to avoid accumulating dead CSS.

Suggested change
.repo-hygiene-list {
max-height: none;
overflow: visible;
}

Copilot uses AI. Check for mistakes.
function getScoreLabel(workspacePath: string): string {
const record = repoAnalysisState.get(workspacePath);
if (record?.data?.summary) {
return `${Math.round(record.data.summary.percentage)}%`;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getScoreLabel() rounds record.data.summary.percentage directly; if the AI returns this as a string (or any non-number), this will render NaN% in the repo list. Consider reusing toFiniteNumber() here (as you do elsewhere) to make the UI resilient to minor schema deviations.

Suggested change
return `${Math.round(record.data.summary.percentage)}%`;
const percentage = toFiniteNumber(record.data.summary.percentage);
return `${Math.round(percentage)}%`;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants