blast-radius-analyzer 1.2.1 → 1.3.1
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/.blast-radius-cache/file-states.json +10 -0
- package/dist/index.js +168 -1
- package/package.json +3 -6
- package/src/index.ts +199 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"/Users/didi/Documents/code3/logic-inverse-v2/blast-radius-analyzer/dist/index.js": {
|
|
3
|
+
"mtime": 1774934096510.6582,
|
|
4
|
+
"hash": "16fb35ae"
|
|
5
|
+
},
|
|
6
|
+
"/Users/didi/Documents/code3/logic-inverse-v2/blast-radius-analyzer/src/index.ts": {
|
|
7
|
+
"mtime": 1774934091686.9543,
|
|
8
|
+
"hash": "4feb087c"
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -30,6 +30,7 @@ function parseArgs(argv) {
|
|
|
30
30
|
let format = 'text';
|
|
31
31
|
let useCache = true;
|
|
32
32
|
let clearCache = false;
|
|
33
|
+
let autoDetect = false; // 自动检测 git diff
|
|
33
34
|
let threshold = undefined;
|
|
34
35
|
for (let i = 2; i < argv.length; i++) {
|
|
35
36
|
const arg = argv[i];
|
|
@@ -80,6 +81,9 @@ function parseArgs(argv) {
|
|
|
80
81
|
else if (arg === '--clear-cache') {
|
|
81
82
|
clearCache = true;
|
|
82
83
|
}
|
|
84
|
+
else if (arg === '--auto' || arg === '-a') {
|
|
85
|
+
autoDetect = true;
|
|
86
|
+
}
|
|
83
87
|
else if (arg === '--threshold' && argv[i + 1]) {
|
|
84
88
|
// 解析阈值: --threshold files:5,score:100,typeErrors:0
|
|
85
89
|
const thresholdStr = argv[++i];
|
|
@@ -115,6 +119,7 @@ function parseArgs(argv) {
|
|
|
115
119
|
format,
|
|
116
120
|
useCache,
|
|
117
121
|
clearCache,
|
|
122
|
+
autoDetect,
|
|
118
123
|
threshold: threshold,
|
|
119
124
|
};
|
|
120
125
|
}
|
|
@@ -132,6 +137,7 @@ Options:
|
|
|
132
137
|
-c, --change <file> File that changed (can be specified multiple times)
|
|
133
138
|
--symbol <name> Specific symbol that changed (function/class name)
|
|
134
139
|
--type <type> Change type: add|modify|delete|rename (default: modify)
|
|
140
|
+
-a, --auto Auto-detect changes via git diff
|
|
135
141
|
--max-depth <n> Maximum analysis depth (default: 10)
|
|
136
142
|
-t, --include-tests Include test files in analysis
|
|
137
143
|
-o, --output <file> Output file (auto-detect format from extension)
|
|
@@ -151,6 +157,9 @@ CI/CD Examples:
|
|
|
151
157
|
blast-radius -p ./src -c src/api/task.ts --threshold files:3 -o result.json
|
|
152
158
|
|
|
153
159
|
Examples:
|
|
160
|
+
# Auto-detect all changes via git diff
|
|
161
|
+
blast-radius -p ./src --auto
|
|
162
|
+
|
|
154
163
|
# Analyze a single file change
|
|
155
164
|
blast-radius -p ./src -c src/api/user.ts
|
|
156
165
|
|
|
@@ -760,11 +769,169 @@ function formatHtml(scope, callChains, callStackView, typeFlowResult, dataFlowRe
|
|
|
760
769
|
</html>`;
|
|
761
770
|
return html;
|
|
762
771
|
}
|
|
772
|
+
/**
|
|
773
|
+
* 使用 git diff 自动检测改动的文件和符号
|
|
774
|
+
*/
|
|
775
|
+
async function autoDetectChanges(projectRoot) {
|
|
776
|
+
const { execSync } = await import('child_process');
|
|
777
|
+
try {
|
|
778
|
+
// 获取 staged + unstaged 的改动
|
|
779
|
+
const diffOutput = execSync('git diff --cached --name-only HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null || git diff --name-only 2>/dev/null', {
|
|
780
|
+
cwd: projectRoot,
|
|
781
|
+
encoding: 'utf8',
|
|
782
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
783
|
+
});
|
|
784
|
+
const changedFiles = diffOutput.trim().split('\n').filter(f => f.trim());
|
|
785
|
+
if (changedFiles.length === 0 || changedFiles[0] === '') {
|
|
786
|
+
// 没有 git 改动,检查工作区
|
|
787
|
+
const unstaged = execSync('git diff --name-only 2>/dev/null || echo ""', {
|
|
788
|
+
cwd: projectRoot,
|
|
789
|
+
encoding: 'utf8',
|
|
790
|
+
}).trim().split('\n').filter(f => f.trim());
|
|
791
|
+
if (unstaged.length === 0 || unstaged[0] === '') {
|
|
792
|
+
return [];
|
|
793
|
+
}
|
|
794
|
+
changedFiles.length = 0;
|
|
795
|
+
changedFiles.push(...unstaged);
|
|
796
|
+
}
|
|
797
|
+
// 过滤只保留 TypeScript/JavaScript 文件
|
|
798
|
+
const codeFiles = changedFiles.filter(f => /\.(ts|tsx|js|jsx)$/.test(f) && !f.includes('node_modules'));
|
|
799
|
+
if (codeFiles.length === 0) {
|
|
800
|
+
return [];
|
|
801
|
+
}
|
|
802
|
+
// 获取每个文件的详细 diff,包括函数/变量名
|
|
803
|
+
const changedSymbols = [];
|
|
804
|
+
for (const file of codeFiles) {
|
|
805
|
+
try {
|
|
806
|
+
// 获取文件的 diff
|
|
807
|
+
const fileDiff = execSync(`git diff HEAD -- "${file}" 2>/dev/null || git diff -- "${file}" 2>/dev/null || echo ""`, {
|
|
808
|
+
cwd: projectRoot,
|
|
809
|
+
encoding: 'utf8',
|
|
810
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
811
|
+
});
|
|
812
|
+
// 解析 diff 中的符号 (函数名、变量名等)
|
|
813
|
+
const symbols = parseDiffSymbols(fileDiff, file);
|
|
814
|
+
if (symbols.length > 0) {
|
|
815
|
+
for (const sym of symbols) {
|
|
816
|
+
changedSymbols.push({
|
|
817
|
+
file: path.resolve(projectRoot, file),
|
|
818
|
+
symbol: sym,
|
|
819
|
+
type: 'modify',
|
|
820
|
+
diff: fileDiff,
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
else {
|
|
825
|
+
// 如果无法解析符号,仍添加文件
|
|
826
|
+
changedSymbols.push({
|
|
827
|
+
file: path.resolve(projectRoot, file),
|
|
828
|
+
symbol: '', // 空符号表示分析整个文件
|
|
829
|
+
type: 'modify',
|
|
830
|
+
diff: fileDiff,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
catch {
|
|
835
|
+
// 单个文件出错继续处理其他的
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
return changedSymbols;
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
console.error('⚠️ 无法获取 git diff:', error.message);
|
|
842
|
+
return [];
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
/**
|
|
846
|
+
* 从 diff 中解析改动的符号名
|
|
847
|
+
*/
|
|
848
|
+
function parseDiffSymbols(diffContent, file) {
|
|
849
|
+
const symbols = [];
|
|
850
|
+
const lines = diffContent.split('\n');
|
|
851
|
+
// 匹配函数定义: +functionName, +const functionName, +export functionName
|
|
852
|
+
const functionPattern = /^[+]export\s+(?:async\s+)?(?:function\s+(\w+)|const\s+(\w+)|(\w+)\s*=)/;
|
|
853
|
+
// 匹配变量声明: +export const name, +export let name, +export var name
|
|
854
|
+
const constPattern = /^[+]export\s+(?:const|let|var)\s+(\w+)/;
|
|
855
|
+
// 匹配类型定义: +export type Name, +export interface Name
|
|
856
|
+
const typePattern = /^[+]export\s+(?:type|interface|class|enum)\s+(\w+)/;
|
|
857
|
+
// 匹配 class 定义: +class ClassName
|
|
858
|
+
const classPattern = /^[+]class\s+(\w+)/;
|
|
859
|
+
for (const line of lines) {
|
|
860
|
+
// 跳过 diff header
|
|
861
|
+
if (line.startsWith('@@') || line.startsWith('---') || line.startsWith('+++') || line.startsWith('diff')) {
|
|
862
|
+
continue;
|
|
863
|
+
}
|
|
864
|
+
// 匹配函数
|
|
865
|
+
let match = line.match(functionPattern);
|
|
866
|
+
if (match) {
|
|
867
|
+
const name = match[1] || match[2] || match[3];
|
|
868
|
+
if (name && !symbols.includes(name)) {
|
|
869
|
+
symbols.push(name);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// 匹配 const/let/var
|
|
873
|
+
match = line.match(constPattern);
|
|
874
|
+
if (match && match[1]) {
|
|
875
|
+
if (!symbols.includes(match[1])) {
|
|
876
|
+
symbols.push(match[1]);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
// 匹配类型
|
|
880
|
+
match = line.match(typePattern);
|
|
881
|
+
if (match && match[1]) {
|
|
882
|
+
if (!symbols.includes(match[1])) {
|
|
883
|
+
symbols.push(match[1]);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
// 匹配 class
|
|
887
|
+
match = line.match(classPattern);
|
|
888
|
+
if (match && match[1]) {
|
|
889
|
+
if (!symbols.includes(match[1])) {
|
|
890
|
+
symbols.push(match[1]);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return symbols;
|
|
895
|
+
}
|
|
763
896
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
764
897
|
async function main() {
|
|
765
898
|
const args = parseArgs(process.argv);
|
|
899
|
+
// 如果指定了 --auto,自动检测 git 改动
|
|
900
|
+
if (args.autoDetect) {
|
|
901
|
+
console.log('🔍 Auto-detecting changes via git diff...');
|
|
902
|
+
const detectedChanges = await autoDetectChanges(args.projectRoot);
|
|
903
|
+
if (detectedChanges.length === 0) {
|
|
904
|
+
console.log('✅ No code changes detected (or not a git repository)');
|
|
905
|
+
process.exit(0);
|
|
906
|
+
}
|
|
907
|
+
console.log(`📝 Found ${detectedChanges.length} changed symbol(s):\n`);
|
|
908
|
+
for (const change of detectedChanges) {
|
|
909
|
+
const relPath = path.relative(args.projectRoot, change.file);
|
|
910
|
+
const symDisplay = change.symbol ? `#${change.symbol}` : '(file)';
|
|
911
|
+
console.log(` • ${relPath}${symDisplay}`);
|
|
912
|
+
}
|
|
913
|
+
console.log('');
|
|
914
|
+
// 将检测到的改动合并到 args.changes
|
|
915
|
+
for (const change of detectedChanges) {
|
|
916
|
+
// 检查是否已存在相同的文件
|
|
917
|
+
const existing = args.changes.find(c => c.file === change.file);
|
|
918
|
+
if (existing) {
|
|
919
|
+
// 如果已有符号,保留;否则添加新符号
|
|
920
|
+
if (!existing.symbol && change.symbol) {
|
|
921
|
+
existing.symbol = change.symbol;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
args.changes.push({
|
|
926
|
+
file: change.file,
|
|
927
|
+
symbol: change.symbol || undefined,
|
|
928
|
+
type: change.type,
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
766
933
|
if (args.changes.length === 0) {
|
|
767
|
-
console.error('❌ Error: No changes specified. Use --change <file>');
|
|
934
|
+
console.error('❌ Error: No changes specified. Use --change <file> or --auto');
|
|
768
935
|
console.error(' Run with --help for usage information');
|
|
769
936
|
process.exit(1);
|
|
770
937
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "blast-radius-analyzer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Analyze code change impact and blast radius - 改动影响范围分析器",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,13 +24,10 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"repository": {
|
|
26
26
|
"type": "git",
|
|
27
|
-
"url": ""
|
|
27
|
+
"url": "https://github.com/huabuyu05100510/blast-radius-analyzer"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"ts-morph": "^25.0.0"
|
|
31
|
-
},
|
|
32
|
-
"devDependencies": {
|
|
33
|
-
"@types/node": "^20.0.0",
|
|
30
|
+
"ts-morph": "^25.0.0",
|
|
34
31
|
"typescript": "^5.4.0"
|
|
35
32
|
},
|
|
36
33
|
"engines": {
|
package/src/index.ts
CHANGED
|
@@ -39,6 +39,7 @@ interface CLIArgs {
|
|
|
39
39
|
format: 'json' | 'text' | 'html' | 'graph';
|
|
40
40
|
useCache: boolean;
|
|
41
41
|
clearCache: boolean;
|
|
42
|
+
autoDetect: boolean; // 自动检测 git diff
|
|
42
43
|
threshold?: {
|
|
43
44
|
files?: number;
|
|
44
45
|
score?: number;
|
|
@@ -57,6 +58,7 @@ function parseArgs(argv: string[]): CLIArgs {
|
|
|
57
58
|
let format: 'json' | 'text' | 'html' | 'graph' = 'text';
|
|
58
59
|
let useCache = true;
|
|
59
60
|
let clearCache = false;
|
|
61
|
+
let autoDetect = false; // 自动检测 git diff
|
|
60
62
|
let threshold: CLIArgs['threshold'] = undefined;
|
|
61
63
|
|
|
62
64
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -95,6 +97,8 @@ function parseArgs(argv: string[]): CLIArgs {
|
|
|
95
97
|
useCache = false;
|
|
96
98
|
} else if (arg === '--clear-cache') {
|
|
97
99
|
clearCache = true;
|
|
100
|
+
} else if (arg === '--auto' || arg === '-a') {
|
|
101
|
+
autoDetect = true;
|
|
98
102
|
} else if (arg === '--threshold' && argv[i + 1]) {
|
|
99
103
|
// 解析阈值: --threshold files:5,score:100,typeErrors:0
|
|
100
104
|
const thresholdStr = argv[++i];
|
|
@@ -128,6 +132,7 @@ function parseArgs(argv: string[]): CLIArgs {
|
|
|
128
132
|
format,
|
|
129
133
|
useCache,
|
|
130
134
|
clearCache,
|
|
135
|
+
autoDetect,
|
|
131
136
|
threshold: threshold,
|
|
132
137
|
};
|
|
133
138
|
}
|
|
@@ -146,6 +151,7 @@ Options:
|
|
|
146
151
|
-c, --change <file> File that changed (can be specified multiple times)
|
|
147
152
|
--symbol <name> Specific symbol that changed (function/class name)
|
|
148
153
|
--type <type> Change type: add|modify|delete|rename (default: modify)
|
|
154
|
+
-a, --auto Auto-detect changes via git diff
|
|
149
155
|
--max-depth <n> Maximum analysis depth (default: 10)
|
|
150
156
|
-t, --include-tests Include test files in analysis
|
|
151
157
|
-o, --output <file> Output file (auto-detect format from extension)
|
|
@@ -165,6 +171,9 @@ CI/CD Examples:
|
|
|
165
171
|
blast-radius -p ./src -c src/api/task.ts --threshold files:3 -o result.json
|
|
166
172
|
|
|
167
173
|
Examples:
|
|
174
|
+
# Auto-detect all changes via git diff
|
|
175
|
+
blast-radius -p ./src --auto
|
|
176
|
+
|
|
168
177
|
# Analyze a single file change
|
|
169
178
|
blast-radius -p ./src -c src/api/user.ts
|
|
170
179
|
|
|
@@ -832,13 +841,202 @@ function formatHtml(
|
|
|
832
841
|
return html;
|
|
833
842
|
}
|
|
834
843
|
|
|
844
|
+
// ─── Auto Detect Changes via Git ───────────────────────────────────────────
|
|
845
|
+
|
|
846
|
+
interface ChangedSymbol {
|
|
847
|
+
file: string;
|
|
848
|
+
symbol: string;
|
|
849
|
+
type: 'modify' | 'delete' | 'rename' | 'add';
|
|
850
|
+
diff: string; // diff content for context
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* 使用 git diff 自动检测改动的文件和符号
|
|
855
|
+
*/
|
|
856
|
+
async function autoDetectChanges(projectRoot: string): Promise<ChangedSymbol[]> {
|
|
857
|
+
const { execSync } = await import('child_process');
|
|
858
|
+
|
|
859
|
+
try {
|
|
860
|
+
// 获取 staged + unstaged 的改动
|
|
861
|
+
const diffOutput = execSync('git diff --cached --name-only HEAD 2>/dev/null || git diff --name-only HEAD 2>/dev/null || git diff --name-only 2>/dev/null', {
|
|
862
|
+
cwd: projectRoot,
|
|
863
|
+
encoding: 'utf8',
|
|
864
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
const changedFiles = diffOutput.trim().split('\n').filter(f => f.trim());
|
|
868
|
+
|
|
869
|
+
if (changedFiles.length === 0 || changedFiles[0] === '') {
|
|
870
|
+
// 没有 git 改动,检查工作区
|
|
871
|
+
const unstaged = execSync('git diff --name-only 2>/dev/null || echo ""', {
|
|
872
|
+
cwd: projectRoot,
|
|
873
|
+
encoding: 'utf8',
|
|
874
|
+
}).trim().split('\n').filter(f => f.trim());
|
|
875
|
+
|
|
876
|
+
if (unstaged.length === 0 || unstaged[0] === '') {
|
|
877
|
+
return [];
|
|
878
|
+
}
|
|
879
|
+
changedFiles.length = 0;
|
|
880
|
+
changedFiles.push(...unstaged);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// 过滤只保留 TypeScript/JavaScript 文件
|
|
884
|
+
const codeFiles = changedFiles.filter(f =>
|
|
885
|
+
/\.(ts|tsx|js|jsx)$/.test(f) && !f.includes('node_modules')
|
|
886
|
+
);
|
|
887
|
+
|
|
888
|
+
if (codeFiles.length === 0) {
|
|
889
|
+
return [];
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// 获取每个文件的详细 diff,包括函数/变量名
|
|
893
|
+
const changedSymbols: ChangedSymbol[] = [];
|
|
894
|
+
|
|
895
|
+
for (const file of codeFiles) {
|
|
896
|
+
try {
|
|
897
|
+
// 获取文件的 diff
|
|
898
|
+
const fileDiff = execSync(`git diff HEAD -- "${file}" 2>/dev/null || git diff -- "${file}" 2>/dev/null || echo ""`, {
|
|
899
|
+
cwd: projectRoot,
|
|
900
|
+
encoding: 'utf8',
|
|
901
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
// 解析 diff 中的符号 (函数名、变量名等)
|
|
905
|
+
const symbols = parseDiffSymbols(fileDiff, file);
|
|
906
|
+
|
|
907
|
+
if (symbols.length > 0) {
|
|
908
|
+
for (const sym of symbols) {
|
|
909
|
+
changedSymbols.push({
|
|
910
|
+
file: path.resolve(projectRoot, file),
|
|
911
|
+
symbol: sym,
|
|
912
|
+
type: 'modify',
|
|
913
|
+
diff: fileDiff,
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
// 如果无法解析符号,仍添加文件
|
|
918
|
+
changedSymbols.push({
|
|
919
|
+
file: path.resolve(projectRoot, file),
|
|
920
|
+
symbol: '', // 空符号表示分析整个文件
|
|
921
|
+
type: 'modify',
|
|
922
|
+
diff: fileDiff,
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
} catch {
|
|
926
|
+
// 单个文件出错继续处理其他的
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return changedSymbols;
|
|
931
|
+
} catch (error) {
|
|
932
|
+
console.error('⚠️ 无法获取 git diff:', (error as Error).message);
|
|
933
|
+
return [];
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
/**
|
|
938
|
+
* 从 diff 中解析改动的符号名
|
|
939
|
+
*/
|
|
940
|
+
function parseDiffSymbols(diffContent: string, file: string): string[] {
|
|
941
|
+
const symbols: string[] = [];
|
|
942
|
+
const lines = diffContent.split('\n');
|
|
943
|
+
|
|
944
|
+
// 匹配函数定义: +functionName, +const functionName, +export functionName
|
|
945
|
+
const functionPattern = /^[+]export\s+(?:async\s+)?(?:function\s+(\w+)|const\s+(\w+)|(\w+)\s*=)/;
|
|
946
|
+
// 匹配变量声明: +export const name, +export let name, +export var name
|
|
947
|
+
const constPattern = /^[+]export\s+(?:const|let|var)\s+(\w+)/;
|
|
948
|
+
// 匹配类型定义: +export type Name, +export interface Name
|
|
949
|
+
const typePattern = /^[+]export\s+(?:type|interface|class|enum)\s+(\w+)/;
|
|
950
|
+
// 匹配 class 定义: +class ClassName
|
|
951
|
+
const classPattern = /^[+]class\s+(\w+)/;
|
|
952
|
+
|
|
953
|
+
for (const line of lines) {
|
|
954
|
+
// 跳过 diff header
|
|
955
|
+
if (line.startsWith('@@') || line.startsWith('---') || line.startsWith('+++') || line.startsWith('diff')) {
|
|
956
|
+
continue;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// 匹配函数
|
|
960
|
+
let match = line.match(functionPattern);
|
|
961
|
+
if (match) {
|
|
962
|
+
const name = match[1] || match[2] || match[3];
|
|
963
|
+
if (name && !symbols.includes(name)) {
|
|
964
|
+
symbols.push(name);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// 匹配 const/let/var
|
|
969
|
+
match = line.match(constPattern);
|
|
970
|
+
if (match && match[1]) {
|
|
971
|
+
if (!symbols.includes(match[1])) {
|
|
972
|
+
symbols.push(match[1]);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// 匹配类型
|
|
977
|
+
match = line.match(typePattern);
|
|
978
|
+
if (match && match[1]) {
|
|
979
|
+
if (!symbols.includes(match[1])) {
|
|
980
|
+
symbols.push(match[1]);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// 匹配 class
|
|
985
|
+
match = line.match(classPattern);
|
|
986
|
+
if (match && match[1]) {
|
|
987
|
+
if (!symbols.includes(match[1])) {
|
|
988
|
+
symbols.push(match[1]);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return symbols;
|
|
994
|
+
}
|
|
995
|
+
|
|
835
996
|
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
836
997
|
|
|
837
998
|
async function main(): Promise<void> {
|
|
838
999
|
const args = parseArgs(process.argv);
|
|
839
1000
|
|
|
1001
|
+
// 如果指定了 --auto,自动检测 git 改动
|
|
1002
|
+
if (args.autoDetect) {
|
|
1003
|
+
console.log('🔍 Auto-detecting changes via git diff...');
|
|
1004
|
+
const detectedChanges = await autoDetectChanges(args.projectRoot);
|
|
1005
|
+
|
|
1006
|
+
if (detectedChanges.length === 0) {
|
|
1007
|
+
console.log('✅ No code changes detected (or not a git repository)');
|
|
1008
|
+
process.exit(0);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
console.log(`📝 Found ${detectedChanges.length} changed symbol(s):\n`);
|
|
1012
|
+
for (const change of detectedChanges) {
|
|
1013
|
+
const relPath = path.relative(args.projectRoot, change.file);
|
|
1014
|
+
const symDisplay = change.symbol ? `#${change.symbol}` : '(file)';
|
|
1015
|
+
console.log(` • ${relPath}${symDisplay}`);
|
|
1016
|
+
}
|
|
1017
|
+
console.log('');
|
|
1018
|
+
|
|
1019
|
+
// 将检测到的改动合并到 args.changes
|
|
1020
|
+
for (const change of detectedChanges) {
|
|
1021
|
+
// 检查是否已存在相同的文件
|
|
1022
|
+
const existing = args.changes.find(c => c.file === change.file);
|
|
1023
|
+
if (existing) {
|
|
1024
|
+
// 如果已有符号,保留;否则添加新符号
|
|
1025
|
+
if (!existing.symbol && change.symbol) {
|
|
1026
|
+
existing.symbol = change.symbol;
|
|
1027
|
+
}
|
|
1028
|
+
} else {
|
|
1029
|
+
args.changes.push({
|
|
1030
|
+
file: change.file,
|
|
1031
|
+
symbol: change.symbol || undefined,
|
|
1032
|
+
type: change.type,
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
840
1038
|
if (args.changes.length === 0) {
|
|
841
|
-
console.error('❌ Error: No changes specified. Use --change <file>');
|
|
1039
|
+
console.error('❌ Error: No changes specified. Use --change <file> or --auto');
|
|
842
1040
|
console.error(' Run with --help for usage information');
|
|
843
1041
|
process.exit(1);
|
|
844
1042
|
}
|