Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
Awing Rules ClaudeCode Mcp
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
awing-mcp
Awing Rules ClaudeCode Mcp
Commits
e3b7b239
Commit
e3b7b239
authored
Feb 02, 2026
by
LongLD
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
refactor: simplify root directory handling and remove fallback logic
parent
e12e933c
Changes
8
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
121 additions
and
216 deletions
+121
-216
dist/index.js
dist/index.js
+5
-43
dist/rules/indexer.js
dist/rules/indexer.js
+2
-2
dist/rules/loader.js
dist/rules/loader.js
+11
-45
dist/utils/glob.js
dist/utils/glob.js
+41
-16
src/index.ts
src/index.ts
+5
-48
src/rules/indexer.ts
src/rules/indexer.ts
+2
-2
src/rules/loader.ts
src/rules/loader.ts
+11
-44
src/utils/glob.ts
src/utils/glob.ts
+44
-16
No files found.
dist/index.js
View file @
e3b7b239
...
...
@@ -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
));
...
...
dist/rules/indexer.js
View file @
e3b7b239
...
...
@@ -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
()
{
...
...
dist/rules/loader.js
View file @
e3b7b239
...
...
@@ -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
()
{
le
t
files
=
await
findMarkdownFiles
(
this
.
rootDir
);
le
t
rules
=
[];
cons
t
files
=
await
findMarkdownFiles
(
this
.
rootDir
);
cons
t
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
||
[],
...
...
dist/utils/glob.js
View file @
e3b7b239
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
)
...
...
src/index.ts
View file @
e3b7b239
...
...
@@ -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)
...
...
src/rules/indexer.ts
View file @
e3b7b239
...
...
@@ -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
();
}
...
...
src/rules/loader.ts
View file @
e3b7b239
...
...
@@ -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
[]
>
{
le
t
files
=
await
findMarkdownFiles
(
this
.
rootDir
);
le
t
rules
:
Rule
[]
=
[];
cons
t
files
=
await
findMarkdownFiles
(
this
.
rootDir
);
cons
t
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
||
[],
...
...
src/utils/glob.ts
View file @
e3b7b239
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
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment