tokenlean 0.1.0 ā 0.3.0
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 +44 -1
- package/bin/tl-cache.mjs +168 -0
- package/bin/tl-component.mjs +109 -83
- package/bin/tl-context.mjs +147 -98
- package/bin/tl-diff.mjs +95 -62
- package/bin/tl-entry.mjs +7 -1
- package/bin/tl-impact.mjs +10 -2
- package/bin/tl-related.mjs +122 -75
- package/bin/tl-search.mjs +148 -27
- package/bin/tl-structure.mjs +152 -83
- package/bin/tl-todo.mjs +8 -1
- package/bin/tl-unused.mjs +29 -19
- package/package.json +2 -1
- package/src/cache.mjs +493 -0
- package/src/config.mjs +6 -0
package/bin/tl-context.mjs
CHANGED
|
@@ -20,137 +20,186 @@ if (process.argv.includes('--prompt')) {
|
|
|
20
20
|
|
|
21
21
|
import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
22
22
|
import { join, relative } from 'path';
|
|
23
|
+
import {
|
|
24
|
+
createOutput,
|
|
25
|
+
parseCommonArgs,
|
|
26
|
+
estimateTokens,
|
|
27
|
+
formatTokens,
|
|
28
|
+
formatTable,
|
|
29
|
+
COMMON_OPTIONS_HELP
|
|
30
|
+
} from '../src/output.mjs';
|
|
31
|
+
import {
|
|
32
|
+
findProjectRoot,
|
|
33
|
+
shouldSkip,
|
|
34
|
+
getSkipDirs,
|
|
35
|
+
getImportantDirs
|
|
36
|
+
} from '../src/project.mjs';
|
|
37
|
+
|
|
38
|
+
const HELP = `
|
|
39
|
+
tl-context - Estimate context token usage for files/directories
|
|
40
|
+
|
|
41
|
+
Usage: tl-context [path] [options]
|
|
42
|
+
|
|
43
|
+
Options:
|
|
44
|
+
--top N, -n N Show top N files (default: 20, use --all for all)
|
|
45
|
+
--all Show all files
|
|
46
|
+
${COMMON_OPTIONS_HELP}
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
tl-context src/ # Estimate tokens for src directory
|
|
50
|
+
tl-context src/ --top 10 # Show top 10 largest files
|
|
51
|
+
tl-context src/ --all # Show all files
|
|
52
|
+
tl-context package.json # Single file estimate
|
|
53
|
+
tl-context -j # JSON output for scripting
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
function analyzeDir(dirPath, results = [], skipDirs, importantDirs) {
|
|
57
|
+
try {
|
|
58
|
+
const entries = readdirSync(dirPath, { withFileTypes: true });
|
|
59
|
+
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
if (entry.name.startsWith('.') && !importantDirs.has(entry.name)) continue;
|
|
62
|
+
if (shouldSkip(entry.name, entry.isDirectory())) continue;
|
|
63
|
+
|
|
64
|
+
const fullPath = join(dirPath, entry.name);
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
analyzeDir(fullPath, results, skipDirs, importantDirs);
|
|
68
|
+
} else {
|
|
69
|
+
try {
|
|
70
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
71
|
+
const tokens = estimateTokens(content);
|
|
72
|
+
results.push({ path: fullPath, tokens, lines: content.split('\n').length });
|
|
73
|
+
} catch {
|
|
74
|
+
// Skip binary or unreadable files
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// Permission error
|
|
80
|
+
}
|
|
23
81
|
|
|
24
|
-
|
|
25
|
-
'node_modules', '.git', 'android', 'ios', 'dist', 'build',
|
|
26
|
-
'.expo', '.next', 'coverage', '__pycache__', '.cache'
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
const SKIP_EXTENSIONS = new Set([
|
|
30
|
-
'.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.webp',
|
|
31
|
-
'.woff', '.woff2', '.ttf', '.eot',
|
|
32
|
-
'.mp3', '.mp4', '.wav', '.ogg',
|
|
33
|
-
'.zip', '.tar', '.gz',
|
|
34
|
-
'.lock', '.log'
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
|
-
// Rough token estimate: ~4 chars per token for code
|
|
38
|
-
function estimateTokens(content) {
|
|
39
|
-
return Math.ceil(content.length / 4);
|
|
82
|
+
return results;
|
|
40
83
|
}
|
|
41
84
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return String(tokens);
|
|
46
|
-
}
|
|
85
|
+
// Main
|
|
86
|
+
const args = process.argv.slice(2);
|
|
87
|
+
const options = parseCommonArgs(args);
|
|
47
88
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
89
|
+
// Parse tool-specific options
|
|
90
|
+
let topN = 20;
|
|
91
|
+
let targetPath = '.';
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < options.remaining.length; i++) {
|
|
94
|
+
const arg = options.remaining[i];
|
|
95
|
+
if ((arg === '--top' || arg === '-n') && options.remaining[i + 1]) {
|
|
96
|
+
topN = parseInt(options.remaining[i + 1], 10);
|
|
97
|
+
i++;
|
|
98
|
+
} else if (arg === '--all') {
|
|
99
|
+
topN = null;
|
|
100
|
+
} else if (!arg.startsWith('-')) {
|
|
101
|
+
targetPath = arg;
|
|
53
102
|
}
|
|
54
|
-
return false;
|
|
55
103
|
}
|
|
56
104
|
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
if (options.help) {
|
|
106
|
+
console.log(HELP);
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
59
109
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
110
|
+
if (!existsSync(targetPath)) {
|
|
111
|
+
console.error(`Path not found: ${targetPath}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
63
114
|
|
|
64
|
-
|
|
115
|
+
const projectRoot = findProjectRoot();
|
|
116
|
+
const skipDirs = getSkipDirs();
|
|
117
|
+
const importantDirs = getImportantDirs();
|
|
118
|
+
const out = createOutput(options);
|
|
65
119
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
results.push({ path: fullPath, tokens, lines: content.split('\n').length });
|
|
73
|
-
} catch (e) {
|
|
74
|
-
// Skip binary or unreadable files
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
120
|
+
const stat = statSync(targetPath);
|
|
121
|
+
if (stat.isFile()) {
|
|
122
|
+
// Single file
|
|
123
|
+
const content = readFileSync(targetPath, 'utf-8');
|
|
124
|
+
const tokens = estimateTokens(content);
|
|
125
|
+
const lines = content.split('\n').length;
|
|
78
126
|
|
|
79
|
-
|
|
80
|
-
|
|
127
|
+
out.setData('file', targetPath);
|
|
128
|
+
out.setData('tokens', tokens);
|
|
129
|
+
out.setData('lines', lines);
|
|
130
|
+
|
|
131
|
+
out.header(`${targetPath}: ~${formatTokens(tokens)} tokens (${lines} lines)`);
|
|
132
|
+
out.print();
|
|
133
|
+
} else {
|
|
134
|
+
// Directory
|
|
135
|
+
const results = analyzeDir(targetPath, [], skipDirs, importantDirs);
|
|
81
136
|
|
|
82
|
-
function printResults(results, rootPath, topN) {
|
|
83
137
|
// Sort by tokens descending
|
|
84
138
|
results.sort((a, b) => b.tokens - a.tokens);
|
|
85
139
|
|
|
86
140
|
const total = results.reduce((sum, r) => sum + r.tokens, 0);
|
|
141
|
+
const totalLines = results.reduce((sum, r) => sum + r.lines, 0);
|
|
87
142
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
results = results.slice(0, topN);
|
|
94
|
-
}
|
|
143
|
+
// Set JSON data
|
|
144
|
+
out.setData('path', targetPath);
|
|
145
|
+
out.setData('totalTokens', total);
|
|
146
|
+
out.setData('totalLines', totalLines);
|
|
147
|
+
out.setData('fileCount', results.length);
|
|
95
148
|
|
|
96
|
-
|
|
149
|
+
// Header
|
|
150
|
+
out.header(`Context Estimate: ${targetPath}`);
|
|
151
|
+
out.header(`Total: ~${formatTokens(total)} tokens across ${results.length} files`);
|
|
152
|
+
out.blank();
|
|
97
153
|
|
|
98
|
-
|
|
99
|
-
|
|
154
|
+
// File list
|
|
155
|
+
const displayResults = topN ? results.slice(0, topN) : results;
|
|
100
156
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
157
|
+
if (displayResults.length > 0) {
|
|
158
|
+
if (topN) {
|
|
159
|
+
out.header(`Top ${Math.min(topN, results.length)} largest files:`);
|
|
160
|
+
}
|
|
161
|
+
out.blank();
|
|
162
|
+
|
|
163
|
+
// Format as table
|
|
164
|
+
const rows = displayResults.map(r => {
|
|
165
|
+
const relPath = relative(targetPath, r.path);
|
|
166
|
+
const truncPath = relPath.length > 60 ? '...' + relPath.slice(-57) : relPath;
|
|
167
|
+
return [formatTokens(r.tokens), r.lines, truncPath];
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const tableLines = formatTable(rows, { indent: ' ', separator: ' ' });
|
|
171
|
+
out.add(' Tokens Lines Path');
|
|
172
|
+
out.add(' ' + '-'.repeat(70));
|
|
173
|
+
out.addLines(tableLines);
|
|
105
174
|
}
|
|
106
175
|
|
|
107
|
-
console.log();
|
|
108
|
-
|
|
109
176
|
// Group by directory
|
|
110
177
|
const byDir = {};
|
|
111
178
|
for (const r of results) {
|
|
112
|
-
const rel = relative(
|
|
179
|
+
const rel = relative(targetPath, r.path);
|
|
113
180
|
const dir = rel.includes('/') ? rel.split('/')[0] : '.';
|
|
114
181
|
byDir[dir] = (byDir[dir] || 0) + r.tokens;
|
|
115
182
|
}
|
|
116
183
|
|
|
117
184
|
const sortedDirs = Object.entries(byDir).sort((a, b) => b[1] - a[1]);
|
|
118
185
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
console.log(` ${formatTokens(tokens).padStart(6)} ${pct.padStart(5)}% ${dir}/`);
|
|
123
|
-
}
|
|
124
|
-
console.log();
|
|
125
|
-
}
|
|
186
|
+
out.blank();
|
|
187
|
+
out.header('By top-level directory:');
|
|
188
|
+
out.blank();
|
|
126
189
|
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
190
|
+
const dirRows = sortedDirs.slice(0, 10).map(([dir, tokens]) => {
|
|
191
|
+
const pct = ((tokens / total) * 100).toFixed(1) + '%';
|
|
192
|
+
return [formatTokens(tokens), pct, dir + '/'];
|
|
193
|
+
});
|
|
131
194
|
|
|
132
|
-
|
|
133
|
-
if (args[i] === '--top' && args[i + 1]) {
|
|
134
|
-
topN = parseInt(args[i + 1], 10);
|
|
135
|
-
i++;
|
|
136
|
-
} else if (args[i] === '--all') {
|
|
137
|
-
topN = null;
|
|
138
|
-
} else if (!args[i].startsWith('-')) {
|
|
139
|
-
targetPath = args[i];
|
|
140
|
-
}
|
|
141
|
-
}
|
|
195
|
+
out.addLines(formatTable(dirRows, { indent: ' ', separator: ' ' }));
|
|
142
196
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
197
|
+
out.setData('byDirectory', Object.fromEntries(sortedDirs));
|
|
198
|
+
out.setData('files', results.slice(0, 100).map(r => ({
|
|
199
|
+
path: relative(targetPath, r.path),
|
|
200
|
+
tokens: r.tokens,
|
|
201
|
+
lines: r.lines
|
|
202
|
+
})));
|
|
147
203
|
|
|
148
|
-
|
|
149
|
-
if (stat.isFile()) {
|
|
150
|
-
const content = readFileSync(targetPath, 'utf-8');
|
|
151
|
-
const tokens = estimateTokens(content);
|
|
152
|
-
console.log(`\n${targetPath}: ~${formatTokens(tokens)} tokens (${content.split('\n').length} lines)\n`);
|
|
153
|
-
} else {
|
|
154
|
-
const results = analyzeDir(targetPath);
|
|
155
|
-
printResults(results, targetPath, topN);
|
|
204
|
+
out.print();
|
|
156
205
|
}
|
package/bin/tl-diff.mjs
CHANGED
|
@@ -21,6 +21,30 @@ if (process.argv.includes('--prompt')) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
import { execSync } from 'child_process';
|
|
24
|
+
import {
|
|
25
|
+
createOutput,
|
|
26
|
+
parseCommonArgs,
|
|
27
|
+
formatTokens,
|
|
28
|
+
COMMON_OPTIONS_HELP
|
|
29
|
+
} from '../src/output.mjs';
|
|
30
|
+
|
|
31
|
+
const HELP = `
|
|
32
|
+
tl-diff - Token-efficient git diff summary
|
|
33
|
+
|
|
34
|
+
Usage: tl-diff [ref] [options]
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
--staged Show staged changes only
|
|
38
|
+
--stat-only Show just the summary (no file list)
|
|
39
|
+
${COMMON_OPTIONS_HELP}
|
|
40
|
+
|
|
41
|
+
Examples:
|
|
42
|
+
tl-diff # Working directory changes
|
|
43
|
+
tl-diff --staged # Staged changes
|
|
44
|
+
tl-diff HEAD~3 # Last 3 commits
|
|
45
|
+
tl-diff main # Changes vs main branch
|
|
46
|
+
tl-diff -j # JSON output
|
|
47
|
+
`;
|
|
24
48
|
|
|
25
49
|
function run(cmd) {
|
|
26
50
|
try {
|
|
@@ -30,15 +54,6 @@ function run(cmd) {
|
|
|
30
54
|
}
|
|
31
55
|
}
|
|
32
56
|
|
|
33
|
-
function estimateTokens(content) {
|
|
34
|
-
return Math.ceil(content.length / 4);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function formatTokens(tokens) {
|
|
38
|
-
if (tokens >= 1000) return `${(tokens / 1000).toFixed(1)}k`;
|
|
39
|
-
return String(tokens);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
57
|
function parseDiffStat(stat) {
|
|
43
58
|
const lines = stat.trim().split('\n');
|
|
44
59
|
const files = [];
|
|
@@ -96,66 +111,30 @@ function categorizeChanges(files) {
|
|
|
96
111
|
return categories;
|
|
97
112
|
}
|
|
98
113
|
|
|
99
|
-
function printSummary(files, categories, options) {
|
|
100
|
-
const totalChanges = files.reduce((sum, f) => sum + f.changes, 0);
|
|
101
|
-
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
102
|
-
const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
|
|
103
|
-
|
|
104
|
-
console.log(`\nš Diff Summary`);
|
|
105
|
-
console.log(` ${files.length} files changed, ~${formatTokens(totalChanges * 4)} tokens of changes`);
|
|
106
|
-
console.log(` +${totalAdditions} additions, -${totalDeletions} deletions\n`);
|
|
107
|
-
|
|
108
|
-
const order = ['components', 'hooks', 'store', 'types', 'manuscripts', 'tests', 'config', 'other'];
|
|
109
|
-
const labels = {
|
|
110
|
-
components: 'š§© Components',
|
|
111
|
-
hooks: 'šŖ Hooks',
|
|
112
|
-
store: 'š¦ Store',
|
|
113
|
-
types: 'š Types',
|
|
114
|
-
manuscripts: 'š Manuscripts',
|
|
115
|
-
tests: 'š§Ŗ Tests',
|
|
116
|
-
config: 'āļø Config',
|
|
117
|
-
other: 'š Other'
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
for (const cat of order) {
|
|
121
|
-
const catFiles = categories[cat];
|
|
122
|
-
if (catFiles.length === 0) continue;
|
|
123
|
-
|
|
124
|
-
console.log(`${labels[cat]} (${catFiles.length})`);
|
|
125
|
-
|
|
126
|
-
// Sort by changes descending
|
|
127
|
-
catFiles.sort((a, b) => b.changes - a.changes);
|
|
128
|
-
|
|
129
|
-
for (const f of catFiles.slice(0, 10)) {
|
|
130
|
-
const bar = '+'.repeat(Math.min(f.additions, 20)) + '-'.repeat(Math.min(f.deletions, 20));
|
|
131
|
-
console.log(` ${f.path}`);
|
|
132
|
-
console.log(` ${f.changes} changes ${bar}`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (catFiles.length > 10) {
|
|
136
|
-
console.log(` ... and ${catFiles.length - 10} more`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
console.log();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
114
|
// Main
|
|
144
115
|
const args = process.argv.slice(2);
|
|
116
|
+
const options = parseCommonArgs(args);
|
|
117
|
+
|
|
118
|
+
// Parse tool-specific options
|
|
145
119
|
let ref = '';
|
|
146
120
|
let staged = false;
|
|
147
121
|
let statOnly = false;
|
|
148
122
|
|
|
149
|
-
for (
|
|
150
|
-
if (
|
|
123
|
+
for (const arg of options.remaining) {
|
|
124
|
+
if (arg === '--staged') {
|
|
151
125
|
staged = true;
|
|
152
|
-
} else if (
|
|
126
|
+
} else if (arg === '--stat-only') {
|
|
153
127
|
statOnly = true;
|
|
154
|
-
} else if (!
|
|
155
|
-
ref =
|
|
128
|
+
} else if (!arg.startsWith('-')) {
|
|
129
|
+
ref = arg;
|
|
156
130
|
}
|
|
157
131
|
}
|
|
158
132
|
|
|
133
|
+
if (options.help) {
|
|
134
|
+
console.log(HELP);
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
|
|
159
138
|
// Build git diff command
|
|
160
139
|
let diffCmd = 'git diff';
|
|
161
140
|
if (staged) {
|
|
@@ -167,17 +146,71 @@ diffCmd += ' --stat=200';
|
|
|
167
146
|
|
|
168
147
|
const stat = run(diffCmd);
|
|
169
148
|
|
|
149
|
+
const out = createOutput(options);
|
|
150
|
+
|
|
170
151
|
if (!stat.trim()) {
|
|
171
|
-
|
|
152
|
+
out.header('No changes detected');
|
|
153
|
+
out.print();
|
|
172
154
|
process.exit(0);
|
|
173
155
|
}
|
|
174
156
|
|
|
175
157
|
const files = parseDiffStat(stat);
|
|
176
158
|
const categories = categorizeChanges(files);
|
|
177
159
|
|
|
178
|
-
|
|
160
|
+
const totalChanges = files.reduce((sum, f) => sum + f.changes, 0);
|
|
161
|
+
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
162
|
+
const totalDeletions = files.reduce((sum, f) => sum + f.deletions, 0);
|
|
163
|
+
|
|
164
|
+
// Set JSON data
|
|
165
|
+
out.setData('files', files);
|
|
166
|
+
out.setData('categories', categories);
|
|
167
|
+
out.setData('totalFiles', files.length);
|
|
168
|
+
out.setData('totalChanges', totalChanges);
|
|
169
|
+
out.setData('estimatedTokens', totalChanges * 4);
|
|
170
|
+
|
|
171
|
+
// Summary header
|
|
172
|
+
out.header('Diff Summary');
|
|
173
|
+
out.header(`${files.length} files changed, ~${formatTokens(totalChanges * 4)} tokens of changes`);
|
|
174
|
+
out.header(`+${totalAdditions} additions, -${totalDeletions} deletions`);
|
|
175
|
+
out.blank();
|
|
179
176
|
|
|
180
177
|
if (!statOnly) {
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
const order = ['components', 'hooks', 'store', 'types', 'manuscripts', 'tests', 'config', 'other'];
|
|
179
|
+
const labels = {
|
|
180
|
+
components: 'Components',
|
|
181
|
+
hooks: 'Hooks',
|
|
182
|
+
store: 'Store',
|
|
183
|
+
types: 'Types',
|
|
184
|
+
manuscripts: 'Manuscripts',
|
|
185
|
+
tests: 'Tests',
|
|
186
|
+
config: 'Config',
|
|
187
|
+
other: 'Other'
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
for (const cat of order) {
|
|
191
|
+
const catFiles = categories[cat];
|
|
192
|
+
if (catFiles.length === 0) continue;
|
|
193
|
+
|
|
194
|
+
out.add(`${labels[cat]} (${catFiles.length})`);
|
|
195
|
+
|
|
196
|
+
// Sort by changes descending
|
|
197
|
+
catFiles.sort((a, b) => b.changes - a.changes);
|
|
198
|
+
|
|
199
|
+
for (const f of catFiles.slice(0, 10)) {
|
|
200
|
+
const bar = '+'.repeat(Math.min(f.additions, 20)) + '-'.repeat(Math.min(f.deletions, 20));
|
|
201
|
+
out.add(` ${f.path}`);
|
|
202
|
+
out.add(` ${f.changes} changes ${bar}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (catFiles.length > 10) {
|
|
206
|
+
out.add(` ... and ${catFiles.length - 10} more`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
out.blank();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
out.header('Tip: Use --stat-only for just the summary, or check specific files with:');
|
|
213
|
+
out.header(' git diff [ref] -- path/to/file.ts');
|
|
183
214
|
}
|
|
215
|
+
|
|
216
|
+
out.print();
|
package/bin/tl-entry.mjs
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
COMMON_OPTIONS_HELP
|
|
31
31
|
} from '../src/output.mjs';
|
|
32
32
|
import { findProjectRoot } from '../src/project.mjs';
|
|
33
|
+
import { withCache } from '../src/cache.mjs';
|
|
33
34
|
|
|
34
35
|
const HELP = `
|
|
35
36
|
tl-entry - Find entry points in a codebase
|
|
@@ -130,7 +131,12 @@ function findEntryPoints(searchPath, projectRoot, filterType) {
|
|
|
130
131
|
for (const { pattern, desc } of config.patterns) {
|
|
131
132
|
try {
|
|
132
133
|
const cmd = `rg -n -g "*.{ts,tsx,js,jsx,mjs}" --no-heading -e "${shellEscape(pattern)}" "${shellEscape(searchPath)}" 2>/dev/null || true`;
|
|
133
|
-
const
|
|
134
|
+
const cacheKey = { op: 'rg-entry-pattern', pattern, path: searchPath };
|
|
135
|
+
const output = withCache(
|
|
136
|
+
cacheKey,
|
|
137
|
+
() => execSync(cmd, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }),
|
|
138
|
+
{ projectRoot }
|
|
139
|
+
);
|
|
134
140
|
|
|
135
141
|
for (const line of output.trim().split('\n')) {
|
|
136
142
|
if (!line) continue;
|
package/bin/tl-impact.mjs
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
COMMON_OPTIONS_HELP
|
|
34
34
|
} from '../src/output.mjs';
|
|
35
35
|
import { findProjectRoot, categorizeFile } from '../src/project.mjs';
|
|
36
|
+
import { withCache } from '../src/cache.mjs';
|
|
36
37
|
|
|
37
38
|
const HELP = `
|
|
38
39
|
tl-impact - Analyze the blast radius of changing a file
|
|
@@ -79,8 +80,15 @@ function findDirectImporters(filePath, projectRoot) {
|
|
|
79
80
|
const patterns = searchTerms.map(t => `-e "${rgEscape(t)}"`).join(' ');
|
|
80
81
|
|
|
81
82
|
try {
|
|
82
|
-
const
|
|
83
|
-
const result =
|
|
83
|
+
const cacheKey = { op: 'rg-find-candidates', terms: searchTerms.sort() };
|
|
84
|
+
const result = withCache(
|
|
85
|
+
cacheKey,
|
|
86
|
+
() => {
|
|
87
|
+
const rgCommand = `rg -l --type-add 'code:*.{js,jsx,ts,tsx,mjs,mts,cjs}' -t code ${patterns} "${projectRoot}" 2>/dev/null || true`;
|
|
88
|
+
return execSync(rgCommand, { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
|
|
89
|
+
},
|
|
90
|
+
{ projectRoot }
|
|
91
|
+
);
|
|
84
92
|
const candidates = result.trim().split('\n').filter(Boolean);
|
|
85
93
|
|
|
86
94
|
for (const candidate of candidates) {
|