@jokeran/frontend-code-skimmer 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -37
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,24 +11,23 @@ AI 代码助手的智能代码索引引擎,支持 **Vue 2 / Vue 3 / React Hook
|
|
|
11
11
|
- **不知道函数名**:`skimmer_find_by_behavior` 按行为搜索,搜 `storage` 直接列出所有操作 localStorage 的函数
|
|
12
12
|
- **同名函数串台**:`skimmer_find_symbol` / `skimmer_find_by_behavior` / `skimmer_get_call_graph` 支持 `file_path` 过滤
|
|
13
13
|
- **修改前评估影响**:`skimmer_get_blast_radius` 告诉你改一个函数会影响哪些地方
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
|
19
|
-
|
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `skimmer_get_project_overview` | 项目整体概览 |
|
|
14
|
+
- **变量数据流追踪**:`skimmer_trace_data_lifecycle` 一次调用聚合声明、属性修改、参数传递、Storage 落点 4 个维度
|
|
15
|
+
|
|
16
|
+
## 10 个 MCP 工具
|
|
17
|
+
|
|
18
|
+
| 工具 | 用途 |
|
|
19
|
+
| -------------------------------- | -------------------------------------------- |
|
|
20
|
+
| `skimmer_index_project` | 初始化/更新代码索引 |
|
|
21
|
+
| `skimmer_get_component_outline` | ⭐ 获取文件骨架(极致 Token 节省) |
|
|
22
|
+
| `skimmer_find_symbol` | 智能符号搜索(支持拼写错误) |
|
|
23
|
+
| `skimmer_get_code_slice` | 精准代码切片(只取一个函数) |
|
|
24
|
+
| `skimmer_trace_assignments` | 变量赋值链路追踪(轻量,降级工具) |
|
|
25
|
+
| `skimmer_trace_property_changes` | 对象属性修改追踪(轻量,降级工具) |
|
|
26
|
+
| `skimmer_find_by_behavior` | ⭐ 按行为标签搜索(绕过命名问题) |
|
|
27
|
+
| `skimmer_trace_data_lifecycle` | ⭐ 变量完整生命周期追踪(首选,一键聚合) |
|
|
28
|
+
| `skimmer_get_call_graph` | 函数调用关系图谱 |
|
|
29
|
+
| `skimmer_get_blast_radius` | 修改影响范围评估 |
|
|
30
|
+
| `skimmer_get_project_overview` | 项目整体概览 |
|
|
32
31
|
|
|
33
32
|
## 安装
|
|
34
33
|
|
|
@@ -49,7 +48,7 @@ npm run build
|
|
|
49
48
|
"frontend-code-skimmer": {
|
|
50
49
|
"command": "node",
|
|
51
50
|
"args": ["/path/to/frontend-code-skimmer/dist/index.js"],
|
|
52
|
-
"autoApprove": ["skimmer_index_project", "skimmer_get_component_outline", "skimmer_find_symbol", "skimmer_get_code_slice", "skimmer_trace_assignments", "skimmer_trace_property_changes", "skimmer_find_by_behavior", "
|
|
51
|
+
"autoApprove": ["skimmer_index_project", "skimmer_get_component_outline", "skimmer_find_symbol", "skimmer_get_code_slice", "skimmer_trace_assignments", "skimmer_trace_property_changes", "skimmer_find_by_behavior", "skimmer_trace_data_lifecycle", "skimmer_get_call_graph", "skimmer_get_blast_radius", "skimmer_get_project_overview"]
|
|
53
52
|
}
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -125,19 +124,24 @@ skimmer_index_project({ project_path: "/work/project-a", force: true })
|
|
|
125
124
|
→ 列出所有写入 localStorage 的函数,带文件位置
|
|
126
125
|
```
|
|
127
126
|
|
|
128
|
-
### 5.
|
|
127
|
+
### 5. ⭐ 变量完整数据流追踪(首选)
|
|
129
128
|
|
|
130
129
|
```
|
|
131
|
-
调用
|
|
132
|
-
|
|
130
|
+
调用 skimmer_trace_data_lifecycle({
|
|
131
|
+
variable: "storageParams",
|
|
133
132
|
file_path: "src/views/apply/index.vue",
|
|
134
133
|
storage_api: "localStorage",
|
|
135
134
|
max_depth: 2
|
|
136
135
|
})
|
|
137
|
-
→
|
|
136
|
+
→ 一次返回 5 个维度:
|
|
137
|
+
① 声明 & 整体赋值(含来源分层:初始化/缓存回显/用户交互)
|
|
138
|
+
② 属性修改(object.prop = xxx,含别名展开)
|
|
139
|
+
③ 作为参数被传入的调用
|
|
140
|
+
④ template 绑定 / watch / computed / return 中的读取引用
|
|
141
|
+
⑤ Storage 落点追踪(自动顺着调用链打通到 localStorage 写入)
|
|
138
142
|
```
|
|
139
143
|
|
|
140
|
-
### 6.
|
|
144
|
+
### 6. 追踪变量赋值与属性修改(轻量降级)
|
|
141
145
|
|
|
142
146
|
```
|
|
143
147
|
skimmer_trace_assignments({ variable: "changeResult", file_path: "src/views/apply/index.vue" })
|
|
@@ -146,22 +150,14 @@ skimmer_trace_property_changes({ object: "params", property: "receiveAdr", file_
|
|
|
146
150
|
|
|
147
151
|
说明:
|
|
148
152
|
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
### 7. 追踪接口调用
|
|
152
|
-
|
|
153
|
-
```
|
|
154
|
-
skimmer_trace_api_calls({ file_path: "src/views/apply/index.vue", keyword: "/queryDetailForC" })
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
说明:
|
|
158
|
-
|
|
159
|
-
- 支持识别封装网络调用(如 `this.aksCommonHandle(...)`、`xxxApi.request(...)`)并归类为 `network`。
|
|
153
|
+
- 绝大多数场景推荐优先用 `skimmer_trace_data_lifecycle`,上面两个工具仅在只需查看单一维度时使用。
|
|
154
|
+
- `skimmer_trace_assignments` 使用 **AST 优先**(正则兜底),可稳定识别 `this.xxx = ...`、`obj[prop] = ...` 等赋值。
|
|
160
155
|
|
|
161
|
-
###
|
|
156
|
+
### 7. 函数调用图谱 & 影响评估
|
|
162
157
|
|
|
163
158
|
```
|
|
164
|
-
|
|
159
|
+
skimmer_get_call_graph({ symbol_name: "saveToCache", direction: "both" })
|
|
160
|
+
skimmer_get_blast_radius({ symbol_name: "saveToCache" })
|
|
165
161
|
```
|
|
166
162
|
|
|
167
163
|
## 框架支持
|
package/dist/index.js
CHANGED
|
@@ -303,7 +303,7 @@ file_path \u4F7F\u7528\u76F8\u5BF9\u4E8E\u9879\u76EE\u6839\u76EE\u5F55\u7684\u8D
|
|
|
303
303
|
skimmer_index_project({ project_path: "${n||"<\u9879\u76EE\u8DEF\u5F84>"}" })`}]};let c=new Map;for(let p of i){let f=p.symbol_id;c.has(f)||c.set(f,[]);let m=`[${p.category}:${p.api_name}.${p.operation}${p.detail?`("${p.detail}")`:""}]`,g=c.get(f);g.includes(m)||g.push(m);}let a=[];a.push(`\u{1F4C4} ${t} (${s.line_count} \u884C, ${s.framework})`),a.push(`${"\u2501".repeat(50)}`);let u={};for(let p of o){if(p.name==="<template>")continue;let f=p.category;u[f]||(u[f]=[]),u[f].push(p);}let d=[{key:"prop",icon:"\u{1F4E6}",label:"Props"},{key:"state",icon:"\u{1F4CA}",label:"Data / State"},{key:"computed",icon:"\u{1F504}",label:"Computed"},{key:"method",icon:"\u26A1",label:"Methods / Functions"},{key:"effect",icon:"\u{1F440}",label:"Watch / Effect"},{key:"lifecycle",icon:"\u{1F517}",label:"Lifecycle"},{key:"hook",icon:"\u{1FA9D}",label:"Hooks"},{key:"component",icon:"\u{1F9E9}",label:"Components"},{key:"mixin",icon:"\u{1F500}",label:"Mixins"},{key:"provide",icon:"\u{1F489}",label:"Provide / Inject"},{key:"emit",icon:"\u{1F4E1}",label:"Emits"},{key:"filter",icon:"\u{1F50D}",label:"Filters"},{key:"function",icon:"\u{1F527}",label:"Functions"},{key:"constant",icon:"\u{1F4CC}",label:"Constants"}];for(let{key:p,icon:f,label:m}of d){let g=u[p];if(g?.length){a.push(`
|
|
304
304
|
${f} ${m} (${g.length}):`);for(let h of g){let N=h.name,y=ce(h.start_line,h.end_line),b=ue(h.params_json,[]),S=b.length>0?`(${b.join(", ")})`:"",v=h.default_value?` = ${h.default_value}`:"",T=h.is_async?"async ":"",P=c.get(h.id),I=P?.length?` ${P.slice(0,3).join(" ")}`:"",O=h.state_setter_name?` \u2192 ${h.state_setter_name}`:"",ie=h.hook_deps_json?` [deps:${ue(h.hook_deps_json,[]).join(",")}]`:"";a.push(` \u2022 ${T}${N}${S}${v}${O}${ie} ${y}${I}`);}}}return a.push(`
|
|
305
305
|
${"\u2500".repeat(50)}`),a.push(`\u{1F4A1} \u67E5\u770B\u5177\u4F53\u4EE3\u7801: skimmer_get_code_slice({ file_path: "${t}", symbol_name: "\u65B9\u6CD5\u540D" })`),{content:[{type:"text",text:a.join(`
|
|
306
|
-
`)}]}});}w();w();J();G();var ge=class{constructor(e){this.db=e;}search(e,t){let{category:n,framework:r,filePath:s,limit:o=bt,fuzzy:i=true}=t||{},c=new Map,a=this.db.exactSearch(e,n,r,s);for(let g of a){let h=g.id;c.set(h,{symbol:g,filePath:g.file_path,score:100,matchReason:"\u7CBE\u786E\u5339\u914D"});}if(c.size>=o)return this.rankResults(c,o);let u=A(e),d=[e,u,...u.split(" ")].filter(Boolean);for(let g of d){if(g.length<2)continue;let h=this.db.ftsSearch(g,n,r,s);for(let N of h){let y=N.id;if(c.has(y))continue;let b=this.calcFtsScore(e,N.name);c.set(y,{symbol:N,filePath:N.file_path,score:b,matchReason:`FTS5 \u5168\u6587\u5339\u914D (${g})`});}}if(!i||c.size>=o)return this.rankResults(c,o);let p=this.db.getAllSymbolNames(s),f=e.toLowerCase(),m=A(e).split(" ");for(let{name:g,file_path:h,id:N}of p){if(c.has(N))continue;let y=_e(f,g.toLowerCase());if(y<=Et){let v=Math.max(0,80-y*15),T=this.db.getSymbolById(N);if(!T||n&&T.category!==n||r&&T.framework!==r)continue;let P=ut(e,g,y);c.set(N,{symbol:T,filePath:h,score:v,matchReason:`\u6A21\u7CCA\u5339\u914D (\u7F16\u8F91\u8DDD\u79BB=${y})`,spellingSuggestion:P});continue}let b=A(g).split(" "),S=m.filter(v=>b.some(T=>dt(v,T)>=75));if(S.length>0&&S.length>=Math.ceil(m.length*.6)){let v=50+S.length*10,T=this.db.getSymbolById(N);if(!T||n&&T.category!==n||r&&T.framework!==r)continue;c.set(N,{symbol:T,filePath:h,score:v,matchReason:`\u5355\u8BCD\u5339\u914D (${S.join(", ")})`});}}return this.rankResults(c,o)}searchByBehavior(e){return this.db.behaviorSearch(e.category,e.apiName,e.operation,e.keyword,e.filePath).map(n=>({symbolName:n.symbol_name,category:n.category,apiName:n.api_name,operation:n.operation,detail:n.detail,line:n.line_number,filePath:n.file_path,startLine:n.start_line,endLine:n.end_line}))}calcFtsScore(e,t){let n=e.toLowerCase(),r=t.toLowerCase();if(r===n)return 98;if(r.startsWith(n))return 88;if(r.includes(n))return 78;let s=A(e).split(" "),o=A(t).split(" ");return 60+s.filter(c=>o.some(a=>a.startsWith(c))).length*8}rankResults(e,t){return Array.from(e.values()).sort((n,r)=>r.score-n.score).slice(0,t)}};J();var Mt=createRequire(import.meta.url),Z="\u9879\u76EE\u6839\u76EE\u5F55\uFF08\u53EF\u7701\u7565\uFF0C\u81EA\u52A8\u4F7F\u7528\u4E0A\u6B21 skimmer_index_project \u6307\u5B9A\u7684\u9879\u76EE\uFF09";function he(l){return l.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function pe(l,e,t){let n=[];for(let r of l){if(!r?.abs_path||!W.existsSync(r.abs_path))continue;let s=W.readFileSync(r.abs_path,"utf-8").split(`
|
|
306
|
+
`)}]}});}w();w();J();G();var ge=class{constructor(e){this.db=e;}db;search(e,t){let{category:n,framework:r,filePath:s,limit:o=bt,fuzzy:i=true}=t||{},c=new Map,a=this.db.exactSearch(e,n,r,s);for(let g of a){let h=g.id;c.set(h,{symbol:g,filePath:g.file_path,score:100,matchReason:"\u7CBE\u786E\u5339\u914D"});}if(c.size>=o)return this.rankResults(c,o);let u=A(e),d=[e,u,...u.split(" ")].filter(Boolean);for(let g of d){if(g.length<2)continue;let h=this.db.ftsSearch(g,n,r,s);for(let N of h){let y=N.id;if(c.has(y))continue;let b=this.calcFtsScore(e,N.name);c.set(y,{symbol:N,filePath:N.file_path,score:b,matchReason:`FTS5 \u5168\u6587\u5339\u914D (${g})`});}}if(!i||c.size>=o)return this.rankResults(c,o);let p=this.db.getAllSymbolNames(s),f=e.toLowerCase(),m=A(e).split(" ");for(let{name:g,file_path:h,id:N}of p){if(c.has(N))continue;let y=_e(f,g.toLowerCase());if(y<=Et){let v=Math.max(0,80-y*15),T=this.db.getSymbolById(N);if(!T||n&&T.category!==n||r&&T.framework!==r)continue;let P=ut(e,g,y);c.set(N,{symbol:T,filePath:h,score:v,matchReason:`\u6A21\u7CCA\u5339\u914D (\u7F16\u8F91\u8DDD\u79BB=${y})`,spellingSuggestion:P});continue}let b=A(g).split(" "),S=m.filter(v=>b.some(T=>dt(v,T)>=75));if(S.length>0&&S.length>=Math.ceil(m.length*.6)){let v=50+S.length*10,T=this.db.getSymbolById(N);if(!T||n&&T.category!==n||r&&T.framework!==r)continue;c.set(N,{symbol:T,filePath:h,score:v,matchReason:`\u5355\u8BCD\u5339\u914D (${S.join(", ")})`});}}return this.rankResults(c,o)}searchByBehavior(e){return this.db.behaviorSearch(e.category,e.apiName,e.operation,e.keyword,e.filePath).map(n=>({symbolName:n.symbol_name,category:n.category,apiName:n.api_name,operation:n.operation,detail:n.detail,line:n.line_number,filePath:n.file_path,startLine:n.start_line,endLine:n.end_line}))}calcFtsScore(e,t){let n=e.toLowerCase(),r=t.toLowerCase();if(r===n)return 98;if(r.startsWith(n))return 88;if(r.includes(n))return 78;let s=A(e).split(" "),o=A(t).split(" ");return 60+s.filter(c=>o.some(a=>a.startsWith(c))).length*8}rankResults(e,t){return Array.from(e.values()).sort((n,r)=>r.score-n.score).slice(0,t)}};J();var Mt=createRequire(import.meta.url),Z="\u9879\u76EE\u6839\u76EE\u5F55\uFF08\u53EF\u7701\u7565\uFF0C\u81EA\u52A8\u4F7F\u7528\u4E0A\u6B21 skimmer_index_project \u6307\u5B9A\u7684\u9879\u76EE\uFF09";function he(l){return l.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function pe(l,e,t){let n=[];for(let r of l){if(!r?.abs_path||!W.existsSync(r.abs_path))continue;let s=W.readFileSync(r.abs_path,"utf-8").split(`
|
|
307
307
|
`);for(let o=0;o<s.length;o++){let i=s[o];for(let c of e)if(c.regex.test(i)){n.push({filePath:r.path,line:o+1,kind:c.kind,text:i.trim()});break}if(n.length>=t)return n}}return n}function Ct(l,e){let t=he(l),n="(?:=|\\+=|-=|\\*=|/=|%=|\\|=|&=|\\^=|&&=|\\|\\|=|\\?\\?=)",r=[{kind:"declaration",regex:new RegExp(`\\b(?:const|let|var)\\s+${t}\\b`)},{kind:"assignment",regex:new RegExp(`\\b${t}\\b\\s*${n}`)},{kind:"update",regex:new RegExp(`(?:\\+\\+|--)\\s*\\b${t}\\b|\\b${t}\\b\\s*(?:\\+\\+|--)`)}];return e&&r.push({kind:"property_assignment",regex:new RegExp(`\\b${t}\\.[A-Za-z_$][\\w$]*\\s*${n}`)},{kind:"indexed_assignment",regex:new RegExp(`\\b${t}\\s*\\[[^\\]]+\\]\\s*${n}`)}),r}function oe(l,e){if(!(!l||typeof l!="object")){e(l);for(let t of Object.keys(l)){if(t==="type"||t==="loc"||t==="start"||t==="end")continue;let n=l[t];if(Array.isArray(n))for(let r of n)r&&typeof r=="object"&&r.type&&oe(r,e);else n&&typeof n=="object"&&n.type&&oe(n,e);}}}function Me(l){try{return Mt("@babel/parser").parse(l,{sourceType:"module",plugins:["typescript","jsx","decorators-legacy"],errorRecovery:!0})}catch{return null}}function De(l,e){if(!l.endsWith(".vue"))return [{content:e,lineOffset:1}];try{let{parse:s}=Mt("@vue/compiler-sfc"),{descriptor:o}=s(e),i=[];if(o.script?.content&&i.push({content:o.script.content,lineOffset:o.script.loc.start.line}),o.scriptSetup?.content&&i.push({content:o.scriptSetup.content,lineOffset:o.scriptSetup.loc.start.line}),i.length>0)return i}catch{}let t=e.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);if(!t||!t[1])return [];let r=e.slice(0,t.index||0).split(`
|
|
308
308
|
`).length;return [{content:t[1],lineOffset:r}]}function ee(l,e){return (l.loc?.start?.line??1)+e-1}function se(l){let e=l.property;return e?e.type==="Identifier"?String(e.name||""):e.type==="Literal"?String(e.value||""):null:null}function Pt(l,e){if(l.type!=="MemberExpression")return false;let t=l.object;return !t||t.type!=="ThisExpression"?false:se(l)===e}function nt(l,e){return !!l&&l.type==="Identifier"&&String(l.name||"")===e}function je(l){let e=l.object;return e?e.type==="Identifier"?String(e.name||""):e.type==="MemberExpression"&&e.object?.type==="ThisExpression"&&se(e)||null:null}function Dt(l){let e=new Set;return l.filter(t=>{let n=`${t.filePath}:${t.line}:${t.kind}:${t.text}`;return e.has(n)?false:(e.add(n),true)})}function rt(l,e,t=2){if(!W.existsSync(l))return {snippet:"",startLine:e};let n=W.readFileSync(l,"utf-8").split(`
|
|
309
309
|
`),r=Math.max(0,e-1-t),s=Math.min(n.length-1,e-1+t);return {snippet:n.slice(r,s+1).map((i,c)=>{let a=r+c+1;return ` ${a===e?"\u25B6":" "} ${String(a).padStart(4)} \u2502 ${i}`}).join(`
|
package/package.json
CHANGED