@mishasinitcyn/betterrank 0.2.2 → 0.2.3
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 +25 -0
- package/package.json +1 -1
- package/src/cli.js +49 -0
- package/src/index.js +56 -0
package/README.md
CHANGED
|
@@ -186,6 +186,31 @@ Callers (1 file):
|
|
|
186
186
|
src/engine/bidding.py
|
|
187
187
|
```
|
|
188
188
|
|
|
189
|
+
### `history` — Git history of a specific function
|
|
190
|
+
|
|
191
|
+
Shows only commits that touched a function's lines. Uses tree-sitter line ranges for accuracy (better than git's heuristic `:funcname:` detection). Add `--patch` to include function-scoped diffs.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Commit list (compact)
|
|
195
|
+
betterrank history calculate_bid --root /path/to/project
|
|
196
|
+
|
|
197
|
+
# With function-scoped diffs
|
|
198
|
+
betterrank history calculate_bid --root /path/to/project --patch --limit 3
|
|
199
|
+
|
|
200
|
+
# Paginate through older commits
|
|
201
|
+
betterrank history calculate_bid --root /path/to/project --offset 5 --limit 5
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Example output:**
|
|
205
|
+
```
|
|
206
|
+
calculate_bid (src/engine/bidding.py:489-718)
|
|
207
|
+
|
|
208
|
+
082b9d5 2026-02-24 fix: restore GSP auction pricing
|
|
209
|
+
c75f5ff 2026-02-14 fix: resolve lint errors from main merge
|
|
210
|
+
623429c 2026-02-13 hot fix
|
|
211
|
+
5d236d3 2026-02-06 feat: wire ad_position to ValuePredictor
|
|
212
|
+
```
|
|
213
|
+
|
|
189
214
|
### `trace` — Recursive caller chain
|
|
190
215
|
|
|
191
216
|
Walk UP the call graph from a symbol to see the full path from entry points to your function. At each hop, resolves which function in the caller file contains the call site.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mishasinitcyn/betterrank",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Structural code index with PageRank-ranked repo maps, symbol search, call-graph queries, and dependency analysis. Built on tree-sitter and graphology.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ Commands:
|
|
|
19
19
|
symbols [--file path] [--kind type] List definitions (ranked by PageRank)
|
|
20
20
|
callers <symbol> [--file path] [--context] All call sites (ranked, with context lines)
|
|
21
21
|
context <symbol> [--file path] Full context: source, deps, types, callers
|
|
22
|
+
history <symbol> [--file path] Git history of a specific function
|
|
22
23
|
trace <symbol> [--depth N] Recursive caller chain (call tree)
|
|
23
24
|
diff [--ref <commit>] Git-aware blast radius (changed symbols + callers)
|
|
24
25
|
deps <file> What this file imports (ranked)
|
|
@@ -154,6 +155,24 @@ Examples:
|
|
|
154
155
|
betterrank context calculate_bid --root .
|
|
155
156
|
betterrank context Router --file src/llm.py --root .`,
|
|
156
157
|
|
|
158
|
+
history: `betterrank history <symbol> [--file path] [--patch] [--limit N] [--root <path>]
|
|
159
|
+
|
|
160
|
+
Git history of a specific function. Uses the tree-sitter line range to
|
|
161
|
+
show only commits that touched that function's lines.
|
|
162
|
+
|
|
163
|
+
More accurate than git log -L :funcname: because betterrank knows the
|
|
164
|
+
exact line range from tree-sitter, not git's heuristic function detection.
|
|
165
|
+
|
|
166
|
+
Options:
|
|
167
|
+
--file <path> Disambiguate when multiple symbols share a name
|
|
168
|
+
--patch, -p Include function-scoped diffs (not just commit list)
|
|
169
|
+
--limit N Max commits to show (default: 20)
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
betterrank history calculate_bid --root .
|
|
173
|
+
betterrank history calculate_bid --root . --patch --limit 3
|
|
174
|
+
betterrank history Router --file src/llm.py --root .`,
|
|
175
|
+
|
|
157
176
|
trace: `betterrank trace <symbol> [--depth N] [--file path] [--root <path>]
|
|
158
177
|
|
|
159
178
|
Recursive caller chain — walk UP the call graph from a symbol to see
|
|
@@ -591,6 +610,36 @@ async function main() {
|
|
|
591
610
|
break;
|
|
592
611
|
}
|
|
593
612
|
|
|
613
|
+
case 'history': {
|
|
614
|
+
const symbol = flags._positional[0];
|
|
615
|
+
if (!symbol) { console.error('Usage: betterrank history <symbol> [--file path] [--patch]'); process.exit(1); }
|
|
616
|
+
const histLimit = flags.limit ? parseInt(flags.limit, 10) : 20;
|
|
617
|
+
const histOffset = flags.offset ? parseInt(flags.offset, 10) : 0;
|
|
618
|
+
const showPatch = flags.patch === true || flags.p === true;
|
|
619
|
+
const result = await idx.history({ symbol, file: normalizeFilePath(flags.file), offset: histOffset, limit: histLimit, patch: showPatch });
|
|
620
|
+
if (!result) {
|
|
621
|
+
console.log(`(symbol "${symbol}" not found)`);
|
|
622
|
+
} else if (result.error) {
|
|
623
|
+
console.error(result.error);
|
|
624
|
+
} else if (result.raw) {
|
|
625
|
+
// --patch mode: print git's full output
|
|
626
|
+
const def = result.definition;
|
|
627
|
+
console.log(`${def.name} (${def.file}:${def.lineStart}-${def.lineEnd})\n`);
|
|
628
|
+
console.log(result.raw);
|
|
629
|
+
} else {
|
|
630
|
+
const def = result.definition;
|
|
631
|
+
console.log(`${def.name} (${def.file}:${def.lineStart}-${def.lineEnd})\n`);
|
|
632
|
+
if (result.commits.length === 0) {
|
|
633
|
+
console.log('(no commits found)');
|
|
634
|
+
} else {
|
|
635
|
+
for (const line of result.commits) {
|
|
636
|
+
console.log(` ${line}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
break;
|
|
641
|
+
}
|
|
642
|
+
|
|
594
643
|
case 'trace': {
|
|
595
644
|
const symbol = flags._positional[0];
|
|
596
645
|
if (!symbol) { console.error('Usage: betterrank trace <symbol> [--depth N]'); process.exit(1); }
|
package/src/index.js
CHANGED
|
@@ -1157,6 +1157,62 @@ class CodeIndex {
|
|
|
1157
1157
|
};
|
|
1158
1158
|
}
|
|
1159
1159
|
|
|
1160
|
+
/**
|
|
1161
|
+
* Git history of a specific function, using its tree-sitter line range.
|
|
1162
|
+
*
|
|
1163
|
+
* @param {object} opts
|
|
1164
|
+
* @param {string} opts.symbol - Symbol name
|
|
1165
|
+
* @param {string} [opts.file] - Disambiguate by file
|
|
1166
|
+
* @param {number} [opts.offset=0] - Skip first N commits
|
|
1167
|
+
* @param {number} [opts.limit=20] - Max commits to show
|
|
1168
|
+
* @param {boolean} [opts.patch=false] - Include function-scoped diffs
|
|
1169
|
+
* @returns {object|null} { definition, commits, raw? }
|
|
1170
|
+
*/
|
|
1171
|
+
async history({ symbol, file, offset = 0, limit = 20, patch = false }) {
|
|
1172
|
+
await this._ensureReady();
|
|
1173
|
+
const graph = this.cache.getGraph();
|
|
1174
|
+
if (!graph) return null;
|
|
1175
|
+
|
|
1176
|
+
const candidates = [];
|
|
1177
|
+
graph.forEachNode((node, attrs) => {
|
|
1178
|
+
if (attrs.type !== 'symbol') return;
|
|
1179
|
+
if (attrs.name !== symbol) return;
|
|
1180
|
+
if (file && attrs.file !== file) return;
|
|
1181
|
+
candidates.push(attrs);
|
|
1182
|
+
});
|
|
1183
|
+
if (candidates.length === 0) return null;
|
|
1184
|
+
|
|
1185
|
+
const ranked = this._getRanked();
|
|
1186
|
+
const scoreMap = new Map(ranked);
|
|
1187
|
+
candidates.sort((a, b) => {
|
|
1188
|
+
const aKey = `${a.file}::${a.name}`;
|
|
1189
|
+
const bKey = `${b.file}::${b.name}`;
|
|
1190
|
+
return (scoreMap.get(bKey) || 0) - (scoreMap.get(aKey) || 0);
|
|
1191
|
+
});
|
|
1192
|
+
const target = candidates[0];
|
|
1193
|
+
|
|
1194
|
+
const { execSync } = await import('child_process');
|
|
1195
|
+
try {
|
|
1196
|
+
if (patch) {
|
|
1197
|
+
// Full output with diffs — return raw text
|
|
1198
|
+
const raw = execSync(
|
|
1199
|
+
`git log -L ${target.lineStart},${target.lineEnd}:${target.file} --skip=${offset} -n ${limit}`,
|
|
1200
|
+
{ cwd: this.projectRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000 }
|
|
1201
|
+
).trim();
|
|
1202
|
+
return { definition: target, commits: [], raw };
|
|
1203
|
+
}
|
|
1204
|
+
// Summary only
|
|
1205
|
+
const output = execSync(
|
|
1206
|
+
`git log -L ${target.lineStart},${target.lineEnd}:${target.file} --no-patch --format="%h %ad %s" --date=short --skip=${offset} -n ${limit}`,
|
|
1207
|
+
{ cwd: this.projectRoot, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000 }
|
|
1208
|
+
).trim();
|
|
1209
|
+
const commits = output ? output.split('\n').filter(Boolean) : [];
|
|
1210
|
+
return { definition: target, commits };
|
|
1211
|
+
} catch {
|
|
1212
|
+
return { definition: target, commits: [], error: 'git log failed — is this a git repo?' };
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1160
1216
|
/**
|
|
1161
1217
|
* Recursive caller chain — walk UP the call graph from a symbol.
|
|
1162
1218
|
* At each hop, resolves which function in the caller file contains
|