1+ import { realpathSync } from "node:fs" ;
12import os from "node:os" ;
23import path from "node:path" ;
34
@@ -32,7 +33,7 @@ const QWEN_EXTENSION_INSTALL_FILE = ".qwen-extension-install.json";
3233const QWEN_EXTENSION_ENABLEMENT_FILE = "extension-enablement.json" ;
3334
3435function defaultQwenHome ( ) {
35- return path . join ( os . homedir ( ) , ".qwen" ) ;
36+ return process . env . QWEN_HOME ?? path . join ( os . homedir ( ) , ".qwen" ) ;
3637}
3738
3839function ensureLeadingAndTrailingSlash ( dirPath ) {
@@ -60,14 +61,44 @@ function isExtensionEnabled(enablementConfig, extensionName, workspace) {
6061 let enabled = true ;
6162 const allOverrides = extensionConfig ?. overrides ?? [ ] ;
6263 const lexicalPath = ensureLeadingAndTrailingSlash ( workspace ) ;
64+ let canonicalPath = lexicalPath ;
65+ try { canonicalPath = ensureLeadingAndTrailingSlash ( realpathSync . native ( path . resolve ( workspace ) ) ) ; } catch { }
6366 for ( const rule of allOverrides ) {
64- if ( overrideMatchesPath ( rule , lexicalPath ) ) {
67+ if ( overrideMatchesPath ( rule , lexicalPath ) || overrideMatchesPath ( rule , canonicalPath ) ) {
6568 enabled = ! rule . startsWith ( "!" ) ;
6669 }
6770 }
6871 return enabled ;
6972}
7073
74+ function flattenSettingsHooks ( settingsHooks ) {
75+ if ( ! settingsHooks || typeof settingsHooks !== "object" || Array . isArray ( settingsHooks ) ) return [ ] ;
76+ const items = [ ] ;
77+ for ( const [ , definitions ] of Object . entries ( settingsHooks ) ) {
78+ if ( ! Array . isArray ( definitions ) ) continue ;
79+ for ( const def of definitions ) {
80+ if ( ! Array . isArray ( def ?. hooks ) ) continue ;
81+ for ( const hook of def . hooks ) {
82+ if ( hook ?. command ) items . push ( { command : hook . command , type : hook . type ?? "command" } ) ;
83+ else if ( hook ?. url ) items . push ( { command : hook . url , type : "http" } ) ;
84+ }
85+ }
86+ }
87+ return items ;
88+ }
89+
90+ function normalizeSettingsMcp ( name , config , scope , sourceLabel , evidencePath , rootForEvidence ) {
91+ return {
92+ name,
93+ scope,
94+ sourceLabel,
95+ command : config . command ?? null ,
96+ args : config . args ?? [ ] ,
97+ url : config . url ?? config . httpUrl ?? null ,
98+ evidence : evidence ( evidencePath , rootForEvidence ) ,
99+ } ;
100+ }
101+
71102function qwenMarkdownRuleSource ( workspace , sourceLabel , precedence = "after-provider-rules" ) {
72103 return {
73104 type : "file" ,
@@ -237,36 +268,23 @@ async function collectQwenPlugins(records, workspace) {
237268}
238269
239270async function collectQwenUserPrimitives ( qwenHome ) {
240- const mcpPath = path . join ( path . dirname ( qwenHome ) , ".mcp.json" ) ;
241- const mcps = await pathExists ( mcpPath )
242- ? ( await collectMcpFromConfig ( mcpPath , "user" , "User" , qwenHome ) ) ?? [ ]
243- : ( await collectMcpItems ( qwenHome , "user" , "User" , qwenHome ) ) ?? [ ] ;
244- const settings = ( await readJson ( path . join ( qwenHome , "settings.json" ) ) ) ?? { } ;
271+ const settingsPath = path . join ( qwenHome , "settings.json" ) ;
272+ const settings = ( await readJson ( settingsPath ) ) ?? { } ;
273+ const mcps = [ ] ;
245274 if ( settings . mcpServers && typeof settings . mcpServers === "object" ) {
246275 for ( const [ name , config ] of Object . entries ( settings . mcpServers ) ) {
247- if ( ! mcps . some ( ( m ) => m . name === name ) ) {
248- mcps . push ( {
249- name,
250- scope : "user" ,
251- sourceLabel : "User" ,
252- command : config . command ?? null ,
253- args : config . args ?? [ ] ,
254- evidence : evidence ( path . join ( qwenHome , "settings.json" ) , qwenHome ) ,
255- } ) ;
256- }
276+ mcps . push ( normalizeSettingsMcp ( name , config , "user" , "User" , settingsPath , qwenHome ) ) ;
257277 }
258278 }
259279 const hooks = await collectHookItems ( qwenHome , "user" , "User" , qwenHome ) ;
260- if ( Array . isArray ( settings . hooks ) ) {
261- for ( const hook of settings . hooks ) {
262- if ( hook ?. command && ! hooks . some ( ( h ) => h . command === hook . command ) ) {
263- hooks . push ( {
264- command : hook . command ,
265- scope : "user" ,
266- sourceLabel : "User" ,
267- evidence : evidence ( path . join ( qwenHome , "settings.json" ) , qwenHome ) ,
268- } ) ;
269- }
280+ for ( const hook of flattenSettingsHooks ( settings . hooks ) ) {
281+ if ( ! hooks . some ( ( h ) => h . command === hook . command ) ) {
282+ hooks . push ( {
283+ command : hook . command ,
284+ scope : "user" ,
285+ sourceLabel : "User" ,
286+ evidence : evidence ( settingsPath , qwenHome ) ,
287+ } ) ;
270288 }
271289 }
272290 return {
@@ -282,31 +300,30 @@ async function collectQwenUserPrimitives(qwenHome) {
282300async function collectQwenWorkspacePrimitives ( workspace ) {
283301 const sourceLabel = await workspaceSourceLabel ( workspace ) ;
284302 const project = await collectWorkspaceRootPrimitives ( path . join ( workspace , ".qwen" ) , sourceLabel , workspace ) ;
285- const settings = ( await readJson ( path . join ( workspace , ".qwen" , "settings.json" ) ) ) ?? { } ;
303+ const projectMcpPath = path . join ( workspace , ".mcp.json" ) ;
304+ if ( await pathExists ( projectMcpPath ) ) {
305+ const projectMcps = ( await collectMcpFromConfig ( projectMcpPath , "project" , sourceLabel , workspace ) ) ?? [ ] ;
306+ for ( const mcp of projectMcps ) {
307+ if ( ! project . mcps . some ( ( m ) => m . name === mcp . name ) ) project . mcps . push ( mcp ) ;
308+ }
309+ }
310+ const settingsPath = path . join ( workspace , ".qwen" , "settings.json" ) ;
311+ const settings = ( await readJson ( settingsPath ) ) ?? { } ;
286312 if ( settings . mcpServers && typeof settings . mcpServers === "object" ) {
287313 for ( const [ name , config ] of Object . entries ( settings . mcpServers ) ) {
288314 if ( ! project . mcps . some ( ( m ) => m . name === name ) ) {
289- project . mcps . push ( {
290- name,
291- scope : "project" ,
292- sourceLabel,
293- command : config . command ?? null ,
294- args : config . args ?? [ ] ,
295- evidence : evidence ( path . join ( workspace , ".qwen" , "settings.json" ) , workspace ) ,
296- } ) ;
315+ project . mcps . push ( normalizeSettingsMcp ( name , config , "project" , sourceLabel , settingsPath , workspace ) ) ;
297316 }
298317 }
299318 }
300- if ( Array . isArray ( settings . hooks ) ) {
301- for ( const hook of settings . hooks ) {
302- if ( hook ?. command && ! project . hooks . some ( ( h ) => h . command === hook . command ) ) {
303- project . hooks . push ( {
304- command : hook . command ,
305- scope : "project" ,
306- sourceLabel,
307- evidence : evidence ( path . join ( workspace , ".qwen" , "settings.json" ) , workspace ) ,
308- } ) ;
309- }
319+ for ( const hook of flattenSettingsHooks ( settings . hooks ) ) {
320+ if ( ! project . hooks . some ( ( h ) => h . command === hook . command ) ) {
321+ project . hooks . push ( {
322+ command : hook . command ,
323+ scope : "project" ,
324+ sourceLabel,
325+ evidence : evidence ( settingsPath , workspace ) ,
326+ } ) ;
310327 }
311328 }
312329 return {
0 commit comments