@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.
Files changed (3) hide show
  1. package/README.md +33 -37
  2. package/dist/index.js +1 -1
  3. 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
- ## 13 个 MCP 工具
16
-
17
- | 工具 | 用途 |
18
- | -------------------------------- | ---------------------------------- |
19
- | `skimmer_index_project` | 初始化/更新代码索引 |
20
- | `skimmer_get_component_outline` | ⭐ 获取文件骨架(极致 Token 节省) |
21
- | `skimmer_find_symbol` | 智能符号搜索(支持拼写错误) |
22
- | `skimmer_get_code_slice` | 精准代码切片(只取一个函数) |
23
- | `skimmer_trace_assignments` | 变量赋值链路追踪(轻量) |
24
- | `skimmer_trace_property_changes` | 对象属性修改追踪(轻量) |
25
- | `skimmer_find_by_behavior` | ⭐ 按行为标签搜索(绕过命名问题) |
26
- | `skimmer_trace_api_calls` | 网络 API 调用追踪 |
27
- | `skimmer_get_call_graph` | 函数调用关系图谱 |
28
- | `skimmer_trace_key_flow` | ⭐ 单工具 key 链路追踪 |
29
- | `skimmer_visualize_dataflow` | 赋值+调用+落点合并视图 |
30
- | `skimmer_get_blast_radius` | 修改影响范围评估 |
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", "skimmer_trace_api_calls", "skimmer_get_call_graph", "skimmer_trace_key_flow", "skimmer_visualize_dataflow", "skimmer_get_blast_radius", "skimmer_get_project_overview"]
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. 追踪某个 key 通过哪个调用链进入缓存
127
+ ### 5. 变量完整数据流追踪(首选)
129
128
 
130
129
  ```
131
- 调用 skimmer_trace_key_flow({
132
- key: "storageParams",
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
- 一次返回:调用入口 + 下游方法链 + storage 落点
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
- - `skimmer_trace_assignments` 已升级为 **AST 优先**(正则兜底),可更稳定识别 `this.xxx = ...`、`obj[prop] = ...` 等赋值。
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
- ### 8. 数据流总览(轻量)
156
+ ### 7. 函数调用图谱 & 影响评估
162
157
 
163
158
  ```
164
- skimmer_visualize_dataflow({ variable: "storageParams", file_path: "src/views/apply/index.vue", max_depth: 2 })
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jokeran/frontend-code-skimmer",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Frontend-Code-Skimmer: 不读全文,只看骨架;不搜字符,搜语义关联。支持 Vue2/Vue3/React Hooks",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/",