token-pilot 0.13.0 → 0.14.2
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 +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +29 -0
- package/README.md +36 -15
- 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 +62 -56
- package/dist/server.js +395 -30
- package/dist/types.d.ts +12 -0
- package/package.json +18 -14
- 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
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
export class SessionCache {
|
|
3
|
+
maxEntries;
|
|
4
|
+
entries = new Map();
|
|
5
|
+
/** Reverse index: file path → set of cache keys that depend on it */
|
|
6
|
+
fileDepsIndex = new Map();
|
|
7
|
+
hits = 0;
|
|
8
|
+
misses = 0;
|
|
9
|
+
invalidations = 0;
|
|
10
|
+
constructor(maxEntries) {
|
|
11
|
+
this.maxEntries = maxEntries;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Generate deterministic cache key from tool name + args.
|
|
15
|
+
* Sorts args keys for consistency regardless of insertion order.
|
|
16
|
+
*/
|
|
17
|
+
static makeCacheKey(tool, args) {
|
|
18
|
+
const sorted = JSON.stringify(args, Object.keys(args).sort());
|
|
19
|
+
const hash = createHash('sha256').update(sorted).digest('hex').slice(0, 16);
|
|
20
|
+
return `${tool}:${hash}`;
|
|
21
|
+
}
|
|
22
|
+
/** Try to get a cached result. Returns null on miss. */
|
|
23
|
+
get(tool, args) {
|
|
24
|
+
const key = SessionCache.makeCacheKey(tool, args);
|
|
25
|
+
const entry = this.entries.get(key);
|
|
26
|
+
if (entry) {
|
|
27
|
+
this.hits++;
|
|
28
|
+
return entry;
|
|
29
|
+
}
|
|
30
|
+
this.misses++;
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
/** Store a result with its dependency metadata. */
|
|
34
|
+
set(tool, args, result, deps, tokenEstimate, tokensWouldBe) {
|
|
35
|
+
// LRU eviction if full
|
|
36
|
+
if (this.entries.size >= this.maxEntries) {
|
|
37
|
+
this.evictOldest();
|
|
38
|
+
}
|
|
39
|
+
const key = SessionCache.makeCacheKey(tool, args);
|
|
40
|
+
const fileDeps = new Set(deps.files ?? []);
|
|
41
|
+
const entry = {
|
|
42
|
+
result,
|
|
43
|
+
fileDeps,
|
|
44
|
+
dependsOnAst: deps.dependsOnAst ?? false,
|
|
45
|
+
dependsOnGit: deps.dependsOnGit ?? false,
|
|
46
|
+
cachedAt: Date.now(),
|
|
47
|
+
tokenEstimate,
|
|
48
|
+
tokensWouldBe,
|
|
49
|
+
};
|
|
50
|
+
this.entries.set(key, entry);
|
|
51
|
+
// Update reverse index
|
|
52
|
+
for (const dep of fileDeps) {
|
|
53
|
+
let keys = this.fileDepsIndex.get(dep);
|
|
54
|
+
if (!keys) {
|
|
55
|
+
keys = new Set();
|
|
56
|
+
this.fileDepsIndex.set(dep, keys);
|
|
57
|
+
}
|
|
58
|
+
keys.add(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Invalidate all entries that depend on any of the given files.
|
|
63
|
+
* Checks both exact path match and directory prefix match.
|
|
64
|
+
*/
|
|
65
|
+
invalidateByFiles(filePaths) {
|
|
66
|
+
let count = 0;
|
|
67
|
+
const keysToDelete = new Set();
|
|
68
|
+
for (const changedFile of filePaths) {
|
|
69
|
+
// Exact match from reverse index
|
|
70
|
+
const exactKeys = this.fileDepsIndex.get(changedFile);
|
|
71
|
+
if (exactKeys) {
|
|
72
|
+
for (const key of exactKeys)
|
|
73
|
+
keysToDelete.add(key);
|
|
74
|
+
}
|
|
75
|
+
// Directory prefix match: check if changedFile is under any cached dir dep
|
|
76
|
+
for (const [dep, keys] of this.fileDepsIndex) {
|
|
77
|
+
if (dep.endsWith('/') && changedFile.startsWith(dep)) {
|
|
78
|
+
for (const key of keys)
|
|
79
|
+
keysToDelete.add(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for (const key of keysToDelete) {
|
|
84
|
+
this.deleteEntry(key);
|
|
85
|
+
count++;
|
|
86
|
+
}
|
|
87
|
+
this.invalidations += count;
|
|
88
|
+
return count;
|
|
89
|
+
}
|
|
90
|
+
/** Invalidate all entries that depend on AST index state. */
|
|
91
|
+
invalidateByAst() {
|
|
92
|
+
let count = 0;
|
|
93
|
+
for (const [key, entry] of this.entries) {
|
|
94
|
+
if (entry.dependsOnAst) {
|
|
95
|
+
this.deleteEntry(key);
|
|
96
|
+
count++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
this.invalidations += count;
|
|
100
|
+
return count;
|
|
101
|
+
}
|
|
102
|
+
/** Invalidate all entries that depend on git state. */
|
|
103
|
+
invalidateByGit() {
|
|
104
|
+
let count = 0;
|
|
105
|
+
for (const [key, entry] of this.entries) {
|
|
106
|
+
if (entry.dependsOnGit) {
|
|
107
|
+
this.deleteEntry(key);
|
|
108
|
+
count++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
this.invalidations += count;
|
|
112
|
+
return count;
|
|
113
|
+
}
|
|
114
|
+
/** Clear all entries. */
|
|
115
|
+
invalidateAll() {
|
|
116
|
+
const count = this.entries.size;
|
|
117
|
+
this.entries.clear();
|
|
118
|
+
this.fileDepsIndex.clear();
|
|
119
|
+
this.invalidations += count;
|
|
120
|
+
}
|
|
121
|
+
/** Cache statistics for analytics. */
|
|
122
|
+
stats() {
|
|
123
|
+
const total = this.hits + this.misses;
|
|
124
|
+
return {
|
|
125
|
+
entries: this.entries.size,
|
|
126
|
+
hits: this.hits,
|
|
127
|
+
misses: this.misses,
|
|
128
|
+
hitRate: total > 0 ? Math.round((this.hits / total) * 100) : 0,
|
|
129
|
+
invalidations: this.invalidations,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
// --- Private helpers ---
|
|
133
|
+
deleteEntry(key) {
|
|
134
|
+
const entry = this.entries.get(key);
|
|
135
|
+
if (!entry)
|
|
136
|
+
return;
|
|
137
|
+
// Clean up reverse index
|
|
138
|
+
for (const dep of entry.fileDeps) {
|
|
139
|
+
const keys = this.fileDepsIndex.get(dep);
|
|
140
|
+
if (keys) {
|
|
141
|
+
keys.delete(key);
|
|
142
|
+
if (keys.size === 0)
|
|
143
|
+
this.fileDepsIndex.delete(dep);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
this.entries.delete(key);
|
|
147
|
+
}
|
|
148
|
+
evictOldest() {
|
|
149
|
+
let oldestKey = null;
|
|
150
|
+
let oldestTime = Infinity;
|
|
151
|
+
for (const [key, entry] of this.entries) {
|
|
152
|
+
if (entry.cachedAt < oldestTime) {
|
|
153
|
+
oldestTime = entry.cachedAt;
|
|
154
|
+
oldestKey = key;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (oldestKey) {
|
|
158
|
+
this.deleteEntry(oldestKey);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=session-cache.js.map
|
|
@@ -64,6 +64,9 @@ export declare function validateReadForEditArgs(args: unknown): {
|
|
|
64
64
|
symbol?: string;
|
|
65
65
|
line?: number;
|
|
66
66
|
context?: number;
|
|
67
|
+
include_callers?: boolean;
|
|
68
|
+
include_tests?: boolean;
|
|
69
|
+
include_changes?: boolean;
|
|
67
70
|
};
|
|
68
71
|
/**
|
|
69
72
|
* Validate related_files arguments.
|
package/dist/core/validation.js
CHANGED
|
@@ -184,6 +184,9 @@ export function validateReadForEditArgs(args) {
|
|
|
184
184
|
symbol: optionalString(a.symbol, 'symbol'),
|
|
185
185
|
line: optionalNumber(a.line, 'line'),
|
|
186
186
|
context: optionalNumber(a.context, 'context'),
|
|
187
|
+
include_callers: optionalBool(a.include_callers, 'include_callers'),
|
|
188
|
+
include_tests: optionalBool(a.include_tests, 'include_tests'),
|
|
189
|
+
include_changes: optionalBool(a.include_changes, 'include_changes'),
|
|
187
190
|
};
|
|
188
191
|
}
|
|
189
192
|
/**
|
|
@@ -18,10 +18,16 @@ export declare class FileWatcher {
|
|
|
18
18
|
private watcher;
|
|
19
19
|
private watchedFiles;
|
|
20
20
|
private updateTimer;
|
|
21
|
+
private fileChangeCallback;
|
|
22
|
+
private astUpdateCallback;
|
|
21
23
|
constructor(_projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, _ignore: string[], astIndex?: AstIndexClient);
|
|
22
24
|
start(): void;
|
|
23
25
|
/** Debounced ast-index incremental update after file changes */
|
|
24
26
|
private scheduleIndexUpdate;
|
|
27
|
+
/** Register callback for file change/unlink events. */
|
|
28
|
+
onFileChange(callback: (absPath: string) => void): void;
|
|
29
|
+
/** Register callback for after AST index incremental update completes. */
|
|
30
|
+
onAstUpdate(callback: () => void): void;
|
|
25
31
|
/** Add a specific file to watch. Called after smart_read/read_symbol loads a file. */
|
|
26
32
|
watchFile(filePath: string): void;
|
|
27
33
|
stop(): void;
|
package/dist/git/file-watcher.js
CHANGED
|
@@ -17,6 +17,8 @@ export class FileWatcher {
|
|
|
17
17
|
watcher = null;
|
|
18
18
|
watchedFiles = new Set();
|
|
19
19
|
updateTimer = null;
|
|
20
|
+
fileChangeCallback = null;
|
|
21
|
+
astUpdateCallback = null;
|
|
20
22
|
constructor(_projectRoot, fileCache, contextRegistry, _ignore, astIndex) {
|
|
21
23
|
this.fileCache = fileCache;
|
|
22
24
|
this.contextRegistry = contextRegistry;
|
|
@@ -37,6 +39,7 @@ export class FileWatcher {
|
|
|
37
39
|
if (this.fileCache.get(absPath)) {
|
|
38
40
|
this.fileCache.invalidate(absPath);
|
|
39
41
|
}
|
|
42
|
+
this.fileChangeCallback?.(absPath);
|
|
40
43
|
this.scheduleIndexUpdate();
|
|
41
44
|
});
|
|
42
45
|
this.watcher.on('unlink', (filePath) => {
|
|
@@ -44,6 +47,7 @@ export class FileWatcher {
|
|
|
44
47
|
this.fileCache.invalidate(absPath);
|
|
45
48
|
this.contextRegistry.forget(absPath);
|
|
46
49
|
this.watchedFiles.delete(absPath);
|
|
50
|
+
this.fileChangeCallback?.(absPath);
|
|
47
51
|
this.scheduleIndexUpdate();
|
|
48
52
|
});
|
|
49
53
|
}
|
|
@@ -53,10 +57,22 @@ export class FileWatcher {
|
|
|
53
57
|
return;
|
|
54
58
|
if (this.updateTimer)
|
|
55
59
|
clearTimeout(this.updateTimer);
|
|
56
|
-
this.updateTimer = setTimeout(() => {
|
|
57
|
-
|
|
60
|
+
this.updateTimer = setTimeout(async () => {
|
|
61
|
+
try {
|
|
62
|
+
await this.astIndex?.incrementalUpdate();
|
|
63
|
+
this.astUpdateCallback?.();
|
|
64
|
+
}
|
|
65
|
+
catch { /* ignore */ }
|
|
58
66
|
}, FileWatcher.UPDATE_DEBOUNCE_MS);
|
|
59
67
|
}
|
|
68
|
+
/** Register callback for file change/unlink events. */
|
|
69
|
+
onFileChange(callback) {
|
|
70
|
+
this.fileChangeCallback = callback;
|
|
71
|
+
}
|
|
72
|
+
/** Register callback for after AST index incremental update completes. */
|
|
73
|
+
onAstUpdate(callback) {
|
|
74
|
+
this.astUpdateCallback = callback;
|
|
75
|
+
}
|
|
60
76
|
/** Add a specific file to watch. Called after smart_read/read_symbol loads a file. */
|
|
61
77
|
watchFile(filePath) {
|
|
62
78
|
const absPath = resolve(filePath);
|
package/dist/git/watcher.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export declare class GitWatcher {
|
|
|
7
7
|
private watcher;
|
|
8
8
|
private headRef;
|
|
9
9
|
private enabled;
|
|
10
|
+
private branchSwitchCallback;
|
|
10
11
|
constructor(projectRoot: string, fileCache: FileCache, contextRegistry: ContextRegistry, enabled: boolean);
|
|
11
12
|
start(): Promise<void>;
|
|
12
13
|
stop(): void;
|
|
@@ -19,6 +20,8 @@ export declare class GitWatcher {
|
|
|
19
20
|
* Get files changed in the last N commits.
|
|
20
21
|
*/
|
|
21
22
|
getRecentlyChangedFiles(commits?: number): Promise<string[]>;
|
|
23
|
+
/** Register callback for branch switch events. */
|
|
24
|
+
onBranchSwitchEvent(callback: (changedFiles: string[]) => void): void;
|
|
22
25
|
private onBranchSwitch;
|
|
23
26
|
private readHead;
|
|
24
27
|
}
|
package/dist/git/watcher.js
CHANGED
|
@@ -11,6 +11,7 @@ export class GitWatcher {
|
|
|
11
11
|
watcher = null;
|
|
12
12
|
headRef = '';
|
|
13
13
|
enabled;
|
|
14
|
+
branchSwitchCallback = null;
|
|
14
15
|
constructor(projectRoot, fileCache, contextRegistry, enabled) {
|
|
15
16
|
this.projectRoot = projectRoot;
|
|
16
17
|
this.fileCache = fileCache;
|
|
@@ -78,6 +79,10 @@ export class GitWatcher {
|
|
|
78
79
|
return [];
|
|
79
80
|
}
|
|
80
81
|
}
|
|
82
|
+
/** Register callback for branch switch events. */
|
|
83
|
+
onBranchSwitchEvent(callback) {
|
|
84
|
+
this.branchSwitchCallback = callback;
|
|
85
|
+
}
|
|
81
86
|
async onBranchSwitch() {
|
|
82
87
|
// On branch switch, get files that differ between old and new branch
|
|
83
88
|
// and selectively invalidate only those
|
|
@@ -85,6 +90,7 @@ export class GitWatcher {
|
|
|
85
90
|
if (changed.length > 0) {
|
|
86
91
|
await this.fileCache.invalidateByGitDiff(changed);
|
|
87
92
|
this.contextRegistry.invalidateByGitDiff(changed);
|
|
93
|
+
this.branchSwitchCallback?.(changed);
|
|
88
94
|
}
|
|
89
95
|
}
|
|
90
96
|
async readHead() {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { AstIndexClient } from '../ast-index/client.js';
|
|
2
2
|
import type { CodeAuditArgs } from '../core/validation.js';
|
|
3
|
-
|
|
3
|
+
type AuditResult = {
|
|
4
4
|
content: Array<{
|
|
5
5
|
type: 'text';
|
|
6
6
|
text: string;
|
|
7
7
|
}>;
|
|
8
|
-
|
|
8
|
+
meta: {
|
|
9
|
+
files: string[];
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
export declare function handleCodeAudit(args: CodeAuditArgs, projectRoot: string, astIndex: AstIndexClient): Promise<AuditResult>;
|
|
13
|
+
export {};
|
|
9
14
|
//# sourceMappingURL=code-audit.d.ts.map
|
|
@@ -6,6 +6,7 @@ export async function handleCodeAudit(args, projectRoot, astIndex) {
|
|
|
6
6
|
type: 'text',
|
|
7
7
|
text: 'ast-index is not available (project root too broad or index oversized). Use Grep/ripgrep for pattern search.',
|
|
8
8
|
}],
|
|
9
|
+
meta: { files: [] },
|
|
9
10
|
};
|
|
10
11
|
}
|
|
11
12
|
const limit = args.limit ?? 50;
|
|
@@ -27,6 +28,7 @@ export async function handleCodeAudit(args, projectRoot, astIndex) {
|
|
|
27
28
|
type: 'text',
|
|
28
29
|
text: `Unknown check type: "${args.check}". Use: pattern, todo, deprecated, annotations, all`,
|
|
29
30
|
}],
|
|
31
|
+
meta: { files: [] },
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
}
|
|
@@ -42,6 +44,7 @@ async function handlePattern(pattern, lang, limit, projectRoot, astIndex) {
|
|
|
42
44
|
type: 'text',
|
|
43
45
|
text: `PATTERN SEARCH: "${pattern}"${lang ? ` (${lang})` : ''}\n\nNo matches found.\n\nHINT: Try Grep/ripgrep for text-based search if the pattern is not structural.`,
|
|
44
46
|
}],
|
|
47
|
+
meta: { files: [] },
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
50
|
// Group by file
|
|
@@ -64,7 +67,7 @@ async function handlePattern(pattern, lang, limit, projectRoot, astIndex) {
|
|
|
64
67
|
lines.push('');
|
|
65
68
|
}
|
|
66
69
|
lines.push('HINT: Use read_symbol() to inspect specific matches, or Grep for text-based counting.');
|
|
67
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
70
|
+
return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: [...byFile.keys()] } };
|
|
68
71
|
}
|
|
69
72
|
catch (err) {
|
|
70
73
|
// ast-grep not installed — return the error message
|
|
@@ -73,6 +76,7 @@ async function handlePattern(pattern, lang, limit, projectRoot, astIndex) {
|
|
|
73
76
|
type: 'text',
|
|
74
77
|
text: `PATTERN SEARCH ERROR:\n${err instanceof Error ? err.message : String(err)}\n\nFallback: Use Grep/ripgrep for text-based pattern search.`,
|
|
75
78
|
}],
|
|
79
|
+
meta: { files: [] },
|
|
76
80
|
};
|
|
77
81
|
}
|
|
78
82
|
}
|
|
@@ -85,6 +89,7 @@ async function handleTodo(limit, projectRoot, astIndex) {
|
|
|
85
89
|
type: 'text',
|
|
86
90
|
text: 'TODO/FIXME COMMENTS: none found.\n\nHINT: ast-index may not detect all comment formats. Try Grep: grep -rn "TODO\\|FIXME\\|HACK" --include="*.ts"',
|
|
87
91
|
}],
|
|
92
|
+
meta: { files: [] },
|
|
88
93
|
};
|
|
89
94
|
}
|
|
90
95
|
// Group by kind
|
|
@@ -106,7 +111,8 @@ async function handleTodo(limit, projectRoot, astIndex) {
|
|
|
106
111
|
}
|
|
107
112
|
lines.push('');
|
|
108
113
|
}
|
|
109
|
-
|
|
114
|
+
const todoFiles = [...new Set(limited.map(e => e.file))];
|
|
115
|
+
return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: todoFiles } };
|
|
110
116
|
}
|
|
111
117
|
async function handleDeprecated(limit, projectRoot, astIndex) {
|
|
112
118
|
const entries = await astIndex.deprecated();
|
|
@@ -117,6 +123,7 @@ async function handleDeprecated(limit, projectRoot, astIndex) {
|
|
|
117
123
|
type: 'text',
|
|
118
124
|
text: 'DEPRECATED SYMBOLS: none found.\n\nHINT: ast-index detects @Deprecated annotations. Try Grep for other deprecation patterns.',
|
|
119
125
|
}],
|
|
126
|
+
meta: { files: [] },
|
|
120
127
|
};
|
|
121
128
|
}
|
|
122
129
|
const lines = [
|
|
@@ -129,7 +136,8 @@ async function handleDeprecated(limit, projectRoot, astIndex) {
|
|
|
129
136
|
}
|
|
130
137
|
lines.push('');
|
|
131
138
|
lines.push('HINT: Use read_symbol() to inspect deprecated symbols before removing them.');
|
|
132
|
-
|
|
139
|
+
const depFiles = [...new Set(limited.map(e => e.file))];
|
|
140
|
+
return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: depFiles } };
|
|
133
141
|
}
|
|
134
142
|
async function handleAnnotations(name, limit, projectRoot, astIndex) {
|
|
135
143
|
const entries = await astIndex.annotations(name);
|
|
@@ -140,6 +148,7 @@ async function handleAnnotations(name, limit, projectRoot, astIndex) {
|
|
|
140
148
|
type: 'text',
|
|
141
149
|
text: `ANNOTATIONS @${name}: none found.\n\nHINT: Try Grep for text-based search: grep -rn "@${name}" --include="*.ts"`,
|
|
142
150
|
}],
|
|
151
|
+
meta: { files: [] },
|
|
143
152
|
};
|
|
144
153
|
}
|
|
145
154
|
// Group by file
|
|
@@ -161,7 +170,8 @@ async function handleAnnotations(name, limit, projectRoot, astIndex) {
|
|
|
161
170
|
}
|
|
162
171
|
lines.push('');
|
|
163
172
|
}
|
|
164
|
-
|
|
173
|
+
const annFiles = [...new Set(limited.map(e => e.file))];
|
|
174
|
+
return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: annFiles } };
|
|
165
175
|
}
|
|
166
176
|
async function handleAll(limit, projectRoot, astIndex) {
|
|
167
177
|
// Run todo + deprecated in parallel
|
|
@@ -196,6 +206,10 @@ async function handleAll(limit, projectRoot, astIndex) {
|
|
|
196
206
|
sections.push('');
|
|
197
207
|
sections.push('HINT: Use code_audit(check="pattern", pattern="...") for structural pattern search (requires ast-grep).');
|
|
198
208
|
sections.push(' Use Grep for text-based counting and regex search.');
|
|
199
|
-
|
|
209
|
+
const allFiles = [...new Set([
|
|
210
|
+
...todos.slice(0, limit).map(e => e.file),
|
|
211
|
+
...deprecated.slice(0, limit).map(e => e.file),
|
|
212
|
+
])];
|
|
213
|
+
return { content: [{ type: 'text', text: sections.join('\n') }], meta: { files: allFiles } };
|
|
200
214
|
}
|
|
201
215
|
//# sourceMappingURL=code-audit.js.map
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import type { AstIndexClient } from '../ast-index/client.js';
|
|
2
2
|
import type { ExploreAreaArgs } from '../core/validation.js';
|
|
3
|
+
export interface ExploreAreaMeta {
|
|
4
|
+
dir: string;
|
|
5
|
+
codeFiles: string[];
|
|
6
|
+
testFiles: string[];
|
|
7
|
+
internalDeps: string[];
|
|
8
|
+
importedBy: string[];
|
|
9
|
+
externalDeps: string[];
|
|
10
|
+
changeCount: number;
|
|
11
|
+
}
|
|
3
12
|
export declare function handleExploreArea(args: ExploreAreaArgs, projectRoot: string, astIndex: AstIndexClient): Promise<{
|
|
4
13
|
content: Array<{
|
|
5
14
|
type: 'text';
|
|
6
15
|
text: string;
|
|
7
16
|
}>;
|
|
17
|
+
meta: ExploreAreaMeta;
|
|
8
18
|
}>;
|
|
9
19
|
//# sourceMappingURL=explore-area.d.ts.map
|
|
@@ -20,6 +20,15 @@ export async function handleExploreArea(args, projectRoot, astIndex) {
|
|
|
20
20
|
if (!pathStat) {
|
|
21
21
|
return {
|
|
22
22
|
content: [{ type: 'text', text: `Path "${args.path}" not found.` }],
|
|
23
|
+
meta: {
|
|
24
|
+
dir: args.path,
|
|
25
|
+
codeFiles: [],
|
|
26
|
+
testFiles: [],
|
|
27
|
+
internalDeps: [],
|
|
28
|
+
importedBy: [],
|
|
29
|
+
externalDeps: [],
|
|
30
|
+
changeCount: 0,
|
|
31
|
+
},
|
|
23
32
|
};
|
|
24
33
|
}
|
|
25
34
|
if (!pathStat.isDirectory()) {
|
|
@@ -49,17 +58,17 @@ export async function handleExploreArea(args, projectRoot, astIndex) {
|
|
|
49
58
|
lines.push('');
|
|
50
59
|
}
|
|
51
60
|
// Imports
|
|
52
|
-
const importLines = extractResult(importsSection);
|
|
61
|
+
const importLines = extractResult(importsSection)?.lines ?? null;
|
|
53
62
|
if (importLines) {
|
|
54
63
|
lines.push(...importLines);
|
|
55
64
|
}
|
|
56
65
|
// Tests
|
|
57
|
-
const testLines = extractResult(testsSection);
|
|
66
|
+
const testLines = extractResult(testsSection)?.lines ?? null;
|
|
58
67
|
if (testLines) {
|
|
59
68
|
lines.push(...testLines);
|
|
60
69
|
}
|
|
61
70
|
// Changes
|
|
62
|
-
const changeLines = extractResult(changesSection);
|
|
71
|
+
const changeLines = extractResult(changesSection)?.lines ?? null;
|
|
63
72
|
if (changeLines) {
|
|
64
73
|
lines.push(...changeLines);
|
|
65
74
|
}
|
|
@@ -69,7 +78,18 @@ export async function handleExploreArea(args, projectRoot, astIndex) {
|
|
|
69
78
|
lines.push('... truncated. Use outline() on specific subdirectories for details.');
|
|
70
79
|
}
|
|
71
80
|
lines.push('HINT: Use smart_read(file) for details, read_symbol(path, symbol) for source code, find_usages(symbol) for references.');
|
|
72
|
-
return {
|
|
81
|
+
return {
|
|
82
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
83
|
+
meta: {
|
|
84
|
+
dir: relDir,
|
|
85
|
+
codeFiles: codeFiles.map((file) => relative(projectRoot, file)).sort(),
|
|
86
|
+
testFiles: extractResult(testsSection)?.testFiles ?? [],
|
|
87
|
+
internalDeps: extractResult(importsSection)?.internalDeps ?? [],
|
|
88
|
+
importedBy: extractResult(importsSection)?.importedBy ?? [],
|
|
89
|
+
externalDeps: extractResult(importsSection)?.externalDeps ?? [],
|
|
90
|
+
changeCount: extractResult(changesSection)?.count ?? 0,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
73
93
|
}
|
|
74
94
|
// ──────────────────────────────────────────────
|
|
75
95
|
// Outline section — reuses outlineDir from outline.ts
|
|
@@ -84,7 +104,7 @@ async function buildOutlineSection(absPath, projectRoot, astIndex) {
|
|
|
84
104
|
// ──────────────────────────────────────────────
|
|
85
105
|
async function buildImportsSection(codeFiles, absPath, projectRoot, astIndex) {
|
|
86
106
|
if (!astIndex.isAvailable() || astIndex.isDisabled() || astIndex.isOversized()) {
|
|
87
|
-
return [];
|
|
107
|
+
return { lines: [], internalDeps: [], importedBy: [], externalDeps: [] };
|
|
88
108
|
}
|
|
89
109
|
const filesToAnalyze = codeFiles.slice(0, MAX_IMPORT_FILES);
|
|
90
110
|
const externalDeps = new Set();
|
|
@@ -150,7 +170,12 @@ async function buildImportsSection(codeFiles, absPath, projectRoot, astIndex) {
|
|
|
150
170
|
}
|
|
151
171
|
if (lines.length > 0)
|
|
152
172
|
lines.push('');
|
|
153
|
-
return
|
|
173
|
+
return {
|
|
174
|
+
lines,
|
|
175
|
+
internalDeps: Array.from(internalDeps).sort(),
|
|
176
|
+
importedBy: Array.from(importedBy).sort(),
|
|
177
|
+
externalDeps: Array.from(externalDeps).sort(),
|
|
178
|
+
};
|
|
154
179
|
}
|
|
155
180
|
// ──────────────────────────────────────────────
|
|
156
181
|
// Tests section — find test/spec files matching area files
|
|
@@ -215,11 +240,11 @@ async function buildTestsSection(codeFiles, absPath, projectRoot) {
|
|
|
215
240
|
catch { /* skip unreadable dirs */ }
|
|
216
241
|
}
|
|
217
242
|
if (testFiles.length === 0)
|
|
218
|
-
return [];
|
|
243
|
+
return { lines: [], testFiles: [] };
|
|
219
244
|
const lines = [];
|
|
220
245
|
lines.push(`TESTS: ${testFiles.join(', ')}`);
|
|
221
246
|
lines.push('');
|
|
222
|
-
return lines;
|
|
247
|
+
return { lines, testFiles: [...testFiles].sort() };
|
|
223
248
|
}
|
|
224
249
|
// ──────────────────────────────────────────────
|
|
225
250
|
// Changes section — recent git log for this area
|
|
@@ -228,24 +253,25 @@ async function buildChangesSection(relDir, projectRoot) {
|
|
|
228
253
|
try {
|
|
229
254
|
const { stdout } = await execFileAsync('git', ['log', '--oneline', '-5', '--', relDir], { cwd: projectRoot, timeout: 5000 });
|
|
230
255
|
if (!stdout.trim())
|
|
231
|
-
return [];
|
|
256
|
+
return { lines: [], count: 0 };
|
|
232
257
|
const lines = [];
|
|
258
|
+
const commits = stdout.trim().split('\n');
|
|
233
259
|
lines.push('RECENT CHANGES:');
|
|
234
|
-
for (const line of
|
|
260
|
+
for (const line of commits) {
|
|
235
261
|
lines.push(` ${line}`);
|
|
236
262
|
}
|
|
237
263
|
lines.push('');
|
|
238
|
-
return lines;
|
|
264
|
+
return { lines, count: commits.length };
|
|
239
265
|
}
|
|
240
266
|
catch {
|
|
241
|
-
return [];
|
|
267
|
+
return { lines: [], count: 0 };
|
|
242
268
|
}
|
|
243
269
|
}
|
|
244
270
|
// ──────────────────────────────────────────────
|
|
245
271
|
// Helpers
|
|
246
272
|
// ──────────────────────────────────────────────
|
|
247
273
|
function extractResult(settled) {
|
|
248
|
-
if (settled.status === 'fulfilled' && settled.value
|
|
274
|
+
if (settled.status === 'fulfilled' && settled.value) {
|
|
249
275
|
return settled.value;
|
|
250
276
|
}
|
|
251
277
|
return null;
|
|
@@ -18,7 +18,7 @@ export async function handleFindUnused(args, astIndex) {
|
|
|
18
18
|
return { content: [{ type: 'text', text: 'find_unused is disabled: ' + (astIndex.isDisabled()
|
|
19
19
|
? 'project root not detected. Call smart_read() on any project file first — this auto-detects the project root and enables ast-index tools.'
|
|
20
20
|
: 'ast-index built >50k files (likely includes node_modules). Ensure node_modules is in .gitignore.') +
|
|
21
|
-
'\nAlternative: use grep_search to find unused exports manually.' }] };
|
|
21
|
+
'\nAlternative: use grep_search to find unused exports manually.' }], meta: { files: [] } };
|
|
22
22
|
}
|
|
23
23
|
const requestLimit = (args.limit ?? 30) + 20; // extra to compensate for filtering
|
|
24
24
|
const unused = await astIndex.unusedSymbols({
|
|
@@ -67,6 +67,7 @@ export async function handleFindUnused(args, astIndex) {
|
|
|
67
67
|
? `No unused symbols found in module "${args.module}".${excluded > 0 ? ` (${excluded} constructors/protocol methods excluded)` : ''}`
|
|
68
68
|
: `No unused symbols found in the project.${excluded > 0 ? ` (${excluded} constructors/protocol methods excluded)` : ''}`,
|
|
69
69
|
}],
|
|
70
|
+
meta: { files: [] },
|
|
70
71
|
};
|
|
71
72
|
}
|
|
72
73
|
const lines = [];
|
|
@@ -100,7 +101,7 @@ export async function handleFindUnused(args, astIndex) {
|
|
|
100
101
|
lines.push(`(${langExcluded} constructors/protocol methods excluded)`);
|
|
101
102
|
}
|
|
102
103
|
lines.push('NOTE: Verify before removing — symbols may be used dynamically, in tests, or via framework conventions.');
|
|
103
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
104
|
+
return { content: [{ type: 'text', text: lines.join('\n') }], meta: { files: uniqueFiles } };
|
|
104
105
|
}
|
|
105
106
|
/**
|
|
106
107
|
* Find decorators for a symbol by matching name + line in the outline tree.
|
|
@@ -15,5 +15,12 @@ export declare function handleFindUsages(args: FindUsagesArgs, astIndex: AstInde
|
|
|
15
15
|
type: 'text';
|
|
16
16
|
text: string;
|
|
17
17
|
}>;
|
|
18
|
+
meta: {
|
|
19
|
+
files: string[];
|
|
20
|
+
definitions: number;
|
|
21
|
+
imports: number;
|
|
22
|
+
usages: number;
|
|
23
|
+
total: number;
|
|
24
|
+
};
|
|
18
25
|
}>;
|
|
19
26
|
//# sourceMappingURL=find-usages.d.ts.map
|