@sdsrs/code-graph 0.7.14 → 0.7.16
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/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/hooks/hooks.json +14 -2
- package/claude-plugin/scripts/auto-update.js +16 -1
- package/claude-plugin/scripts/lifecycle.e2e.test.js +5 -1
- package/claude-plugin/scripts/lifecycle.js +95 -14
- package/claude-plugin/scripts/mcp-launcher.js +2 -1
- package/claude-plugin/scripts/pre-edit-guide.js +5 -1
- package/claude-plugin/scripts/pre-edit-guide.test.js +161 -0
- package/claude-plugin/scripts/session-init.js +24 -2
- package/claude-plugin/scripts/statusline-composite.js +2 -2
- package/claude-plugin/scripts/user-prompt-context.js +90 -81
- package/claude-plugin/scripts/user-prompt-context.test.js +466 -0
- package/claude-plugin/skills/explore.md +22 -0
- package/claude-plugin/skills/index.md +24 -0
- package/package.json +6 -6
- package/claude-plugin/commands/impact.md +0 -9
- package/claude-plugin/commands/rebuild.md +0 -7
- package/claude-plugin/commands/status.md +0 -5
- package/claude-plugin/commands/trace.md +0 -12
- package/claude-plugin/commands/understand.md +0 -12
- package/claude-plugin/skills/code-navigation.md +0 -20
|
@@ -69,7 +69,7 @@ const cwd = process.cwd();
|
|
|
69
69
|
const dbPath = path.join(cwd, '.code-graph', 'index.db');
|
|
70
70
|
if (!fs.existsSync(dbPath)) process.exit(0);
|
|
71
71
|
|
|
72
|
-
// ---
|
|
72
|
+
// --- Pure logic (exported for testing) ---
|
|
73
73
|
|
|
74
74
|
const STOP_WORDS = new Set([
|
|
75
75
|
'this', 'that', 'with', 'from', 'what', 'when', 'which', 'there',
|
|
@@ -79,100 +79,109 @@ const STOP_WORDS = new Set([
|
|
|
79
79
|
'being', 'through', 'default', 'function', 'method', 'class',
|
|
80
80
|
]);
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
const PLAIN_WORD_EXCLUDE = /^(possible|together|actually|something|different|important|following|available|necessary|currently|implement|operation|otherwise|beginning|knowledge|attention|according|certainly|sometimes|direction|recommend|structure|describe|question|complete|generate|anything|continue|consider|response|approach|happened|recently|probably|expected|previous|original|specific|directly|received|required|supposed|separate|designed|finished|provided|included|prepared|combined|properly|remember|whatever|although|document|handling|existing|everyone|standard|research|personal|relative|absolute|practice|language|thousand|national|evidence|refactor|understand|validate|analysis|debugging|configure|improving|resolving|creating|building|checking|updating|removing|changing|searching|cleaning|optimize|migration|overview|introduce|reviewing|thinking|managing|starting|yourself|features|problems|breaking|requires|argument|settings|includes|examples|comments|patterns|tutorial|concepts|supports|priority|organize|scenario|tracking|internal|external|abstract|concrete|strategy|evaluate|diagnose|platform|variable|optional|multiple)$/;
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
const trimmed =
|
|
86
|
-
if (/^(yes|no|ok|commit|push|y|n|done|thanks|thank you|继续|确认|好的|好|是的|不|可以|行|对|提交|推送|没问题|谢谢|发布|更新|编译|安装|卸载|重启|重连|清理)\s*[.!?。!?]?\s*$/i.test(trimmed))
|
|
87
|
-
|
|
84
|
+
function shouldSkip(msg) {
|
|
85
|
+
const trimmed = msg.trim();
|
|
86
|
+
if (/^(yes|no|ok|commit|push|y|n|done|thanks|thank you|继续|确认|好的|好|是的|不|可以|行|对|提交|推送|没问题|谢谢|发布|更新|编译|安装|卸载|重启|重连|清理)\s*[.!?。!?]?\s*$/i.test(trimmed)) return 'simple';
|
|
87
|
+
if (/^(修复|实施|执行|开始|按|实测|进入|用|重新)/.test(trimmed) && !/[a-zA-Z_]{3,}/.test(trimmed)) return 'action-only';
|
|
88
|
+
return false;
|
|
88
89
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
|
|
91
|
+
function extractFilePaths(msg) {
|
|
92
|
+
return (msg.match(/(?:src|lib|test|pkg|cmd|internal|app|components?)\/[\w/.-]+/g) || []).slice(0, 2);
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
function extractSymbols(msg) {
|
|
96
|
+
const candidates = (msg.match(/\b(?:[A-Z]\w*(?:(?:::|\.)\w+)+|[a-z]\w*(?:_\w+){1,}|[a-z]\w*(?:[A-Z]\w*)+|[A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/g) || [])
|
|
97
|
+
.filter(s => s.length > 4)
|
|
98
|
+
.filter(s => !STOP_WORDS.has(s.toLowerCase()))
|
|
99
|
+
.slice(0, 3);
|
|
100
|
+
|
|
101
|
+
if (candidates.length === 0) {
|
|
102
|
+
const backtickSymbols = (msg.match(/`([a-zA-Z_]\w{2,})`/g) || [])
|
|
103
|
+
.map(s => s.replace(/`/g, ''))
|
|
104
|
+
.filter(s => s.length >= 3 && !STOP_WORDS.has(s.toLowerCase()));
|
|
105
|
+
candidates.push(...backtickSymbols.slice(0, 3));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let lowConfidence = false;
|
|
109
|
+
if (candidates.length === 0) {
|
|
110
|
+
const plain = (msg.match(/\b[a-z][a-z]{7,}\b/g) || [])
|
|
111
|
+
.filter(s => !STOP_WORDS.has(s))
|
|
112
|
+
.filter(s => !PLAIN_WORD_EXCLUDE.test(s));
|
|
113
|
+
candidates.push(...plain.slice(0, 2));
|
|
114
|
+
if (candidates.length > 0) lowConfidence = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { symbols: candidates, lowConfidence };
|
|
110
118
|
}
|
|
111
119
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
120
|
+
function detectIntents(msg) {
|
|
121
|
+
return {
|
|
122
|
+
impact: /(?:impact|影响|修改前|改之前|blast radius|before (?:edit|chang|modif)|risk|风险|改动范围|波及|问题在|bug|干扰|冲突|卡)/i.test(msg),
|
|
123
|
+
modify: /(?:改(?!变)|修改|修复|重构|优化|简化|精简|适配|统一|修正|调整|去掉|整理|清理|解耦|更新|\brefactor\b|\bchange\b|\brename\b|\bfix\b|移动|\bmove\b|删(?!除文件)|\bremove\b|替换|\breplace\b|\bupdate\b|升级|\bmigrate\b|迁移|拆分|\bsplit\b|合并|\bmerge\b|提取|\bextract\b|改成|改为|换成|转为|异步|同步)/i.test(msg),
|
|
124
|
+
implement: /(?:\badd\b|\bimplement\b|\bcreate\b|\bbuild\b|\bwrite\b|新增|添加|实现|创建|编写|开发|增加|加上|加个|写|做个|搭建|补充|引入|支持|封装|接入|对接|配置)/i.test(msg),
|
|
125
|
+
understand: /(?:how does|怎么工作|怎么实现|怎么做|什么|理解|看看|看一下|了解|分析|explain|understand|架构|architecture|structure|overview|模块|概览|干什么|做什么|工作原理|逻辑|机制|流程|功能|结合度|效率|评估|调研|是什么|有什么|能用不|高效不|达标|起作用|科学|深入思考|源码|检查|审核|审查|验证|诊断)/i.test(msg),
|
|
126
|
+
callgraph: /(?:who calls|what calls|调用|call(?:graph|er|ee)|trace|链路|追踪|谁调|被谁调|调了谁|上下游|依赖关系|触发|路径|覆盖|介入)/i.test(msg),
|
|
127
|
+
search: /(?:where is|在哪|find|search|搜索|找|locate|哪里用|哪里定义|定义在|实现在|处理没|在源码|加不加)/i.test(msg),
|
|
128
|
+
};
|
|
121
129
|
}
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
if (
|
|
134
|
-
|
|
131
|
+
function determineQueryType(intents, symbols, filePaths, isCoolingDownFn) {
|
|
132
|
+
const hasStrict = symbols.symbols.length > 0 && !symbols.lowConfidence;
|
|
133
|
+
const hasQualified = symbols.symbols.some(s => s.includes('::'));
|
|
134
|
+
const hasAny = intents.impact || intents.modify || intents.implement || intents.understand || intents.callgraph || intents.search;
|
|
135
|
+
|
|
136
|
+
// Gate: need intent, qualified symbol, file path, or any symbol
|
|
137
|
+
if (!hasAny && !hasQualified && filePaths.length === 0 && symbols.symbols.length === 0) return null;
|
|
138
|
+
|
|
139
|
+
const cd = isCoolingDownFn || (() => false);
|
|
140
|
+
|
|
141
|
+
if ((intents.impact || intents.modify) && hasStrict && !cd('impact')) return { type: 'impact', symbol: symbols.symbols[0] };
|
|
142
|
+
if (intents.callgraph && hasStrict && !cd('callgraph')) return { type: 'callgraph', symbol: symbols.symbols[0] };
|
|
143
|
+
if (filePaths.length > 0 && !cd('overview')) return { type: 'overview', path: filePaths[0].replace(/\/[^/]+$/, '/') };
|
|
144
|
+
if ((intents.search || intents.implement || hasQualified) && symbols.symbols.length > 0 && !cd('search')) return { type: 'search', symbol: symbols.symbols[0] };
|
|
145
|
+
if ((intents.understand || !hasAny) && symbols.symbols.length > 0 && !cd('search')) return { type: 'search', symbol: symbols.symbols[0] };
|
|
146
|
+
|
|
147
|
+
return null;
|
|
135
148
|
}
|
|
136
149
|
|
|
137
|
-
// ---
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
overview: '[code-graph:structure] Module structure:',
|
|
141
|
-
callgraph: '[code-graph:callgraph] Call relationships:',
|
|
142
|
-
search: '[code-graph:search] Relevant code:',
|
|
143
|
-
};
|
|
150
|
+
// --- Main execution (only when run directly) ---
|
|
151
|
+
if (require.main === module) {
|
|
152
|
+
if (shouldSkip(message)) process.exit(0);
|
|
144
153
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
result =
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
result = run('code-graph-mcp', ['
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
result
|
|
154
|
+
const filePaths = extractFilePaths(message);
|
|
155
|
+
const symbols = extractSymbols(message);
|
|
156
|
+
const intents = detectIntents(message);
|
|
157
|
+
const query = determineQueryType(intents, symbols, filePaths, isCoolingDown);
|
|
158
|
+
|
|
159
|
+
if (!query) process.exit(0);
|
|
160
|
+
|
|
161
|
+
const PREFIXES = {
|
|
162
|
+
impact: '[code-graph:impact] Blast radius — review before editing:',
|
|
163
|
+
overview: '[code-graph:structure] Module structure:',
|
|
164
|
+
callgraph: '[code-graph:callgraph] Call relationships:',
|
|
165
|
+
search: '[code-graph:search] Relevant code:',
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
let result = '';
|
|
170
|
+
if (query.type === 'impact') result = run('code-graph-mcp', ['impact', query.symbol]);
|
|
171
|
+
else if (query.type === 'callgraph') result = run('code-graph-mcp', ['callgraph', query.symbol, '--depth', '2']);
|
|
172
|
+
else if (query.type === 'overview') result = run('code-graph-mcp', ['overview', query.path]);
|
|
173
|
+
else if (query.type === 'search') result = run('code-graph-mcp', ['search', query.symbol, '--limit', '8']);
|
|
174
|
+
|
|
175
|
+
if (result && result.trim()) {
|
|
176
|
+
markCooldown(query.type);
|
|
177
|
+
process.stdout.write(`${PREFIXES[query.type]}\n${result.trim()}\n`);
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
process.exit(0);
|
|
167
181
|
}
|
|
168
|
-
} catch {
|
|
169
|
-
process.exit(0);
|
|
170
182
|
}
|
|
171
183
|
|
|
172
|
-
|
|
173
|
-
markCooldown(queryType);
|
|
174
|
-
process.stdout.write(`${PREFIXES[queryType]}\n${result.trim()}\n`);
|
|
175
|
-
}
|
|
184
|
+
module.exports = { shouldSkip, extractFilePaths, extractSymbols, detectIntents, determineQueryType, STOP_WORDS, PLAIN_WORD_EXCLUDE };
|
|
176
185
|
|
|
177
186
|
// --- Helpers ---
|
|
178
187
|
|