Skip to content
Open
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
27 changes: 27 additions & 0 deletions .github/workflows/conflict-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Conflict Check

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install dependencies
run: npm install

- name: Run Conflict Check
run: npm run conflict-check
env:
GITHUB_BASE_REF: ${{ github.base_ref }}
2 changes: 1 addition & 1 deletion PREVENT_CONFLICTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ This document outlines strategies to minimize merge conflicts and improve produc

### Long-term Strategy
1. **Adopt "Open for Extension, Closed for Modification"**: Design core classes to accept extensions (plugins, commands, routes) without requiring modification to the class source code.
2. **Automated Conflict Detection**: Implement pre-commit hooks or CI checks that flag potential conflict areas (e.g., large files, modification of frozen core files).
2. [x] **Automated Conflict Detection**: Implement pre-commit hooks or CI checks that flag potential conflict areas (e.g., large files, modification of frozen core files). (Completed 2026-02-23)
3. [x] **Enhanced Testing**: Add unit tests specifically for the plugin registration and configuration loading logic to ensure refactoring doesn't introduce regressions. (Completed 2026-02-22)

## Conclusion
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"postbuild": "chmod +x dist/cli/index.js",
"c": "tsx ./src/context/index.ts",
"cc": "tsx ./src/context/index.ts -c -o ./gen/context.txt",
"c:all": "npm run c -- -o ./gen/context.txt ./src/core/dataprompt.ts"
"c:all": "npm run c -- -o ./gen/context.txt ./src/core/dataprompt.ts",
"conflict-check": "tsx scripts/conflict-check.ts"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
Expand Down
131 changes: 131 additions & 0 deletions scripts/conflict-check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';

const FROZEN_FILES = [
'src/core/dataprompt.ts',
'src/core/plugin.manager.ts',
'src/core/interfaces.ts',
'src/index.ts',
];

const MAX_LINES = 300;
const IGNORED_LARGE_FILES = [
'src/context/index.ts',
];

function getChangedFiles(): string[] {
try {
// If running in CI, GITHUB_BASE_REF is set.
const baseRef = process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : 'origin/main';

// Check if we are in a git repo
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
} catch {
console.warn('Not a git repository. Skipping modified file checks.');
return [];
}

// specific check for local dev environment where origin/main might be missing
if (!process.env.GITHUB_BASE_REF) {
try {
execSync(`git rev-parse --verify ${baseRef}`, { stdio: 'ignore' });
} catch {
console.warn(`Base ref ${baseRef} not found. Skipping frozen file checks.`);
return [];
}
}

const command = `git diff --name-only ${baseRef}...HEAD`;
const output = execSync(command).toString();

// Check for uncommitted changes too (for local development)
let uncommittedOutput = '';
try {
uncommittedOutput = execSync('git diff --name-only').toString();
} catch (e) {
// Ignore error if basic git diff fails (unlikely if inside work tree)
}

// Check for staged changes too
let stagedOutput = '';
try {
stagedOutput = execSync('git diff --name-only --cached').toString();
} catch (e) {
}

const allFiles = new Set([
...output.split('\n').filter(Boolean),
...uncommittedOutput.split('\n').filter(Boolean),
...stagedOutput.split('\n').filter(Boolean)
]);

return Array.from(allFiles);
} catch (error) {
console.error('Error getting changed files:', error);
return [];
}
}

function checkFrozenFiles(changedFiles: string[]) {
const modifiedFrozen = changedFiles.filter(file => FROZEN_FILES.includes(file));
if (modifiedFrozen.length > 0) {
console.warn('\x1b[33m%s\x1b[0m', 'WARNING: The following frozen core files have been modified:');
modifiedFrozen.forEach(file => console.warn(` - ${file}`));
console.warn(' Please ensure these changes are necessary and reviewed carefully.');
}
}

function getAllTsFiles(dir: string, fileList: string[] = []): string[] {
const files = fs.readdirSync(dir);
files.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory()) {
getAllTsFiles(filePath, fileList);
} else {
if (filePath.endsWith('.ts') && !filePath.endsWith('.d.ts')) {
fileList.push(filePath);
}
}
});
return fileList;
}

function checkFileSizes() {
const files = getAllTsFiles('src');
const largeFiles: string[] = [];

files.forEach(file => {
const normalizedFile = file.split(path.sep).join('/');
if (IGNORED_LARGE_FILES.some(ignored => normalizedFile.includes(ignored))) {
return;
}
const content = fs.readFileSync(file, 'utf-8');
const lines = content.split('\n').length;
if (lines > MAX_LINES) {
largeFiles.push(`${file} (${lines} lines)`);
}
});

if (largeFiles.length > 0) {
console.error('\x1b[31m%s\x1b[0m', `ERROR: The following files exceed the ${MAX_LINES} line limit:`);
largeFiles.forEach(file => console.error(` - ${file}`));
console.error(' Please refactor these files to be smaller.');
process.exit(1);
}
}

function run() {
console.log('Running conflict detection checks...');

const changedFiles = getChangedFiles();
checkFrozenFiles(changedFiles);

checkFileSizes();

console.log('Conflict checks completed.');
}

run();