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'; ...@@ -6,57 +6,19 @@ import { z } from 'zod';
import { RuleIndexer } from './rules/indexer.js'; import { RuleIndexer } from './rules/indexer.js';
import { Composer } from './rules/composer.js'; import { Composer } from './rules/composer.js';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
// --- Configuration --- // --- Configuration ---
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
// Package directory contains the rules (one level up from src) // Package directory contains the rules (one level up from src)
const PACKAGE_DIR = path.resolve(__dirname, '..'); const PACKAGE_DIR = path.resolve(__dirname, '..');
// Function to determine the best root directory // Rules are always loaded from the package directory
function determineRootDir() { // This ensures consistency across all machines and avoids path issues
// 1. Command line argument has highest priority const ROOT_DIR = PACKAGE_DIR;
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();
console.error(`Awing Rules MCP Server starting...`); console.error(`Awing Rules MCP Server starting...`);
console.error(`Root directory: ${ROOT_DIR}`); console.error(`Rules directory: ${ROOT_DIR}`);
console.error(`Current working directory: ${process.cwd()}`);
// --- Initialization --- // --- Initialization ---
const indexer = new RuleIndexer(ROOT_DIR, PACKAGE_DIR); const indexer = new RuleIndexer(ROOT_DIR);
const composer = new Composer(ROOT_DIR); const composer = new Composer(ROOT_DIR);
// Init index on startup (async but we assume fast enough or lazy) // Init index on startup (async but we assume fast enough or lazy)
indexer.init().catch(err => console.error('Failed to init index:', err)); indexer.init().catch(err => console.error('Failed to init index:', err));
......
...@@ -5,8 +5,8 @@ export class RuleIndexer { ...@@ -5,8 +5,8 @@ export class RuleIndexer {
scorer; scorer;
cachedRules = []; cachedRules = [];
lastIndexed = 0; lastIndexed = 0;
constructor(baseDir, fallbackDir) { constructor(baseDir) {
this.loader = new RuleLoader(baseDir, fallbackDir); this.loader = new RuleLoader(baseDir);
this.scorer = new Scorer(); this.scorer = new Scorer();
} }
async init() { async init() {
......
...@@ -4,19 +4,16 @@ import matter from 'gray-matter'; ...@@ -4,19 +4,16 @@ import matter from 'gray-matter';
import { findMarkdownFiles } from '../utils/glob.js'; import { findMarkdownFiles } from '../utils/glob.js';
export class RuleLoader { export class RuleLoader {
rootDir; rootDir;
fallbackDir; constructor(rootDir) {
constructor(rootDir, fallbackDir) {
this.rootDir = path.resolve(rootDir); this.rootDir = path.resolve(rootDir);
this.fallbackDir = fallbackDir ? path.resolve(fallbackDir) : undefined;
} }
async loadAllRules() { async loadAllRules() {
let files = await findMarkdownFiles(this.rootDir); const files = await findMarkdownFiles(this.rootDir);
let rules = []; const rules = [];
console.error(`Found ${files.length} rule files in ${this.rootDir}`); console.error(`Found ${files.length} rule files in ${this.rootDir}`);
// Load rules from root directory
for (const file of files) { for (const file of files) {
try { try {
const rule = await this.parseRule(file, 'primary'); const rule = await this.parseRule(file);
if (rule) if (rule)
rules.push(rule); rules.push(rule);
} }
...@@ -24,65 +21,34 @@ export class RuleLoader { ...@@ -24,65 +21,34 @@ export class RuleLoader {
console.error(`Failed to parse rule file: ${file}`, 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; return rules;
} }
async loadBaseRule() { async loadBaseRule() {
// Try to load base.md from root directory first const basePath = path.join(this.rootDir, 'base.md');
let basePath = path.join(this.rootDir, 'base.md');
try { try {
const content = await fs.readFile(basePath, 'utf-8'); const content = await fs.readFile(basePath, 'utf-8');
return content; return content;
} }
catch (error) { 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 ''; return '';
} }
} }
async parseRule(filePath, source = 'primary') { async parseRule(filePath) {
const rawContent = await fs.readFile(filePath, 'utf-8'); const rawContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(rawContent); const { data, content } = matter(rawContent);
const fm = data; const fm = data;
const stats = await fs.stat(filePath); const stats = await fs.stat(filePath);
// Use the appropriate root directory for calculating relative path const relativePath = path.relative(this.rootDir, filePath);
const rootForRelPath = source === 'primary' ? this.rootDir : (this.fallbackDir || this.rootDir);
const relativePath = path.relative(rootForRelPath, filePath);
const id = fm.id || relativePath.replace(/\\/g, '/').replace(/\.md$/, ''); const id = fm.id || relativePath.replace(/\\/g, '/').replace(/\.md$/, '');
const title = fm.title || path.basename(filePath, '.md'); const title = fm.title || path.basename(filePath, '.md');
return { return {
id: source === 'fallback' ? `fallback:${id}` : id, id,
path: filePath, path: filePath,
relativePath, relativePath,
title: source === 'fallback' ? `[Fallback] ${title}` : title, title,
content, content,
tags: [...(fm.tags || []), ...(source === 'fallback' ? ['fallback'] : [])], tags: fm.tags || [],
priority: typeof fm.priority === 'number' ? fm.priority : (source === 'fallback' ? 10 : 50), // Lower priority for fallback priority: typeof fm.priority === 'number' ? fm.priority : 50,
paths: fm.paths || [], paths: fm.paths || [],
applies_when: fm.applies_when || [], applies_when: fm.applies_when || [],
avoid: fm.avoid || [], avoid: fm.avoid || [],
......
import { minimatch } from 'minimatch'; import { minimatch } from 'minimatch';
import { glob } from 'glob'; import { glob } from 'glob';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path';
export const findMarkdownFiles = async (cwd) => { export const findMarkdownFiles = async (cwd) => {
// Find all markdown files, ignoring node_modules and dot folders 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', { const files = await glob('**/*.md', {
cwd, cwd,
ignore: ['**/node_modules/**', '**/.*/**', '**/Library/**', '**/Library/Application Support/**'], ignore: ignorePatterns,
absolute: true, absolute: true,
follow: false, // Don't follow symlinks to avoid duplication follow: false,
maxDepth: 5, // Reasonable depth for project rules
}); });
// Use realpath to deduplicate and ensure unique files console.error(`Found ${files.length} markdown files`);
// Deduplicate using realpath
const uniqueFiles = Array.from(new Set(files.map(f => { const uniqueFiles = Array.from(new Set(files.map(f => {
try { try {
return fs.realpathSync(f); return fs.realpathSync(f);
...@@ -18,7 +31,19 @@ export const findMarkdownFiles = async (cwd) => { ...@@ -18,7 +31,19 @@ export const findMarkdownFiles = async (cwd) => {
return f; 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, patterns) => { export const matchGlob = (filePath, patterns) => {
if (!patterns || patterns.length === 0) if (!patterns || patterns.length === 0)
......
...@@ -11,8 +11,6 @@ import { z } from 'zod'; ...@@ -11,8 +11,6 @@ import { z } from 'zod';
import { RuleIndexer } from './rules/indexer.js'; import { RuleIndexer } from './rules/indexer.js';
import { Composer } from './rules/composer.js'; import { Composer } from './rules/composer.js';
import * as path from 'path'; import * as path from 'path';
import * as fs from 'fs';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
// --- Configuration --- // --- Configuration ---
...@@ -21,56 +19,15 @@ const __dirname = path.dirname(__filename); ...@@ -21,56 +19,15 @@ const __dirname = path.dirname(__filename);
// Package directory contains the rules (one level up from src) // Package directory contains the rules (one level up from src)
const PACKAGE_DIR = path.resolve(__dirname, '..'); const PACKAGE_DIR = path.resolve(__dirname, '..');
// Function to determine the best root directory // Rules are always loaded from the package directory
function determineRootDir(): string { // This ensures consistency across all machines and avoids path issues
// 1. Command line argument has highest priority const ROOT_DIR = PACKAGE_DIR;
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();
console.error(`Awing Rules MCP Server starting...`); console.error(`Awing Rules MCP Server starting...`);
console.error(`Root directory: ${ROOT_DIR}`); console.error(`Rules directory: ${ROOT_DIR}`);
console.error(`Current working directory: ${process.cwd()}`);
// --- Initialization --- // --- Initialization ---
const indexer = new RuleIndexer(ROOT_DIR, PACKAGE_DIR); const indexer = new RuleIndexer(ROOT_DIR);
const composer = new Composer(ROOT_DIR); const composer = new Composer(ROOT_DIR);
// Init index on startup (async but we assume fast enough or lazy) // Init index on startup (async but we assume fast enough or lazy)
......
...@@ -8,8 +8,8 @@ export class RuleIndexer { ...@@ -8,8 +8,8 @@ export class RuleIndexer {
private cachedRules: Rule[] = []; private cachedRules: Rule[] = [];
private lastIndexed: number = 0; private lastIndexed: number = 0;
constructor(baseDir: string, fallbackDir?: string) { constructor(baseDir: string) {
this.loader = new RuleLoader(baseDir, fallbackDir); this.loader = new RuleLoader(baseDir);
this.scorer = new Scorer(); this.scorer = new Scorer();
} }
......
...@@ -6,90 +6,57 @@ import { findMarkdownFiles } from '../utils/glob.js'; ...@@ -6,90 +6,57 @@ import { findMarkdownFiles } from '../utils/glob.js';
export class RuleLoader { export class RuleLoader {
private rootDir: string; private rootDir: string;
private fallbackDir?: string;
constructor(rootDir: string, fallbackDir?: string) { constructor(rootDir: string) {
this.rootDir = path.resolve(rootDir); this.rootDir = path.resolve(rootDir);
this.fallbackDir = fallbackDir ? path.resolve(fallbackDir) : undefined;
} }
async loadAllRules(): Promise<Rule[]> { async loadAllRules(): Promise<Rule[]> {
let files = await findMarkdownFiles(this.rootDir); const files = await findMarkdownFiles(this.rootDir);
let rules: Rule[] = []; const rules: Rule[] = [];
console.error(`Found ${files.length} rule files in ${this.rootDir}`); console.error(`Found ${files.length} rule files in ${this.rootDir}`);
// Load rules from root directory
for (const file of files) { for (const file of files) {
try { try {
const rule = await this.parseRule(file, 'primary'); const rule = await this.parseRule(file);
if (rule) rules.push(rule); if (rule) rules.push(rule);
} catch (error) { } catch (error) {
console.error(`Failed to parse rule file: ${file}`, 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; return rules;
} }
async loadBaseRule(): Promise<string> { async loadBaseRule(): Promise<string> {
// Try to load base.md from root directory first const basePath = path.join(this.rootDir, 'base.md');
let basePath = path.join(this.rootDir, 'base.md');
try { try {
const content = await fs.readFile(basePath, 'utf-8'); const content = await fs.readFile(basePath, 'utf-8');
return content; return content;
} catch (error) { } 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 ''; 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 rawContent = await fs.readFile(filePath, 'utf-8');
const { data, content } = matter(rawContent); const { data, content } = matter(rawContent);
const fm = data as RuleFrontmatter; const fm = data as RuleFrontmatter;
const stats = await fs.stat(filePath); const stats = await fs.stat(filePath);
// Use the appropriate root directory for calculating relative path const relativePath = path.relative(this.rootDir, filePath);
const rootForRelPath = source === 'primary' ? this.rootDir : (this.fallbackDir || this.rootDir);
const relativePath = path.relative(rootForRelPath, filePath);
const id = fm.id || relativePath.replace(/\\/g, '/').replace(/\.md$/, ''); const id = fm.id || relativePath.replace(/\\/g, '/').replace(/\.md$/, '');
const title = fm.title || path.basename(filePath, '.md'); const title = fm.title || path.basename(filePath, '.md');
return { return {
id: source === 'fallback' ? `fallback:${id}` : id, id,
path: filePath, path: filePath,
relativePath, relativePath,
title: source === 'fallback' ? `[Fallback] ${title}` : title, title,
content, content,
tags: [...(fm.tags || []), ...(source === 'fallback' ? ['fallback'] : [])], tags: fm.tags || [],
priority: typeof fm.priority === 'number' ? fm.priority : (source === 'fallback' ? 10 : 50), // Lower priority for fallback priority: typeof fm.priority === 'number' ? fm.priority : 50,
paths: fm.paths || [], paths: fm.paths || [],
applies_when: fm.applies_when || [], applies_when: fm.applies_when || [],
avoid: fm.avoid || [], avoid: fm.avoid || [],
......
import { minimatch } from 'minimatch'; import { minimatch } from 'minimatch';
import { glob } from 'glob'; import { glob } from 'glob';
import * as fs from 'fs'; import * as fs from 'fs';
import * as path from 'path';
export const findMarkdownFiles = async (cwd: string): Promise<string[]> => { export const findMarkdownFiles = async (cwd: string): Promise<string[]> => {
// Find all markdown files, ignoring node_modules and dot folders 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', { const files = await glob('**/*.md', {
cwd, cwd,
ignore: ['**/node_modules/**', '**/.*/**', '**/Library/**', '**/Library/Application Support/**'], ignore: ignorePatterns,
absolute: true, absolute: true,
follow: false, // Don't follow symlinks to avoid duplication follow: false,
maxDepth: 5, // Reasonable depth for project rules
}); });
// Use realpath to deduplicate and ensure unique files console.error(`Found ${files.length} markdown files`);
// Deduplicate using realpath
const uniqueFiles = Array.from(new Set(files.map(f => { const uniqueFiles = Array.from(new Set(files.map(f => {
try { try {
return fs.realpathSync(f); return fs.realpathSync(f);
...@@ -20,7 +36,19 @@ export const findMarkdownFiles = async (cwd: string): Promise<string[]> => { ...@@ -20,7 +36,19 @@ export const findMarkdownFiles = async (cwd: string): Promise<string[]> => {
} }
}))); })));
// 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 => { 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