@@ -457,6 +457,9 @@ export class ConfigManager {
457457
458458 /**
459459 * 将权限规则追加到项目本地 settings.local.json 的 allow 列表
460+ * 增强功能:
461+ * 1. 去重:检查规则是否已存在
462+ * 2. 清理:移除被新规则覆盖的旧规则
460463 */
461464 async appendLocalPermissionAllowRule ( rule : string ) : Promise < void > {
462465 const localSettingsPath = path . join ( process . cwd ( ) , '.blade' , 'settings.local.json' ) ;
@@ -475,19 +478,37 @@ export class ConfigManager {
475478 permissions . ask = Array . isArray ( permissions . ask ) ? permissions . ask : [ ] ;
476479 permissions . deny = Array . isArray ( permissions . deny ) ? permissions . deny : [ ] ;
477480
478- if ( ! permissions . allow . includes ( rule ) ) {
479- permissions . allow = [ ...permissions . allow , rule ] ;
480- existingSettings . permissions = permissions ;
481+ // 检查新规则是否已存在
482+ if ( permissions . allow . includes ( rule ) ) {
483+ return ; // 规则已存在,无需重复添加
484+ }
481485
482- await fs . writeFile (
483- localSettingsPath ,
484- JSON . stringify ( existingSettings , null , 2 ) ,
485- { mode : 0o600 , encoding : 'utf-8' }
486+ // 移除被新规则覆盖的旧规则
487+ const originalCount = permissions . allow . length ;
488+ permissions . allow = permissions . allow . filter ( ( oldRule : string ) => {
489+ return ! this . isRuleCoveredBy ( oldRule , rule ) ;
490+ } ) ;
491+
492+ const removedCount = originalCount - permissions . allow . length ;
493+ if ( removedCount > 0 && this . config ?. debug ) {
494+ console . log (
495+ `[ConfigManager] 新规则 "${ rule } " 覆盖了 ${ removedCount } 条旧规则,已自动清理`
486496 ) ;
487497 }
488498
489- if ( this . config && ! this . config . permissions . allow . includes ( rule ) ) {
490- this . config . permissions . allow = [ ...this . config . permissions . allow , rule ] ;
499+ // 添加新规则
500+ permissions . allow . push ( rule ) ;
501+ existingSettings . permissions = permissions ;
502+
503+ await fs . writeFile (
504+ localSettingsPath ,
505+ JSON . stringify ( existingSettings , null , 2 ) ,
506+ { mode : 0o600 , encoding : 'utf-8' }
507+ ) ;
508+
509+ // 更新内存配置
510+ if ( this . config ) {
511+ this . config . permissions . allow = [ ...permissions . allow ] ;
491512 }
492513 } catch ( error ) {
493514 console . error ( '[ConfigManager] Failed to append local permission rule:' , error ) ;
@@ -497,6 +518,113 @@ export class ConfigManager {
497518 }
498519 }
499520
521+ /**
522+ * 判断 rule1 是否被 rule2 覆盖
523+ * 使用 PermissionChecker 的匹配逻辑来判断
524+ * @param rule1 旧规则
525+ * @param rule2 新规则
526+ */
527+ private isRuleCoveredBy ( rule1 : string , rule2 : string ) : boolean {
528+ try {
529+ // 动态导入 PermissionChecker
530+ const { PermissionChecker } = require ( './PermissionChecker.js' ) ;
531+
532+ // 创建只包含新规则的 checker
533+ const checker = new PermissionChecker ( {
534+ allow : [ rule2 ] ,
535+ ask : [ ] ,
536+ deny : [ ] ,
537+ } ) ;
538+
539+ // 从 rule1 解析出工具名和参数
540+ const toolName = this . extractToolNameFromRule ( rule1 ) ;
541+ if ( ! toolName ) return false ;
542+
543+ const params = this . extractParamsFromRule ( rule1 ) ;
544+
545+ // 构造描述符
546+ const descriptor = {
547+ toolName,
548+ params,
549+ affectedPaths : [ ] ,
550+ } ;
551+
552+ // 检查 rule1 是否匹配 rule2
553+ const checkResult = checker . check ( descriptor ) ;
554+ return checkResult . result === 'allow' ;
555+ } catch ( error ) {
556+ // 解析失败,保守处理:不删除
557+ console . warn (
558+ `[ConfigManager] 无法判断规则覆盖关系: ${ error instanceof Error ? error . message : '未知错误' } `
559+ ) ;
560+ return false ;
561+ }
562+ }
563+
564+ /**
565+ * 从规则字符串中提取工具名
566+ */
567+ private extractToolNameFromRule ( rule : string ) : string | null {
568+ const match = rule . match ( / ^ ( [ A - Z a - z 0 - 9 _ ] + ) ( \( | $ ) / ) ;
569+ return match ? match [ 1 ] : null ;
570+ }
571+
572+ /**
573+ * 从规则字符串中提取参数
574+ */
575+ private extractParamsFromRule ( rule : string ) : Record < string , unknown > {
576+ const match = rule . match ( / \( ( .* ) \) $ / ) ;
577+ if ( ! match ) return { } ;
578+
579+ const paramString = match [ 1 ] ;
580+ const params : Record < string , unknown > = { } ;
581+
582+ // 简单解析参数(key:value 格式)
583+ const parts = this . smartSplitParams ( paramString ) ;
584+ for ( const part of parts ) {
585+ const colonIndex = part . indexOf ( ':' ) ;
586+ if ( colonIndex > 0 ) {
587+ const key = part . slice ( 0 , colonIndex ) . trim ( ) ;
588+ const value = part . slice ( colonIndex + 1 ) . trim ( ) ;
589+ params [ key ] = value ;
590+ }
591+ }
592+
593+ return params ;
594+ }
595+
596+ /**
597+ * 智能分割参数字符串(处理嵌套括号)
598+ */
599+ private smartSplitParams ( str : string ) : string [ ] {
600+ const result : string [ ] = [ ] ;
601+ let current = '' ;
602+ let braceDepth = 0 ;
603+ let parenDepth = 0 ;
604+
605+ for ( let i = 0 ; i < str . length ; i ++ ) {
606+ const char = str [ i ] ;
607+
608+ if ( char === '{' ) braceDepth ++ ;
609+ else if ( char === '}' ) braceDepth -- ;
610+ else if ( char === '(' ) parenDepth ++ ;
611+ else if ( char === ')' ) parenDepth -- ;
612+
613+ if ( char === ',' && braceDepth === 0 && parenDepth === 0 ) {
614+ result . push ( current . trim ( ) ) ;
615+ current = '' ;
616+ } else {
617+ current += char ;
618+ }
619+ }
620+
621+ if ( current ) {
622+ result . push ( current . trim ( ) ) ;
623+ }
624+
625+ return result ;
626+ }
627+
500628 /**
501629 * 设置权限模式
502630 * @param mode 目标权限模式
0 commit comments