Commit e3b7b239 authored by LongLD's avatar LongLD

refactor: simplify root directory handling and remove fallback logic

parent e12e933c
......@@ -6,57 +6,19 @@ import { z } from 'zod';
import { RuleIndexer } from './rules/indexer.js';
import { Composer } from './rules/composer.js';
import * as path from 'path';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
// --- Configuration ---
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Package directory contains the rules (one level up from src)
const PACKAGE_DIR = path.resolve(__dirname, '..');
// Function to determine the best root directory
function determineRootDir() {
// 1. Command line argument has highest priority
if (process.argv[2]) {
return path.resolve(process.argv[2]);
}
// 2. Environment variable
if (process.env.AWING_RULES_ROOT) {
return path.resolve(process.env.AWING_RULES_ROOT);
}
// 3. Try to find workspace root by looking for common indicators
const currentWorkingDir = process.cwd();
const workspaceIndicators = [
'package.json',
'.git',
'tsconfig.json',
'pyproject.toml',
'Cargo.toml',
'go.mod',
'pom.xml',
'.vscode'
];
// Check if current working directory has workspace indicators
for (const indicator of workspaceIndicators) {
const indicatorPath = path.join(currentWorkingDir, indicator);
try {
if (fs.existsSync(indicatorPath)) {
// Found workspace root, use it
return currentWorkingDir;
}
}
catch (error) {
// Ignore errors, continue checking
}
}
// 4. Fallback to package directory (for development/testing)
return PACKAGE_DIR;
}
const ROOT_DIR = determineRootDir();
// Rules are always loaded from the package directory
// This ensures consistency across all machines and avoids path issues
const ROOT_DIR = PACKAGE_DIR;
console.error(`Awing Rules MCP Server starting...`);
console.error(`Root directory: ${ROOT_DIR}`);
console.error(`Current working directory: ${process.cwd()}`);
console.error(`Rules directory: ${ROOT_DIR}`);
// --- Initialization ---
const indexer = new RuleIndexer(ROOT_DIR, PACKAGE_DIR);
const indexer = new RuleIndexer(ROOT_DIR);
const composer = new Composer(ROOT_DIR);
// Init index on startup (async but we assume fast enough or lazy)
indexer.init().catch(err => console.error('Failed to init index:', err));
......
......@@ -5,8 +5,8 @@ export class RuleIndexer {
scorer;
cachedRules = [];
lastIndexed = 0;
constructor(baseDir, fallbackDir) {
this.loader = new RuleLoader(baseDir, fallbackDir);
constructor(baseDir) {
this.loader = new RuleLoader(baseDir);
this.scorer = new Scorer();
}
async init() {
......
......@@ -4,19 +4,16 @@ import matter from 'gray-matter';
import { findMarkdownFiles } from '../utils/glob.js';
export class RuleLoader {
rootDir;
fallbackDir;
constructor(rootDir, fallbackDir) {
constructor(rootDir) {
this.rootDir = path.resolve(rootDir);
this.fallbackDir = fallbackDir ? path.resolve(fallbackDir) : undefined;
}
async loadAllRules() {
let files = await findMarkdownFiles(this.rootDir);
let rules = [];
const files = await findMarkdownFiles(this.rootDir);
const rules = [];
console.error(`Found ${files.length} rule files in ${this.rootDir}`);
// Load rules from root directory
for (const file of files) {
try {
const rule = await this.parseRule(file, 'primary');
const rule = await this.parseRule(file);
if (rule)
rules.push(rule);
}
......@@ -24,65 +21,34 @@ export class RuleLoader {
console.error(`Failed to parse rule file: ${file}`, error);
}
}
// If no rules found and fallback directory exists, load from fallback
if (rules.length === 0 && this.fallbackDir && this.fallbackDir !== this.rootDir) {
console.error(`No rules found in primary directory, trying fallback: ${this.fallbackDir}`);
const fallbackFiles = await findMarkdownFiles(this.fallbackDir);
console.error(`Found ${fallbackFiles.length} fallback rule files`);
for (const file of fallbackFiles) {
try {
const rule = await this.parseRule(file, 'fallback');
if (rule)
rules.push(rule);
}
catch (error) {
console.error(`Failed to parse fallback rule file: ${file}`, error);
}
}
}
return rules;
}
async loadBaseRule() {
// Try to load base.md from root directory first
let basePath = path.join(this.rootDir, 'base.md');
const basePath = path.join(this.rootDir, 'base.md');
try {
const content = await fs.readFile(basePath, 'utf-8');
return content;
}
catch (error) {
// If not found and fallback directory exists, try fallback
if (this.fallbackDir && this.fallbackDir !== this.rootDir) {
basePath = path.join(this.fallbackDir, 'base.md');
try {
const content = await fs.readFile(basePath, 'utf-8');
return content;
}
catch (fallbackError) {
// Return empty string if neither exists
return '';
}
}
return '';
}
}
async parseRule(filePath, source = 'primary') {
async parseRule(filePath) {
const rawContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(rawContent);
const fm = data;
const stats = await fs.stat(filePath);
// Use the appropriate root directory for calculating relative path
const rootForRelPath = source === 'primary' ? this.rootDir : (this.fallbackDir || this.rootDir);
const relativePath = path.relative(rootForRelPath, filePath);
const relativePath = path.relative(this.rootDir, filePath);
const id = fm.id || relativePath.replace(/\\/g, '/').replace(/\.md$/, '');
const title = fm.title || path.basename(filePath, '.md');
return {
id: source === 'fallback' ? `fallback:${id}` : id,
id,
path: filePath,
relativePath,
title: source === 'fallback' ? `[Fallback] ${title}` : title,
title,
content,
tags: [...(fm.tags || []), ...(source === 'fallback' ? ['fallback'] : [])],
priority: typeof fm.priority === 'number' ? fm.priority : (source === 'fallback' ? 10 : 50), // Lower priority for fallback
tags: fm.tags || [],
priority: typeof fm.priority === 'number' ? fm.priority : 50,
paths: fm.paths || [],
applies_when: fm.applies_when || [],
avoid: fm.avoid || [],
......
import { minimatch } from 'minimatch';
import { glob } from 'glob';
import * as fs from 'fs';
import * as path from 'path';
export const findMarkdownFiles = async (cwd) => {
// Find all markdown files, ignoring node_modules and dot folders
const files = await glob('**/*.md', {
cwd,
ignore: ['**/node_modules/**', '**/.*/**', '**/Library/**', '**/Library/Application Support/**'],
absolute: true,
follow: false, // Don't follow symlinks to avoid duplication
});
// Use realpath to deduplicate and ensure unique files
const uniqueFiles = Array.from(new Set(files.map(f => {
try {
return fs.realpathSync(f);
console.error(`Searching for markdown files in: ${cwd}`);
// Standard ignore patterns for project files
const ignorePatterns = [
'**/node_modules/**',
'**/.*/**', // Hidden folders (.git, .vscode, etc.)
'**/dist/**',
'**/build/**',
'**/target/**',
'**/vendor/**',
];
try {
const files = await glob('**/*.md', {
cwd,
ignore: ignorePatterns,
absolute: true,
follow: false,
maxDepth: 5, // Reasonable depth for project rules
});
console.error(`Found ${files.length} markdown files`);
// Deduplicate using realpath
const uniqueFiles = Array.from(new Set(files.map(f => {
try {
return fs.realpathSync(f);
}
catch {
return f;
}
})));
// Log sample files
if (uniqueFiles.length > 0) {
console.error(`Sample files:`);
uniqueFiles.slice(0, 3).forEach((file, i) => {
console.error(` ${i + 1}. ${path.relative(cwd, file)}`);
});
}
catch {
return f;
}
})));
return uniqueFiles;
return uniqueFiles;
}
catch (error) {
console.error(`Error finding markdown files:`, error);
return [];
}
};
export const matchGlob = (filePath, patterns) => {
if (!patterns || patterns.length === 0)
......
......@@ -11,8 +11,6 @@ import { z } from 'zod';
import { RuleIndexer } from './rules/indexer.js';
import { Composer } from './rules/composer.js';
import * as path from 'path';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
// --- Configuration ---
......@@ -21,56 +19,15 @@ const __dirname = path.dirname(__filename);
// Package directory contains the rules (one level up from src)
const PACKAGE_DIR = path.resolve(__dirname, '..');
// Function to determine the best root directory
function determineRootDir(): string {
// 1. Command line argument has highest priority
if (process.argv[2]) {
return path.resolve(process.argv[2]);
}
// 2. Environment variable
if (process.env.AWING_RULES_ROOT) {
return path.resolve(process.env.AWING_RULES_ROOT);
}
// 3. Try to find workspace root by looking for common indicators
const currentWorkingDir = process.cwd();
const workspaceIndicators = [
'package.json',
'.git',
'tsconfig.json',
'pyproject.toml',
'Cargo.toml',
'go.mod',
'pom.xml',
'.vscode'
];
// Check if current working directory has workspace indicators
for (const indicator of workspaceIndicators) {
const indicatorPath = path.join(currentWorkingDir, indicator);
try {
if (fs.existsSync(indicatorPath)) {
// Found workspace root, use it
return currentWorkingDir;
}
} catch (error) {
// Ignore errors, continue checking
}
}
// 4. Fallback to package directory (for development/testing)
return PACKAGE_DIR;
}
const ROOT_DIR = determineRootDir();
// Rules are always loaded from the package directory
// This ensures consistency across all machines and avoids path issues
const ROOT_DIR = PACKAGE_DIR;
console.error(`Awing Rules MCP Server starting...`);
console.error(`Root directory: ${ROOT_DIR}`);
console.error(`Current working directory: ${process.cwd()}`);
console.error(`Rules directory: ${ROOT_DIR}`);
// --- Initialization ---
const indexer = new RuleIndexer(ROOT_DIR, PACKAGE_DIR);
const indexer = new RuleIndexer(ROOT_DIR);
const composer = new Composer(ROOT_DIR);
// Init index on startup (async but we assume fast enough or lazy)
......
......@@ -8,8 +8,8 @@ export class RuleIndexer {
private cachedRules: Rule[] = [];
private lastIndexed: number = 0;
constructor(baseDir: string, fallbackDir?: string) {
this.loader = new RuleLoader(baseDir, fallbackDir);
constructor(baseDir: string) {
this.loader = new RuleLoader(baseDir);
this.scorer = new Scorer();
}
......
......@@ -6,90 +6,57 @@ import { findMarkdownFiles } from '../utils/glob.js';
export class RuleLoader {
private rootDir: string;
private fallbackDir?: string;
constructor(rootDir: string, fallbackDir?: string) {
constructor(rootDir: string) {
this.rootDir = path.resolve(rootDir);
this.fallbackDir = fallbackDir ? path.resolve(fallbackDir) : undefined;
}
async loadAllRules(): Promise<Rule[]> {
let files = await findMarkdownFiles(this.rootDir);
let rules: Rule[] = [];
const files = await findMarkdownFiles(this.rootDir);
const rules: Rule[] = [];
console.error(`Found ${files.length} rule files in ${this.rootDir}`);
// Load rules from root directory
for (const file of files) {
try {
const rule = await this.parseRule(file, 'primary');
const rule = await this.parseRule(file);
if (rule) rules.push(rule);
} catch (error) {
console.error(`Failed to parse rule file: ${file}`, error);
}
}
// If no rules found and fallback directory exists, load from fallback
if (rules.length === 0 && this.fallbackDir && this.fallbackDir !== this.rootDir) {
console.error(`No rules found in primary directory, trying fallback: ${this.fallbackDir}`);
const fallbackFiles = await findMarkdownFiles(this.fallbackDir);
console.error(`Found ${fallbackFiles.length} fallback rule files`);
for (const file of fallbackFiles) {
try {
const rule = await this.parseRule(file, 'fallback');
if (rule) rules.push(rule);
} catch (error) {
console.error(`Failed to parse fallback rule file: ${file}`, error);
}
}
}
return rules;
}
async loadBaseRule(): Promise<string> {
// Try to load base.md from root directory first
let basePath = path.join(this.rootDir, 'base.md');
const basePath = path.join(this.rootDir, 'base.md');
try {
const content = await fs.readFile(basePath, 'utf-8');
return content;
} catch (error) {
// If not found and fallback directory exists, try fallback
if (this.fallbackDir && this.fallbackDir !== this.rootDir) {
basePath = path.join(this.fallbackDir, 'base.md');
try {
const content = await fs.readFile(basePath, 'utf-8');
return content;
} catch (fallbackError) {
// Return empty string if neither exists
return '';
}
}
return '';
}
}
private async parseRule(filePath: string, source: 'primary' | 'fallback' = 'primary'): Promise<Rule | null> {
private async parseRule(filePath: string): Promise<Rule | null> {
const rawContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(rawContent);
const fm = data as RuleFrontmatter;
const stats = await fs.stat(filePath);
// Use the appropriate root directory for calculating relative path
const rootForRelPath = source === 'primary' ? this.rootDir : (this.fallbackDir || this.rootDir);
const relativePath = path.relative(rootForRelPath, filePath);
const relativePath = path.relative(this.rootDir, filePath);
const id = fm.id || relativePath.replace(/\\/g, '/').replace(/\.md$/, '');
const title = fm.title || path.basename(filePath, '.md');
return {
id: source === 'fallback' ? `fallback:${id}` : id,
id,
path: filePath,
relativePath,
title: source === 'fallback' ? `[Fallback] ${title}` : title,
title,
content,
tags: [...(fm.tags || []), ...(source === 'fallback' ? ['fallback'] : [])],
priority: typeof fm.priority === 'number' ? fm.priority : (source === 'fallback' ? 10 : 50), // Lower priority for fallback
tags: fm.tags || [],
priority: typeof fm.priority === 'number' ? fm.priority : 50,
paths: fm.paths || [],
applies_when: fm.applies_when || [],
avoid: fm.avoid || [],
......
import { minimatch } from 'minimatch';
import { glob } from 'glob';
import * as fs from 'fs';
import * as path from 'path';
export const findMarkdownFiles = async (cwd: string): Promise<string[]> => {
// Find all markdown files, ignoring node_modules and dot folders
const files = await glob('**/*.md', {
cwd,
ignore: ['**/node_modules/**', '**/.*/**', '**/Library/**', '**/Library/Application Support/**'],
absolute: true,
follow: false, // Don't follow symlinks to avoid duplication
});
// Use realpath to deduplicate and ensure unique files
const uniqueFiles = Array.from(new Set(files.map(f => {
try {
return fs.realpathSync(f);
} catch {
return f;
console.error(`Searching for markdown files in: ${cwd}`);
// Standard ignore patterns for project files
const ignorePatterns = [
'**/node_modules/**',
'**/.*/**', // Hidden folders (.git, .vscode, etc.)
'**/dist/**',
'**/build/**',
'**/target/**',
'**/vendor/**',
];
try {
const files = await glob('**/*.md', {
cwd,
ignore: ignorePatterns,
absolute: true,
follow: false,
maxDepth: 5, // Reasonable depth for project rules
});
console.error(`Found ${files.length} markdown files`);
// Deduplicate using realpath
const uniqueFiles = Array.from(new Set(files.map(f => {
try {
return fs.realpathSync(f);
} catch {
return f;
}
})));
// Log sample files
if (uniqueFiles.length > 0) {
console.error(`Sample files:`);
uniqueFiles.slice(0, 3).forEach((file, i) => {
console.error(` ${i + 1}. ${path.relative(cwd, file)}`);
});
}
})));
return uniqueFiles;
return uniqueFiles;
} catch (error) {
console.error(`Error finding markdown files:`, error);
return [];
}
};
export const matchGlob = (filePath: string, patterns: string[]): boolean => {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment