token-pilot 0.12.0 → 0.14.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/.claude-plugin/hooks/hooks.json +9 -0
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +30 -1
- package/README.md +28 -7
- package/dist/config/defaults.js +12 -0
- package/dist/core/architecture-fingerprint.d.ts +34 -0
- package/dist/core/architecture-fingerprint.js +127 -0
- package/dist/core/budget-planner.d.ts +21 -0
- package/dist/core/budget-planner.js +68 -0
- package/dist/core/confidence.d.ts +31 -0
- package/dist/core/confidence.js +99 -0
- package/dist/core/context-registry.d.ts +14 -0
- package/dist/core/context-registry.js +55 -0
- package/dist/core/decision-trace.d.ts +31 -0
- package/dist/core/decision-trace.js +45 -0
- package/dist/core/intent-classifier.d.ts +13 -0
- package/dist/core/intent-classifier.js +44 -0
- package/dist/core/policy-engine.d.ts +41 -0
- package/dist/core/policy-engine.js +76 -0
- package/dist/core/session-analytics.d.ts +8 -0
- package/dist/core/session-analytics.js +86 -7
- package/dist/core/session-cache.d.ts +74 -0
- package/dist/core/session-cache.js +162 -0
- package/dist/core/validation.d.ts +3 -0
- package/dist/core/validation.js +3 -0
- package/dist/git/file-watcher.d.ts +6 -0
- package/dist/git/file-watcher.js +18 -2
- package/dist/git/watcher.d.ts +3 -0
- package/dist/git/watcher.js +6 -0
- package/dist/handlers/code-audit.d.ts +7 -2
- package/dist/handlers/code-audit.js +19 -5
- package/dist/handlers/explore-area.d.ts +10 -0
- package/dist/handlers/explore-area.js +39 -13
- package/dist/handlers/find-unused.d.ts +3 -0
- package/dist/handlers/find-unused.js +3 -2
- package/dist/handlers/find-usages.d.ts +7 -0
- package/dist/handlers/find-usages.js +36 -5
- package/dist/handlers/module-info.d.ts +3 -0
- package/dist/handlers/module-info.js +22 -2
- package/dist/handlers/project-overview.d.ts +1 -1
- package/dist/handlers/project-overview.js +18 -2
- package/dist/handlers/read-for-edit.d.ts +3 -0
- package/dist/handlers/read-for-edit.js +185 -3
- package/dist/handlers/read-range.d.ts +1 -1
- package/dist/handlers/read-range.js +16 -1
- package/dist/handlers/read-symbol.d.ts +1 -1
- package/dist/handlers/read-symbol.js +26 -2
- package/dist/handlers/related-files.d.ts +11 -0
- package/dist/handlers/related-files.js +178 -42
- package/dist/handlers/smart-read-many.js +70 -16
- package/dist/handlers/smart-read.js +10 -1
- package/dist/handlers/test-summary.js +26 -3
- package/dist/hooks/installer.d.ts +12 -8
- package/dist/hooks/installer.js +24 -8
- package/dist/index.d.ts +16 -1
- package/dist/index.js +61 -55
- package/dist/server.js +395 -30
- package/dist/types.d.ts +12 -0
- package/package.json +5 -3
- package/start.sh +28 -27
- package/dist/handlers/class-hierarchy.d.ts +0 -11
- package/dist/handlers/class-hierarchy.js +0 -28
- package/dist/handlers/export-ast-index.d.ts +0 -22
- package/dist/handlers/export-ast-index.js +0 -175
- package/dist/handlers/find-implementations.d.ts +0 -11
- package/dist/handlers/find-implementations.js +0 -27
- package/dist/handlers/search-code.d.ts +0 -14
- package/dist/handlers/search-code.js +0 -32
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
1
2
|
/**
|
|
2
3
|
* Escape special regex characters in a string.
|
|
3
4
|
*/
|
|
@@ -33,10 +34,16 @@ const LANG_EXT_MAP = {
|
|
|
33
34
|
*/
|
|
34
35
|
export async function handleFindUsages(args, astIndex) {
|
|
35
36
|
if (astIndex.isDisabled() || astIndex.isOversized()) {
|
|
36
|
-
return {
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: 'text',
|
|
40
|
+
text: 'find_usages is disabled: ' + (astIndex.isDisabled()
|
|
37
41
|
? 'project root not detected. Call smart_read() on any project file first — this auto-detects the project root and enables ast-index tools.'
|
|
38
|
-
: 'ast-index built >50k files (likely includes node_modules). Ensure node_modules is in .gitignore.')
|
|
39
|
-
'\nAlternative: use grep_search to find symbol references.'
|
|
42
|
+
: 'ast-index built >50k files (likely includes node_modules). Ensure node_modules is in .gitignore.')
|
|
43
|
+
+ '\nAlternative: use grep_search to find symbol references.',
|
|
44
|
+
}],
|
|
45
|
+
meta: { files: [], definitions: 0, imports: 0, usages: 0, total: 0 },
|
|
46
|
+
};
|
|
40
47
|
}
|
|
41
48
|
// Run refs + search in parallel
|
|
42
49
|
const [refs, searchResults] = await Promise.all([
|
|
@@ -129,7 +136,10 @@ export async function handleFindUsages(args, astIndex) {
|
|
|
129
136
|
if (!astIndex.isAvailable()) {
|
|
130
137
|
hints.push('WARNING: ast-index is not available.');
|
|
131
138
|
}
|
|
132
|
-
return {
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: 'text', text: hints.join('\n') }],
|
|
141
|
+
meta: { files: [], definitions: 0, imports: 0, usages: 0, total: 0 },
|
|
142
|
+
};
|
|
133
143
|
}
|
|
134
144
|
// Build header with active filters
|
|
135
145
|
const filterHints = [];
|
|
@@ -169,6 +179,27 @@ export async function handleFindUsages(args, astIndex) {
|
|
|
169
179
|
lines.push('');
|
|
170
180
|
}
|
|
171
181
|
lines.push('HINT: Use read_symbol() or read_range() to load specific results.');
|
|
172
|
-
|
|
182
|
+
// Confidence metadata
|
|
183
|
+
const confidenceMeta = assessConfidence({
|
|
184
|
+
refsFound: totalCount > 0,
|
|
185
|
+
astAvailable: astIndex.isAvailable(),
|
|
186
|
+
symbolResolved: definitions.length > 0,
|
|
187
|
+
});
|
|
188
|
+
lines.push(formatConfidence(confidenceMeta));
|
|
189
|
+
const files = Array.from(new Set([
|
|
190
|
+
...definitions.map((d) => d.file),
|
|
191
|
+
...allImports.map((i) => i.file),
|
|
192
|
+
...allUsages.map((u) => u.file),
|
|
193
|
+
])).sort();
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
196
|
+
meta: {
|
|
197
|
+
files,
|
|
198
|
+
definitions: definitions.length,
|
|
199
|
+
imports: allImports.length,
|
|
200
|
+
usages: allUsages.length,
|
|
201
|
+
total: totalCount,
|
|
202
|
+
},
|
|
203
|
+
};
|
|
173
204
|
}
|
|
174
205
|
//# sourceMappingURL=find-usages.js.map
|
|
@@ -8,6 +8,7 @@ export async function handleModuleInfo(args, projectRoot, astIndex) {
|
|
|
8
8
|
text: '⚠ ast-index unavailable — module_info requires ast-index.\n' +
|
|
9
9
|
'DEGRADED: Use find_usages() + related_files() as alternatives for dependency analysis.',
|
|
10
10
|
}],
|
|
11
|
+
meta: { files: [] },
|
|
11
12
|
};
|
|
12
13
|
}
|
|
13
14
|
const check = args.check ?? 'all';
|
|
@@ -36,7 +37,7 @@ export async function handleModuleInfo(args, projectRoot, astIndex) {
|
|
|
36
37
|
sections.push('No modules detected. ast-index module analysis requires a modular project structure.');
|
|
37
38
|
sections.push('HINT: Use find_usages() for cross-file symbol references, related_files() for import graphs.');
|
|
38
39
|
}
|
|
39
|
-
return { content: [{ type: 'text', text: sections.join('\n') }] };
|
|
40
|
+
return { content: [{ type: 'text', text: sections.join('\n') }], meta: { files: [] } };
|
|
40
41
|
}
|
|
41
42
|
sections.push('');
|
|
42
43
|
// Run requested checks in parallel
|
|
@@ -115,7 +116,26 @@ export async function handleModuleInfo(args, projectRoot, astIndex) {
|
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
sections.push('HINT: Use smart_read() on module files, find_usages() for cross-module references.');
|
|
118
|
-
|
|
119
|
+
// Collect files from results for analytics
|
|
120
|
+
const metaFiles = [];
|
|
121
|
+
for (const r of results) {
|
|
122
|
+
if (r.status !== 'fulfilled')
|
|
123
|
+
continue;
|
|
124
|
+
const { type, data } = r.value;
|
|
125
|
+
switch (type) {
|
|
126
|
+
case 'api':
|
|
127
|
+
for (const a of data)
|
|
128
|
+
metaFiles.push(a.file);
|
|
129
|
+
break;
|
|
130
|
+
case 'deps':
|
|
131
|
+
case 'dependents':
|
|
132
|
+
case 'unused-deps':
|
|
133
|
+
for (const d of data)
|
|
134
|
+
metaFiles.push(d.path);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return { content: [{ type: 'text', text: sections.join('\n') }], meta: { files: [...new Set(metaFiles)] } };
|
|
119
139
|
}
|
|
120
140
|
function rel(projectRoot, absPath) {
|
|
121
141
|
return relative(projectRoot, absPath) || absPath;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AstIndexClient } from '../ast-index/client.js';
|
|
2
2
|
import type { ProjectOverviewArgs } from '../core/validation.js';
|
|
3
|
-
export declare function handleProjectOverview(args: ProjectOverviewArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
3
|
+
export declare function handleProjectOverview(args: ProjectOverviewArgs, projectRoot: string, astIndex: AstIndexClient, appVersion?: string): Promise<{
|
|
4
4
|
content: Array<{
|
|
5
5
|
type: 'text';
|
|
6
6
|
text: string;
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { detectProject } from '../core/project-detector.js';
|
|
2
|
-
|
|
2
|
+
import { loadFingerprint, saveFingerprint, buildFingerprint, formatCachedFingerprint, } from '../core/architecture-fingerprint.js';
|
|
3
|
+
export async function handleProjectOverview(args, projectRoot, astIndex, appVersion) {
|
|
3
4
|
const lines = [];
|
|
5
|
+
// Check for cached fingerprint
|
|
6
|
+
const cachedFp = await loadFingerprint(projectRoot);
|
|
7
|
+
if (cachedFp) {
|
|
8
|
+
lines.push(formatCachedFingerprint(cachedFp));
|
|
9
|
+
lines.push('');
|
|
10
|
+
}
|
|
4
11
|
// 1. Dual detection: ast-index + config scanner
|
|
5
12
|
let astIndexType;
|
|
6
13
|
let mapData = null;
|
|
@@ -117,7 +124,16 @@ export async function handleProjectOverview(args, projectRoot, astIndex) {
|
|
|
117
124
|
lines.push('');
|
|
118
125
|
}
|
|
119
126
|
lines.push('HINT: Use smart_read() on files, find_usages() for symbol references, outline() for directory overview.');
|
|
120
|
-
|
|
127
|
+
// Save fingerprint for next session
|
|
128
|
+
const outputText = lines.join('\n');
|
|
129
|
+
try {
|
|
130
|
+
const fp = buildFingerprint(outputText, appVersion ?? 'unknown');
|
|
131
|
+
await saveFingerprint(projectRoot, fp);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Non-critical — don't fail the handler
|
|
135
|
+
}
|
|
136
|
+
return { content: [{ type: 'text', text: outputText }] };
|
|
121
137
|
}
|
|
122
138
|
// ──────────────────────────────────────────────
|
|
123
139
|
// Formatters
|
|
@@ -7,6 +7,9 @@ export interface ReadForEditArgs {
|
|
|
7
7
|
symbol?: string;
|
|
8
8
|
line?: number;
|
|
9
9
|
context?: number;
|
|
10
|
+
include_callers?: boolean;
|
|
11
|
+
include_tests?: boolean;
|
|
12
|
+
include_changes?: boolean;
|
|
10
13
|
}
|
|
11
14
|
export declare function handleReadForEdit(args: ReadForEditArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex: AstIndexClient): Promise<{
|
|
12
15
|
content: Array<{
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import { readFile, stat } from 'node:fs/promises';
|
|
1
|
+
import { readFile, stat, access } from 'node:fs/promises';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
2
4
|
import { createHash } from 'node:crypto';
|
|
5
|
+
import { relative, join } from 'node:path';
|
|
3
6
|
import { estimateTokens } from '../core/token-estimator.js';
|
|
4
7
|
import { resolveSafePath } from '../core/validation.js';
|
|
8
|
+
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
5
10
|
const DEFAULT_CONTEXT = 5;
|
|
6
11
|
export async function handleReadForEdit(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex) {
|
|
7
12
|
const absPath = resolveSafePath(projectRoot, args.path);
|
|
@@ -91,7 +96,7 @@ export async function handleReadForEdit(args, projectRoot, symbolResolver, fileC
|
|
|
91
96
|
const rangeCount = rangeEnd - rangeStart + 1;
|
|
92
97
|
// Extract RAW code (no line number prefixes — ready for Edit old_string)
|
|
93
98
|
const rawCode = lines.slice(rangeStart - 1, rangeEnd).join('\n');
|
|
94
|
-
const
|
|
99
|
+
const outputLines = [
|
|
95
100
|
`--- EDIT CONTEXT ---`,
|
|
96
101
|
`FILE: ${args.path}`,
|
|
97
102
|
`TARGET: ${targetLabel}`,
|
|
@@ -103,7 +108,54 @@ export async function handleReadForEdit(args, projectRoot, symbolResolver, fileC
|
|
|
103
108
|
'',
|
|
104
109
|
`To edit: use exact text above as old_string in Edit tool.`,
|
|
105
110
|
`For Read requirement: Read("${args.path}", offset=${rangeStart}, limit=${rangeCount})`,
|
|
106
|
-
]
|
|
111
|
+
];
|
|
112
|
+
// --- Optional enrichment sections ---
|
|
113
|
+
// include_callers: compact caller list via ast-index refs
|
|
114
|
+
if (args.include_callers && args.symbol && !astIndex.isDisabled()) {
|
|
115
|
+
try {
|
|
116
|
+
const refs = await astIndex.refs(args.symbol, 10);
|
|
117
|
+
const callers = refs.usages.slice(0, 5);
|
|
118
|
+
if (callers.length > 0) {
|
|
119
|
+
outputLines.push('');
|
|
120
|
+
outputLines.push(`CALLERS (${callers.length}):`);
|
|
121
|
+
for (const c of callers) {
|
|
122
|
+
const relPath = relative(projectRoot, c.path);
|
|
123
|
+
const ctx = c.context ? ` — ${c.context.trim().slice(0, 80)}` : '';
|
|
124
|
+
outputLines.push(` ${relPath}:${c.line}${ctx}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
outputLines.push('');
|
|
129
|
+
outputLines.push('CALLERS: none found');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// ast-index unavailable — skip silently
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// include_tests: find related test file and list test names
|
|
137
|
+
if (args.include_tests) {
|
|
138
|
+
const testSection = await findTestSection(absPath, args.path, projectRoot, astIndex);
|
|
139
|
+
outputLines.push('');
|
|
140
|
+
outputLines.push(...testSection);
|
|
141
|
+
}
|
|
142
|
+
// include_changes: git diff filtered to target region
|
|
143
|
+
if (args.include_changes) {
|
|
144
|
+
const diffSection = await findChangesSection(absPath, projectRoot, rangeStart, rangeEnd);
|
|
145
|
+
outputLines.push('');
|
|
146
|
+
outputLines.push(...diffSection);
|
|
147
|
+
}
|
|
148
|
+
// Confidence metadata
|
|
149
|
+
const confidenceMeta = assessConfidence({
|
|
150
|
+
symbolResolved: !!args.symbol && startLine > 0,
|
|
151
|
+
fullFile: false,
|
|
152
|
+
truncated: false,
|
|
153
|
+
hasCallers: args.include_callers ?? false,
|
|
154
|
+
hasTests: args.include_tests ?? false,
|
|
155
|
+
astAvailable: true,
|
|
156
|
+
});
|
|
157
|
+
outputLines.push(formatConfidence(confidenceMeta));
|
|
158
|
+
const output = outputLines.join('\n');
|
|
107
159
|
const tokens = estimateTokens(output);
|
|
108
160
|
// Track in context
|
|
109
161
|
contextRegistry.trackLoad(absPath, {
|
|
@@ -115,4 +167,134 @@ export async function handleReadForEdit(args, projectRoot, symbolResolver, fileC
|
|
|
115
167
|
});
|
|
116
168
|
return { content: [{ type: 'text', text: output }] };
|
|
117
169
|
}
|
|
170
|
+
// --- Helper: find related test file and extract test names ---
|
|
171
|
+
async function findTestSection(absPath, relPath, projectRoot, astIndex) {
|
|
172
|
+
// Derive test file path from source path using common conventions
|
|
173
|
+
// src/handlers/foo.ts → tests/handlers/foo.test.ts
|
|
174
|
+
// src/core/bar.ts → tests/core/bar.test.ts
|
|
175
|
+
const srcPrefix = 'src/';
|
|
176
|
+
let testRelPath;
|
|
177
|
+
if (relPath.startsWith(srcPrefix)) {
|
|
178
|
+
const rest = relPath.slice(srcPrefix.length);
|
|
179
|
+
const ext = rest.match(/\.[^.]+$/)?.[0] ?? '.ts';
|
|
180
|
+
const base = rest.replace(/\.[^.]+$/, '');
|
|
181
|
+
testRelPath = `tests/${base}.test${ext}`;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
const ext = relPath.match(/\.[^.]+$/)?.[0] ?? '.ts';
|
|
185
|
+
const base = relPath.replace(/\.[^.]+$/, '');
|
|
186
|
+
testRelPath = `${base}.test${ext}`;
|
|
187
|
+
}
|
|
188
|
+
const testAbsPath = join(projectRoot, testRelPath);
|
|
189
|
+
try {
|
|
190
|
+
await access(testAbsPath);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
return [`TESTS: none found (expected at ${testRelPath})`];
|
|
194
|
+
}
|
|
195
|
+
// Test file exists — try to get outline for test names
|
|
196
|
+
const lines = [`TESTS: ${testRelPath}`];
|
|
197
|
+
if (!astIndex.isDisabled()) {
|
|
198
|
+
try {
|
|
199
|
+
const outline = await astIndex.outline(testAbsPath);
|
|
200
|
+
if (outline?.symbols && outline.symbols.length > 0) {
|
|
201
|
+
for (const sym of outline.symbols) {
|
|
202
|
+
lines.push(` ${sym.kind} ${sym.name}`);
|
|
203
|
+
if (sym.children) {
|
|
204
|
+
for (const child of sym.children) {
|
|
205
|
+
lines.push(` ${child.kind} ${child.name}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// outline failed — just show file path
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return lines;
|
|
216
|
+
}
|
|
217
|
+
// --- Helper: git diff filtered to target region ---
|
|
218
|
+
async function findChangesSection(absPath, projectRoot, rangeStart, rangeEnd) {
|
|
219
|
+
const MAX_DIFF_LINES = 30;
|
|
220
|
+
try {
|
|
221
|
+
// Try unstaged changes first
|
|
222
|
+
let diffOutput = '';
|
|
223
|
+
let diffLabel = 'unstaged';
|
|
224
|
+
try {
|
|
225
|
+
const { stdout } = await execFileAsync('git', ['diff', 'HEAD', '--', absPath], {
|
|
226
|
+
cwd: projectRoot,
|
|
227
|
+
timeout: 5000,
|
|
228
|
+
});
|
|
229
|
+
diffOutput = stdout;
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// git not available or not a repo
|
|
233
|
+
return ['RECENT CHANGES: unavailable (not a git repo)'];
|
|
234
|
+
}
|
|
235
|
+
// If no unstaged changes, try last commit
|
|
236
|
+
if (!diffOutput.trim()) {
|
|
237
|
+
try {
|
|
238
|
+
const { stdout } = await execFileAsync('git', ['diff', 'HEAD~1', '--', absPath], {
|
|
239
|
+
cwd: projectRoot,
|
|
240
|
+
timeout: 5000,
|
|
241
|
+
});
|
|
242
|
+
diffOutput = stdout;
|
|
243
|
+
diffLabel = 'last commit';
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// no previous commit
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!diffOutput.trim()) {
|
|
250
|
+
return ['RECENT CHANGES: none (file unchanged)'];
|
|
251
|
+
}
|
|
252
|
+
// Filter hunks to those overlapping with target range
|
|
253
|
+
const relevantLines = filterDiffHunks(diffOutput, rangeStart, rangeEnd);
|
|
254
|
+
if (relevantLines.length === 0) {
|
|
255
|
+
return ['RECENT CHANGES: none in target region'];
|
|
256
|
+
}
|
|
257
|
+
const lines = [`RECENT CHANGES (${diffLabel}):`];
|
|
258
|
+
const trimmed = relevantLines.slice(0, MAX_DIFF_LINES);
|
|
259
|
+
for (const line of trimmed) {
|
|
260
|
+
lines.push(` ${line}`);
|
|
261
|
+
}
|
|
262
|
+
if (relevantLines.length > MAX_DIFF_LINES) {
|
|
263
|
+
lines.push(` ... ${relevantLines.length - MAX_DIFF_LINES} more lines`);
|
|
264
|
+
}
|
|
265
|
+
return lines;
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return ['RECENT CHANGES: unavailable'];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Filter diff output to only hunks overlapping [rangeStart, rangeEnd]. */
|
|
272
|
+
function filterDiffHunks(diff, rangeStart, rangeEnd) {
|
|
273
|
+
const allLines = diff.split('\n');
|
|
274
|
+
const result = [];
|
|
275
|
+
let inRelevantHunk = false;
|
|
276
|
+
for (const line of allLines) {
|
|
277
|
+
// Hunk header: @@ -a,b +c,d @@
|
|
278
|
+
const hunkMatch = line.match(/^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/);
|
|
279
|
+
if (hunkMatch) {
|
|
280
|
+
const hunkStart = parseInt(hunkMatch[1], 10);
|
|
281
|
+
const hunkLen = parseInt(hunkMatch[2] ?? '1', 10);
|
|
282
|
+
const hunkEnd = hunkStart + hunkLen - 1;
|
|
283
|
+
// Check overlap with target range
|
|
284
|
+
inRelevantHunk = hunkStart <= rangeEnd && hunkEnd >= rangeStart;
|
|
285
|
+
if (inRelevantHunk) {
|
|
286
|
+
result.push(line);
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
// Skip diff metadata lines (diff --git, index, ---, +++)
|
|
291
|
+
if (line.startsWith('diff ') || line.startsWith('index ') || line.startsWith('--- ') || line.startsWith('+++ ')) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (inRelevantHunk && (line.startsWith('+') || line.startsWith('-') || line.startsWith(' '))) {
|
|
295
|
+
result.push(line);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
118
300
|
//# sourceMappingURL=read-for-edit.js.map
|
|
@@ -5,7 +5,7 @@ export interface ReadRangeArgs {
|
|
|
5
5
|
start_line: number;
|
|
6
6
|
end_line: number;
|
|
7
7
|
}
|
|
8
|
-
export declare function handleReadRange(args: ReadRangeArgs, projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry): Promise<{
|
|
8
|
+
export declare function handleReadRange(args: ReadRangeArgs, projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, advisoryReminders?: boolean): Promise<{
|
|
9
9
|
content: Array<{
|
|
10
10
|
type: 'text';
|
|
11
11
|
text: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { estimateTokens } from '../core/token-estimator.js';
|
|
3
3
|
import { resolveSafePath } from '../core/validation.js';
|
|
4
|
-
export async function handleReadRange(args, projectRoot, fileCache, contextRegistry) {
|
|
4
|
+
export async function handleReadRange(args, projectRoot, fileCache, contextRegistry, advisoryReminders = true) {
|
|
5
5
|
const absPath = resolveSafePath(projectRoot, args.path);
|
|
6
6
|
// Get lines
|
|
7
7
|
const cached = fileCache.get(absPath);
|
|
@@ -13,6 +13,18 @@ export async function handleReadRange(args, projectRoot, fileCache, contextRegis
|
|
|
13
13
|
const content = await readFile(absPath, 'utf-8');
|
|
14
14
|
lines = content.split('\n');
|
|
15
15
|
}
|
|
16
|
+
// Dedup: check if full file is already in context and unchanged
|
|
17
|
+
if (advisoryReminders) {
|
|
18
|
+
const hash = cached?.hash;
|
|
19
|
+
if (hash && !contextRegistry.isStale(absPath, hash)) {
|
|
20
|
+
if (contextRegistry.isFullyLoaded(absPath)) {
|
|
21
|
+
const reminder = contextRegistry.rangeReminder(absPath, args.start_line, args.end_line);
|
|
22
|
+
if (reminder) {
|
|
23
|
+
return { content: [{ type: 'text', text: reminder }] };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
16
28
|
const start = Math.max(0, args.start_line - 1);
|
|
17
29
|
const end = Math.min(lines.length, args.end_line);
|
|
18
30
|
if (start >= lines.length || start >= end) {
|
|
@@ -39,6 +51,9 @@ export async function handleReadRange(args, projectRoot, fileCache, contextRegis
|
|
|
39
51
|
endLine: args.end_line,
|
|
40
52
|
tokens,
|
|
41
53
|
});
|
|
54
|
+
if (cached?.hash) {
|
|
55
|
+
contextRegistry.setContentHash(absPath, cached.hash);
|
|
56
|
+
}
|
|
42
57
|
return { content: [{ type: 'text', text: output }] };
|
|
43
58
|
}
|
|
44
59
|
//# sourceMappingURL=read-range.js.map
|
|
@@ -9,7 +9,7 @@ export interface ReadSymbolArgs {
|
|
|
9
9
|
context_after?: number;
|
|
10
10
|
show?: 'full' | 'head' | 'tail' | 'outline';
|
|
11
11
|
}
|
|
12
|
-
export declare function handleReadSymbol(args: ReadSymbolArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex?: AstIndexClient): Promise<{
|
|
12
|
+
export declare function handleReadSymbol(args: ReadSymbolArgs, projectRoot: string, symbolResolver: SymbolResolver, fileCache: FileCache, contextRegistry: ContextRegistry, astIndex?: AstIndexClient, advisoryReminders?: boolean): Promise<{
|
|
13
13
|
content: Array<{
|
|
14
14
|
type: 'text';
|
|
15
15
|
text: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { estimateTokens } from '../core/token-estimator.js';
|
|
3
3
|
import { resolveSafePath } from '../core/validation.js';
|
|
4
|
-
|
|
4
|
+
import { assessConfidence, formatConfidence } from '../core/confidence.js';
|
|
5
|
+
export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCache, contextRegistry, astIndex, advisoryReminders = true) {
|
|
5
6
|
const absPath = resolveSafePath(projectRoot, args.path);
|
|
6
7
|
// Get file content
|
|
7
8
|
const cached = fileCache.get(absPath);
|
|
@@ -13,6 +14,18 @@ export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCa
|
|
|
13
14
|
const content = await readFile(absPath, 'utf-8');
|
|
14
15
|
lines = content.split('\n');
|
|
15
16
|
}
|
|
17
|
+
// Dedup: check if content already in context and unchanged
|
|
18
|
+
if (advisoryReminders) {
|
|
19
|
+
const hash = cached?.hash;
|
|
20
|
+
if (hash && !contextRegistry.isStale(absPath, hash)) {
|
|
21
|
+
if (contextRegistry.isFullyLoaded(absPath) || contextRegistry.isSymbolLoaded(absPath, args.symbol)) {
|
|
22
|
+
const reminder = contextRegistry.symbolReminder(absPath, args.symbol);
|
|
23
|
+
if (reminder) {
|
|
24
|
+
return { content: [{ type: 'text', text: reminder }] };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
16
29
|
// Resolve symbol — auto-fetch structure if not cached
|
|
17
30
|
let structure = cached?.structure;
|
|
18
31
|
if (!structure && astIndex) {
|
|
@@ -116,6 +129,17 @@ export async function handleReadSymbol(args, projectRoot, symbolResolver, fileCa
|
|
|
116
129
|
endLine: resolved.endLine,
|
|
117
130
|
tokens,
|
|
118
131
|
});
|
|
119
|
-
|
|
132
|
+
if (cached?.hash) {
|
|
133
|
+
contextRegistry.setContentHash(absPath, cached.hash);
|
|
134
|
+
}
|
|
135
|
+
// Confidence metadata
|
|
136
|
+
const confidenceMeta = assessConfidence({
|
|
137
|
+
symbolResolved: true,
|
|
138
|
+
truncated,
|
|
139
|
+
fullFile: false,
|
|
140
|
+
hasCallers: resolved.symbol.references.length > 0,
|
|
141
|
+
astAvailable: !!structure,
|
|
142
|
+
});
|
|
143
|
+
return { content: [{ type: 'text', text: output + formatConfidence(confidenceMeta) }] };
|
|
120
144
|
}
|
|
121
145
|
//# sourceMappingURL=read-symbol.js.map
|
|
@@ -2,10 +2,21 @@ import type { AstIndexClient } from '../ast-index/client.js';
|
|
|
2
2
|
export interface RelatedFilesArgs {
|
|
3
3
|
path: string;
|
|
4
4
|
}
|
|
5
|
+
export interface RelatedFilesMeta {
|
|
6
|
+
imports: string[];
|
|
7
|
+
importedBy: string[];
|
|
8
|
+
tests: string[];
|
|
9
|
+
ranked: {
|
|
10
|
+
high: string[];
|
|
11
|
+
medium: string[];
|
|
12
|
+
low: string[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
5
15
|
export declare function handleRelatedFiles(args: RelatedFilesArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
6
16
|
content: Array<{
|
|
7
17
|
type: 'text';
|
|
8
18
|
text: string;
|
|
9
19
|
}>;
|
|
20
|
+
meta: RelatedFilesMeta;
|
|
10
21
|
}>;
|
|
11
22
|
//# sourceMappingURL=related-files.d.ts.map
|