@blamechris/repo-memory 0.1.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/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/cache/gc.d.ts +14 -0
- package/dist/cache/gc.d.ts.map +1 -0
- package/dist/cache/gc.js +75 -0
- package/dist/cache/gc.js.map +1 -0
- package/dist/cache/hash.d.ts +10 -0
- package/dist/cache/hash.d.ts.map +1 -0
- package/dist/cache/hash.js +22 -0
- package/dist/cache/hash.js.map +1 -0
- package/dist/cache/invalidation.d.ts +29 -0
- package/dist/cache/invalidation.d.ts.map +1 -0
- package/dist/cache/invalidation.js +58 -0
- package/dist/cache/invalidation.js.map +1 -0
- package/dist/cache/ranking.d.ts +57 -0
- package/dist/cache/ranking.d.ts.map +1 -0
- package/dist/cache/ranking.js +196 -0
- package/dist/cache/ranking.js.map +1 -0
- package/dist/cache/store.d.ts +16 -0
- package/dist/cache/store.d.ts.map +1 -0
- package/dist/cache/store.js +70 -0
- package/dist/cache/store.js.map +1 -0
- package/dist/graph/dependency-graph.d.ts +26 -0
- package/dist/graph/dependency-graph.d.ts.map +1 -0
- package/dist/graph/dependency-graph.js +131 -0
- package/dist/graph/dependency-graph.js.map +1 -0
- package/dist/indexer/diff-analyzer.d.ts +8 -0
- package/dist/indexer/diff-analyzer.d.ts.map +1 -0
- package/dist/indexer/diff-analyzer.js +95 -0
- package/dist/indexer/diff-analyzer.js.map +1 -0
- package/dist/indexer/imports.d.ts +3 -0
- package/dist/indexer/imports.d.ts.map +1 -0
- package/dist/indexer/imports.js +123 -0
- package/dist/indexer/imports.js.map +1 -0
- package/dist/indexer/project-map.d.ts +20 -0
- package/dist/indexer/project-map.d.ts.map +1 -0
- package/dist/indexer/project-map.js +120 -0
- package/dist/indexer/project-map.js.map +1 -0
- package/dist/indexer/scanner.d.ts +7 -0
- package/dist/indexer/scanner.d.ts.map +1 -0
- package/dist/indexer/scanner.js +114 -0
- package/dist/indexer/scanner.js.map +1 -0
- package/dist/indexer/smart-summarizer.d.ts +6 -0
- package/dist/indexer/smart-summarizer.d.ts.map +1 -0
- package/dist/indexer/smart-summarizer.js +26 -0
- package/dist/indexer/smart-summarizer.js.map +1 -0
- package/dist/indexer/summarizer.d.ts +3 -0
- package/dist/indexer/summarizer.d.ts.map +1 -0
- package/dist/indexer/summarizer.js +145 -0
- package/dist/indexer/summarizer.js.map +1 -0
- package/dist/memory/session.d.ts +18 -0
- package/dist/memory/session.d.ts.map +1 -0
- package/dist/memory/session.js +75 -0
- package/dist/memory/session.js.map +1 -0
- package/dist/memory/task.d.ts +35 -0
- package/dist/memory/task.d.ts.map +1 -0
- package/dist/memory/task.js +120 -0
- package/dist/memory/task.js.map +1 -0
- package/dist/persistence/db.d.ts +4 -0
- package/dist/persistence/db.d.ts.map +1 -0
- package/dist/persistence/db.js +146 -0
- package/dist/persistence/db.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +197 -0
- package/dist/server.js.map +1 -0
- package/dist/telemetry/tracker.d.ts +40 -0
- package/dist/telemetry/tracker.d.ts.map +1 -0
- package/dist/telemetry/tracker.js +136 -0
- package/dist/telemetry/tracker.js.map +1 -0
- package/dist/tools/force-reread.d.ts +9 -0
- package/dist/tools/force-reread.d.ts.map +1 -0
- package/dist/tools/force-reread.js +17 -0
- package/dist/tools/force-reread.js.map +1 -0
- package/dist/tools/get-changed-files.d.ts +8 -0
- package/dist/tools/get-changed-files.d.ts.map +1 -0
- package/dist/tools/get-changed-files.js +61 -0
- package/dist/tools/get-changed-files.js.map +1 -0
- package/dist/tools/get-dependency-graph.d.ts +17 -0
- package/dist/tools/get-dependency-graph.d.ts.map +1 -0
- package/dist/tools/get-dependency-graph.js +88 -0
- package/dist/tools/get-dependency-graph.js.map +1 -0
- package/dist/tools/get-file-summary.d.ts +12 -0
- package/dist/tools/get-file-summary.d.ts.map +1 -0
- package/dist/tools/get-file-summary.js +53 -0
- package/dist/tools/get-file-summary.js.map +1 -0
- package/dist/tools/get-project-map.d.ts +3 -0
- package/dist/tools/get-project-map.d.ts.map +1 -0
- package/dist/tools/get-project-map.js +5 -0
- package/dist/tools/get-project-map.js.map +1 -0
- package/dist/tools/get-token-report.d.ts +16 -0
- package/dist/tools/get-token-report.d.ts.map +1 -0
- package/dist/tools/get-token-report.js +44 -0
- package/dist/tools/get-token-report.js.map +1 -0
- package/dist/tools/invalidate.d.ts +5 -0
- package/dist/tools/invalidate.d.ts.map +1 -0
- package/dist/tools/invalidate.js +19 -0
- package/dist/tools/invalidate.js.map +1 -0
- package/dist/tools/task-context.d.ts +18 -0
- package/dist/tools/task-context.d.ts.map +1 -0
- package/dist/tools/task-context.js +28 -0
- package/dist/tools/task-context.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/validate-path.d.ts +10 -0
- package/dist/utils/validate-path.d.ts.map +1 -0
- package/dist/utils/validate-path.js +22 -0
- package/dist/utils/validate-path.js.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 blamechris
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# repo-memory
|
|
2
|
+
|
|
3
|
+
An MCP server that gives AI coding agents persistent memory about your codebase.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
AI agents waste tokens re-reading files they've already seen. repo-memory fixes this by:
|
|
8
|
+
- **Caching file summaries** -- exports, imports, purpose, line count
|
|
9
|
+
- **Tracking changes** -- only re-read files that actually changed (SHA-256 hash comparison)
|
|
10
|
+
- **Dependency graphs** -- understand which files depend on which
|
|
11
|
+
- **Task memory** -- remember what's been explored across conversation turns
|
|
12
|
+
- **Token telemetry** -- measure and prove the savings
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### With Claude Code
|
|
17
|
+
Add to your Claude Code MCP settings:
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"repo-memory": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "repo-memory"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Manual
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g repo-memory
|
|
32
|
+
repo-memory # starts MCP server on stdio
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Tools
|
|
36
|
+
|
|
37
|
+
| Tool | Description |
|
|
38
|
+
|------|-------------|
|
|
39
|
+
| `get_file_summary` | Cached file summary (exports, imports, purpose) |
|
|
40
|
+
| `get_changed_files` | Files changed since last check |
|
|
41
|
+
| `get_project_map` | Structural overview of project |
|
|
42
|
+
| `force_reread` | Force fresh summary generation |
|
|
43
|
+
| `invalidate` | Clear cache entries |
|
|
44
|
+
| `get_dependency_graph` | File dependency relationships |
|
|
45
|
+
| `create_task` | Create investigation task |
|
|
46
|
+
| `get_task_context` | Task state and explored files |
|
|
47
|
+
| `mark_explored` | Mark file as explored for task |
|
|
48
|
+
| `get_token_report` | Token usage telemetry report |
|
|
49
|
+
|
|
50
|
+
## How It Works
|
|
51
|
+
|
|
52
|
+
1. Files are hashed (SHA-256) on first access
|
|
53
|
+
2. Summaries extracted via regex analysis (exports, imports, purpose, declarations)
|
|
54
|
+
3. Cached in SQLite (`.repo-memory/cache.db` in your project)
|
|
55
|
+
4. Subsequent requests return cached data if hash unchanged
|
|
56
|
+
5. ~96% token reduction vs reading full files
|
|
57
|
+
|
|
58
|
+
## Architecture
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
MCP Server (stdio transport)
|
|
62
|
+
├── Cache Engine (hash, store, invalidation, ranking, GC)
|
|
63
|
+
├── Indexer Pipeline (scanner, summarizer, imports, diff-analyzer)
|
|
64
|
+
├── Dependency Graph (in-memory adjacency maps backed by SQLite)
|
|
65
|
+
├── Task Memory (CRUD, exploration tracking, frontier)
|
|
66
|
+
├── Telemetry (token tracking, sampling, export)
|
|
67
|
+
├── Session Manager (cross-turn persistence)
|
|
68
|
+
└── Persistence Layer (SQLite with WAL mode)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/blamechris/repo-memory.git
|
|
75
|
+
cd repo-memory
|
|
76
|
+
npm install
|
|
77
|
+
npm test # unit tests
|
|
78
|
+
npm run test:integration # integration tests
|
|
79
|
+
npm run typecheck # TypeScript check
|
|
80
|
+
npm run lint # ESLint
|
|
81
|
+
npm run build # compile
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
MIT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface GCOptions {
|
|
2
|
+
cacheMaxAgeDays?: number;
|
|
3
|
+
taskMaxAgeDays?: number;
|
|
4
|
+
telemetryMaxAgeDays?: number;
|
|
5
|
+
dryRun?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface GCResult {
|
|
8
|
+
removedCacheEntries: string[];
|
|
9
|
+
removedTasks: string[];
|
|
10
|
+
removedTelemetryCount: number;
|
|
11
|
+
removedOrphanImports: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function runGC(projectRoot: string, options?: GCOptions): Promise<GCResult>;
|
|
14
|
+
//# sourceMappingURL=gc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gc.d.ts","sourceRoot":"","sources":["../../src/cache/gc.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,QAAQ;IACvB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAID,wBAAsB,KAAK,CACzB,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,SAAS,GAClB,OAAO,CAAC,QAAQ,CAAC,CA4FnB"}
|
package/dist/cache/gc.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { getDatabase } from '../persistence/db.js';
|
|
2
|
+
import { CacheStore } from './store.js';
|
|
3
|
+
import { scanProject } from '../indexer/scanner.js';
|
|
4
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
5
|
+
export async function runGC(projectRoot, options) {
|
|
6
|
+
const cacheMaxAgeDays = options?.cacheMaxAgeDays ?? 30;
|
|
7
|
+
const taskMaxAgeDays = options?.taskMaxAgeDays ?? 30;
|
|
8
|
+
const telemetryMaxAgeDays = options?.telemetryMaxAgeDays ?? 90;
|
|
9
|
+
const dryRun = options?.dryRun ?? false;
|
|
10
|
+
const db = getDatabase(projectRoot);
|
|
11
|
+
const store = new CacheStore(projectRoot);
|
|
12
|
+
const result = {
|
|
13
|
+
removedCacheEntries: [],
|
|
14
|
+
removedTasks: [],
|
|
15
|
+
removedTelemetryCount: 0,
|
|
16
|
+
removedOrphanImports: 0,
|
|
17
|
+
};
|
|
18
|
+
// 1. Remove cache entries for deleted files
|
|
19
|
+
const currentFiles = new Set(await scanProject(projectRoot));
|
|
20
|
+
const allEntries = store.getAllEntries();
|
|
21
|
+
const deletedFilePaths = allEntries
|
|
22
|
+
.filter((entry) => !currentFiles.has(entry.path))
|
|
23
|
+
.map((entry) => entry.path);
|
|
24
|
+
result.removedCacheEntries.push(...deletedFilePaths);
|
|
25
|
+
// 2. Remove old cache entries (by lastChecked age)
|
|
26
|
+
const staleEntries = store.getStaleEntries(cacheMaxAgeDays * MS_PER_DAY);
|
|
27
|
+
const stalePaths = staleEntries
|
|
28
|
+
.filter((entry) => !result.removedCacheEntries.includes(entry.path))
|
|
29
|
+
.map((entry) => entry.path);
|
|
30
|
+
result.removedCacheEntries.push(...stalePaths);
|
|
31
|
+
if (!dryRun) {
|
|
32
|
+
for (const path of result.removedCacheEntries) {
|
|
33
|
+
store.deleteEntry(path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// 3. Remove old completed/archived tasks
|
|
37
|
+
const taskCutoff = Date.now() - taskMaxAgeDays * MS_PER_DAY;
|
|
38
|
+
const oldTasks = db
|
|
39
|
+
.prepare(`SELECT id FROM tasks
|
|
40
|
+
WHERE state IN ('completed', 'archived')
|
|
41
|
+
AND updated_at < ?`)
|
|
42
|
+
.all(taskCutoff);
|
|
43
|
+
result.removedTasks = oldTasks.map((t) => t.id);
|
|
44
|
+
if (!dryRun && result.removedTasks.length > 0) {
|
|
45
|
+
const deleteTaskFiles = db.prepare('DELETE FROM task_files WHERE task_id = ?');
|
|
46
|
+
const deleteTask = db.prepare('DELETE FROM tasks WHERE id = ?');
|
|
47
|
+
const deleteTasks = db.transaction(() => {
|
|
48
|
+
for (const id of result.removedTasks) {
|
|
49
|
+
deleteTaskFiles.run(id);
|
|
50
|
+
deleteTask.run(id);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
deleteTasks();
|
|
54
|
+
}
|
|
55
|
+
// 4. Remove old telemetry events
|
|
56
|
+
const telemetryCutoff = Date.now() - telemetryMaxAgeDays * MS_PER_DAY;
|
|
57
|
+
const telemetryCount = db
|
|
58
|
+
.prepare('SELECT COUNT(*) as count FROM telemetry WHERE timestamp < ?')
|
|
59
|
+
.get(telemetryCutoff);
|
|
60
|
+
result.removedTelemetryCount = telemetryCount.count;
|
|
61
|
+
if (!dryRun && result.removedTelemetryCount > 0) {
|
|
62
|
+
db.prepare('DELETE FROM telemetry WHERE timestamp < ?').run(telemetryCutoff);
|
|
63
|
+
}
|
|
64
|
+
// 5. Remove orphan import records (source not in files table)
|
|
65
|
+
const orphanCount = db
|
|
66
|
+
.prepare(`SELECT COUNT(*) as count FROM imports
|
|
67
|
+
WHERE source NOT IN (SELECT path FROM files)`)
|
|
68
|
+
.get();
|
|
69
|
+
result.removedOrphanImports = orphanCount.count;
|
|
70
|
+
if (!dryRun && result.removedOrphanImports > 0) {
|
|
71
|
+
db.prepare('DELETE FROM imports WHERE source NOT IN (SELECT path FROM files)').run();
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=gc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gc.js","sourceRoot":"","sources":["../../src/cache/gc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute a SHA-256 hex digest of a file at the given absolute path.
|
|
3
|
+
* Returns `null` if the file does not exist or cannot be read.
|
|
4
|
+
*/
|
|
5
|
+
export declare function hashFile(absolutePath: string): Promise<string | null>;
|
|
6
|
+
/**
|
|
7
|
+
* Compute a SHA-256 hex digest of the given string or Buffer.
|
|
8
|
+
*/
|
|
9
|
+
export declare function hashContents(contents: string | Buffer): string;
|
|
10
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/cache/hash.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAO3E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE9D"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
/**
|
|
4
|
+
* Compute a SHA-256 hex digest of a file at the given absolute path.
|
|
5
|
+
* Returns `null` if the file does not exist or cannot be read.
|
|
6
|
+
*/
|
|
7
|
+
export async function hashFile(absolutePath) {
|
|
8
|
+
try {
|
|
9
|
+
const contents = await readFile(absolutePath);
|
|
10
|
+
return hashContents(contents);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Compute a SHA-256 hex digest of the given string or Buffer.
|
|
18
|
+
*/
|
|
19
|
+
export function hashContents(contents) {
|
|
20
|
+
return createHash('sha256').update(contents).digest('hex');
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/cache/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,YAAoB;IACjD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;QAC9C,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,QAAyB;IACpD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { CacheStore } from './store.js';
|
|
2
|
+
export declare class CacheInvalidator {
|
|
3
|
+
private readonly projectRoot;
|
|
4
|
+
private readonly store;
|
|
5
|
+
constructor(projectRoot: string, store: CacheStore);
|
|
6
|
+
/**
|
|
7
|
+
* Hash the file at `absolutePath` and compare to the stored hash.
|
|
8
|
+
* Returns whether the cached entry is still valid and the current hash.
|
|
9
|
+
*/
|
|
10
|
+
validateEntry(absolutePath: string): Promise<{
|
|
11
|
+
valid: boolean;
|
|
12
|
+
currentHash: string | null;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* Delete the cache entry for the given relative path.
|
|
16
|
+
*/
|
|
17
|
+
invalidate(relativePath: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Delete all cache entries.
|
|
20
|
+
*/
|
|
21
|
+
invalidateAll(): void;
|
|
22
|
+
/**
|
|
23
|
+
* Given a list of relative paths, return those whose current hash
|
|
24
|
+
* differs from the cached hash. Includes new files (no cache entry)
|
|
25
|
+
* and deleted files (hash is null).
|
|
26
|
+
*/
|
|
27
|
+
findChangedFiles(trackedPaths: string[]): Promise<string[]>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=invalidation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invalidation.d.ts","sourceRoot":"","sources":["../../src/cache/invalidation.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;gBAEvB,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU;IAKlD;;;OAGG;IACG,aAAa,CACjB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAe1D;;OAEG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAItC;;OAEG;IACH,aAAa,IAAI,IAAI;IAOrB;;;;OAIG;IACG,gBAAgB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAelE"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { hashFile } from './hash.js';
|
|
3
|
+
export class CacheInvalidator {
|
|
4
|
+
projectRoot;
|
|
5
|
+
store;
|
|
6
|
+
constructor(projectRoot, store) {
|
|
7
|
+
this.projectRoot = projectRoot;
|
|
8
|
+
this.store = store;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Hash the file at `absolutePath` and compare to the stored hash.
|
|
12
|
+
* Returns whether the cached entry is still valid and the current hash.
|
|
13
|
+
*/
|
|
14
|
+
async validateEntry(absolutePath) {
|
|
15
|
+
const currentHash = await hashFile(absolutePath);
|
|
16
|
+
const relativePath = absolutePath.startsWith(this.projectRoot)
|
|
17
|
+
? absolutePath.slice(this.projectRoot.length).replace(/^\//, '')
|
|
18
|
+
: absolutePath;
|
|
19
|
+
const entry = this.store.getEntry(relativePath);
|
|
20
|
+
if (currentHash === null || entry === null) {
|
|
21
|
+
return { valid: false, currentHash };
|
|
22
|
+
}
|
|
23
|
+
return { valid: entry.hash === currentHash, currentHash };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Delete the cache entry for the given relative path.
|
|
27
|
+
*/
|
|
28
|
+
invalidate(relativePath) {
|
|
29
|
+
this.store.deleteEntry(relativePath);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Delete all cache entries.
|
|
33
|
+
*/
|
|
34
|
+
invalidateAll() {
|
|
35
|
+
const entries = this.store.getAllEntries();
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
this.store.deleteEntry(entry.path);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Given a list of relative paths, return those whose current hash
|
|
42
|
+
* differs from the cached hash. Includes new files (no cache entry)
|
|
43
|
+
* and deleted files (hash is null).
|
|
44
|
+
*/
|
|
45
|
+
async findChangedFiles(trackedPaths) {
|
|
46
|
+
const changed = [];
|
|
47
|
+
for (const relativePath of trackedPaths) {
|
|
48
|
+
const absolutePath = join(this.projectRoot, relativePath);
|
|
49
|
+
const currentHash = await hashFile(absolutePath);
|
|
50
|
+
const entry = this.store.getEntry(relativePath);
|
|
51
|
+
if (entry === null || currentHash === null || entry.hash !== currentHash) {
|
|
52
|
+
changed.push(relativePath);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return changed;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=invalidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invalidation.js","sourceRoot":"","sources":["../../src/cache/invalidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAGrC,MAAM,OAAO,gBAAgB;IACV,WAAW,CAAS;IACpB,KAAK,CAAa;IAEnC,YAAY,WAAmB,EAAE,KAAiB;QAChD,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,aAAa,CACjB,YAAoB;QAEpB,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,YAAY,GAAG,YAAY,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC;YAC5D,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;YAChE,CAAC,CAAC,YAAY,CAAC;QAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;QAEhD,IAAI,WAAW,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YAC3C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QACvC,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,WAAW,EAAE,CAAC;IAC5D,CAAC;IAED;;OAEG;IACH,UAAU,CAAC,YAAoB;QAC7B,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACH,aAAa;QACX,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;QAC3C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,gBAAgB,CAAC,YAAsB;QAC3C,MAAM,OAAO,GAAa,EAAE,CAAC;QAE7B,KAAK,MAAM,YAAY,IAAI,YAAY,EAAE,CAAC;YACxC,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;YAC1D,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC;YACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;YAEhD,IAAI,KAAK,KAAK,IAAI,IAAI,WAAW,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACzE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { DependencyGraph } from '../graph/dependency-graph.js';
|
|
2
|
+
import type { CacheStore } from './store.js';
|
|
3
|
+
export interface RankingSignals {
|
|
4
|
+
dependencyProximity: number;
|
|
5
|
+
recency: number;
|
|
6
|
+
fileTypeRelevance: number;
|
|
7
|
+
taskContextRelevance: number;
|
|
8
|
+
changeFrequency: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RankingWeights {
|
|
11
|
+
dependencyProximity: number;
|
|
12
|
+
recency: number;
|
|
13
|
+
fileTypeRelevance: number;
|
|
14
|
+
taskContextRelevance: number;
|
|
15
|
+
changeFrequency: number;
|
|
16
|
+
}
|
|
17
|
+
export interface RankedFile {
|
|
18
|
+
path: string;
|
|
19
|
+
score: number;
|
|
20
|
+
signals: RankingSignals;
|
|
21
|
+
}
|
|
22
|
+
export interface RankingOptions {
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
weights?: Partial<RankingWeights>;
|
|
25
|
+
exploredFiles?: string[];
|
|
26
|
+
flaggedFiles?: string[];
|
|
27
|
+
graph?: DependencyGraph;
|
|
28
|
+
cacheStore?: CacheStore;
|
|
29
|
+
limit?: number;
|
|
30
|
+
}
|
|
31
|
+
export declare const DEFAULT_WEIGHTS: RankingWeights;
|
|
32
|
+
/** Score file type relevance: source=1.0, config=0.5, test=0.3, other=0.2 */
|
|
33
|
+
export declare function computeFileTypeRelevance(filePath: string): number;
|
|
34
|
+
/**
|
|
35
|
+
* Exponential decay based on how recently the file was checked.
|
|
36
|
+
* Files checked in the last hour get ~1.0, files checked days ago get ~0.0.
|
|
37
|
+
* Uses a half-life of 4 hours.
|
|
38
|
+
*/
|
|
39
|
+
export declare function computeRecency(lastChecked: number, now?: number): number;
|
|
40
|
+
/**
|
|
41
|
+
* Score based on distance in the dependency graph from explored files.
|
|
42
|
+
* 1.0 if directly connected, 0.5 if 2 hops away, decaying by 1/2^(distance-1).
|
|
43
|
+
* Returns 0 if no graph or no explored files.
|
|
44
|
+
*/
|
|
45
|
+
export declare function computeDependencyProximity(filePath: string, exploredFiles: string[], graph: DependencyGraph): number;
|
|
46
|
+
/**
|
|
47
|
+
* Score based on directory proximity to flagged and explored files.
|
|
48
|
+
* 1.0 if in same directory as flagged files, 0.7 if in same directory as explored files,
|
|
49
|
+
* 0.4 if in sibling directory of flagged/explored, 0.1 otherwise.
|
|
50
|
+
*/
|
|
51
|
+
export declare function computeTaskContextRelevance(filePath: string, exploredFiles: string[], flaggedFiles: string[]): number;
|
|
52
|
+
/**
|
|
53
|
+
* Composite ranking: scores each file on all signals, combines with weights,
|
|
54
|
+
* returns sorted results descending by score.
|
|
55
|
+
*/
|
|
56
|
+
export declare function rankFiles(files: string[], options: RankingOptions): RankedFile[];
|
|
57
|
+
//# sourceMappingURL=ranking.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ranking.d.ts","sourceRoot":"","sources":["../../src/cache/ranking.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,eAAe,EAAE,cAM7B,CAAC;AAiCF,6EAA6E;AAC7E,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAoBjE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAQxE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EAAE,EACvB,KAAK,EAAE,eAAe,GACrB,MAAM,CA0CR;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EAAE,EACvB,YAAY,EAAE,MAAM,EAAE,GACrB,MAAM,CA+BR;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,cAAc,GAAG,UAAU,EAAE,CA4DhF"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { dirname } from 'path';
|
|
2
|
+
export const DEFAULT_WEIGHTS = {
|
|
3
|
+
dependencyProximity: 0.3,
|
|
4
|
+
recency: 0.2,
|
|
5
|
+
fileTypeRelevance: 0.15,
|
|
6
|
+
taskContextRelevance: 0.25,
|
|
7
|
+
changeFrequency: 0.1,
|
|
8
|
+
};
|
|
9
|
+
const SOURCE_EXTENSIONS = new Set(['.ts', '.js', '.tsx', '.jsx']);
|
|
10
|
+
const CONFIG_BASENAMES = new Set([
|
|
11
|
+
'package.json',
|
|
12
|
+
'tsconfig.json',
|
|
13
|
+
'tsconfig.base.json',
|
|
14
|
+
'.eslintrc',
|
|
15
|
+
'.eslintrc.json',
|
|
16
|
+
'.eslintrc.js',
|
|
17
|
+
'.prettierrc',
|
|
18
|
+
'.prettierrc.json',
|
|
19
|
+
'jest.config.js',
|
|
20
|
+
'jest.config.ts',
|
|
21
|
+
'vitest.config.ts',
|
|
22
|
+
'vitest.config.js',
|
|
23
|
+
'webpack.config.js',
|
|
24
|
+
'webpack.config.ts',
|
|
25
|
+
'rollup.config.js',
|
|
26
|
+
'rollup.config.ts',
|
|
27
|
+
'vite.config.ts',
|
|
28
|
+
'vite.config.js',
|
|
29
|
+
'eslint.config.mjs',
|
|
30
|
+
'eslint.config.js',
|
|
31
|
+
]);
|
|
32
|
+
const TEST_PATTERNS = [
|
|
33
|
+
/\.test\.[jt]sx?$/,
|
|
34
|
+
/\.spec\.[jt]sx?$/,
|
|
35
|
+
/\/__tests__\//,
|
|
36
|
+
/\/tests?\//,
|
|
37
|
+
];
|
|
38
|
+
/** Score file type relevance: source=1.0, config=0.5, test=0.3, other=0.2 */
|
|
39
|
+
export function computeFileTypeRelevance(filePath) {
|
|
40
|
+
const basename = filePath.split('/').pop() ?? filePath;
|
|
41
|
+
const ext = getExtension(basename);
|
|
42
|
+
// Check test patterns first (test files may also be .ts/.js)
|
|
43
|
+
if (TEST_PATTERNS.some((p) => p.test(filePath))) {
|
|
44
|
+
return 0.3;
|
|
45
|
+
}
|
|
46
|
+
// Config files
|
|
47
|
+
if (CONFIG_BASENAMES.has(basename)) {
|
|
48
|
+
return 0.5;
|
|
49
|
+
}
|
|
50
|
+
// Source files
|
|
51
|
+
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
52
|
+
return 1.0;
|
|
53
|
+
}
|
|
54
|
+
return 0.2;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Exponential decay based on how recently the file was checked.
|
|
58
|
+
* Files checked in the last hour get ~1.0, files checked days ago get ~0.0.
|
|
59
|
+
* Uses a half-life of 4 hours.
|
|
60
|
+
*/
|
|
61
|
+
export function computeRecency(lastChecked, now) {
|
|
62
|
+
const currentTime = now ?? Date.now();
|
|
63
|
+
const ageMs = currentTime - lastChecked;
|
|
64
|
+
if (ageMs <= 0)
|
|
65
|
+
return 1.0;
|
|
66
|
+
const HALF_LIFE_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
67
|
+
return Math.pow(2, -ageMs / HALF_LIFE_MS);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Score based on distance in the dependency graph from explored files.
|
|
71
|
+
* 1.0 if directly connected, 0.5 if 2 hops away, decaying by 1/2^(distance-1).
|
|
72
|
+
* Returns 0 if no graph or no explored files.
|
|
73
|
+
*/
|
|
74
|
+
export function computeDependencyProximity(filePath, exploredFiles, graph) {
|
|
75
|
+
if (exploredFiles.length === 0)
|
|
76
|
+
return 0;
|
|
77
|
+
let bestScore = 0;
|
|
78
|
+
for (const explored of exploredFiles) {
|
|
79
|
+
// Check direct connection (1 hop)
|
|
80
|
+
const deps = graph.getDependencies(explored);
|
|
81
|
+
const dependents = graph.getDependents(explored);
|
|
82
|
+
const directNeighbors = new Set([...deps, ...dependents]);
|
|
83
|
+
if (directNeighbors.has(filePath)) {
|
|
84
|
+
return 1.0; // Can't get better than this
|
|
85
|
+
}
|
|
86
|
+
// Check 2 hops
|
|
87
|
+
for (const neighbor of directNeighbors) {
|
|
88
|
+
const neighborDeps = graph.getDependencies(neighbor);
|
|
89
|
+
const neighborDependents = graph.getDependents(neighbor);
|
|
90
|
+
const secondHop = new Set([...neighborDeps, ...neighborDependents]);
|
|
91
|
+
if (secondHop.has(filePath)) {
|
|
92
|
+
bestScore = Math.max(bestScore, 0.5);
|
|
93
|
+
break; // Found at 2 hops from this explored file
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (bestScore >= 0.5) {
|
|
97
|
+
// Check 3 hops via transitive
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
// Check 3 hops
|
|
101
|
+
const transitiveDeps = new Set(graph.getTransitiveDependencies(explored, 3));
|
|
102
|
+
const transitiveDependents = new Set(graph.getTransitiveDependents(explored, 3));
|
|
103
|
+
if (transitiveDeps.has(filePath) || transitiveDependents.has(filePath)) {
|
|
104
|
+
bestScore = Math.max(bestScore, 0.25);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return bestScore;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Score based on directory proximity to flagged and explored files.
|
|
111
|
+
* 1.0 if in same directory as flagged files, 0.7 if in same directory as explored files,
|
|
112
|
+
* 0.4 if in sibling directory of flagged/explored, 0.1 otherwise.
|
|
113
|
+
*/
|
|
114
|
+
export function computeTaskContextRelevance(filePath, exploredFiles, flaggedFiles) {
|
|
115
|
+
const fileDir = dirname(filePath);
|
|
116
|
+
// Check flagged files first (higher priority)
|
|
117
|
+
for (const flagged of flaggedFiles) {
|
|
118
|
+
if (dirname(flagged) === fileDir) {
|
|
119
|
+
return 1.0;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Check explored files
|
|
123
|
+
for (const explored of exploredFiles) {
|
|
124
|
+
if (dirname(explored) === fileDir) {
|
|
125
|
+
return 0.7;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Check sibling directories (parent directory matches)
|
|
129
|
+
const parentDir = dirname(fileDir);
|
|
130
|
+
for (const flagged of flaggedFiles) {
|
|
131
|
+
if (dirname(dirname(flagged)) === parentDir) {
|
|
132
|
+
return 0.4;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
for (const explored of exploredFiles) {
|
|
136
|
+
if (dirname(dirname(explored)) === parentDir) {
|
|
137
|
+
return 0.4;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return 0.1;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Composite ranking: scores each file on all signals, combines with weights,
|
|
144
|
+
* returns sorted results descending by score.
|
|
145
|
+
*/
|
|
146
|
+
export function rankFiles(files, options) {
|
|
147
|
+
const weights = {
|
|
148
|
+
...DEFAULT_WEIGHTS,
|
|
149
|
+
...options.weights,
|
|
150
|
+
};
|
|
151
|
+
const exploredFiles = options.exploredFiles ?? [];
|
|
152
|
+
const flaggedFiles = options.flaggedFiles ?? [];
|
|
153
|
+
const now = Date.now();
|
|
154
|
+
const results = files.map((filePath) => {
|
|
155
|
+
const fileTypeRelevance = computeFileTypeRelevance(filePath);
|
|
156
|
+
let recency = 0.5; // default if no cache store
|
|
157
|
+
if (options.cacheStore) {
|
|
158
|
+
const entry = options.cacheStore.getEntry(filePath);
|
|
159
|
+
if (entry) {
|
|
160
|
+
recency = computeRecency(entry.lastChecked, now);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
let dependencyProximity = 0;
|
|
164
|
+
if (options.graph) {
|
|
165
|
+
dependencyProximity = computeDependencyProximity(filePath, exploredFiles, options.graph);
|
|
166
|
+
}
|
|
167
|
+
const taskContextRelevance = computeTaskContextRelevance(filePath, exploredFiles, flaggedFiles);
|
|
168
|
+
// changeFrequency is a placeholder — would need git log data to compute properly
|
|
169
|
+
const changeFrequency = 0.5;
|
|
170
|
+
const signals = {
|
|
171
|
+
dependencyProximity,
|
|
172
|
+
recency,
|
|
173
|
+
fileTypeRelevance,
|
|
174
|
+
taskContextRelevance,
|
|
175
|
+
changeFrequency,
|
|
176
|
+
};
|
|
177
|
+
const score = signals.dependencyProximity * weights.dependencyProximity +
|
|
178
|
+
signals.recency * weights.recency +
|
|
179
|
+
signals.fileTypeRelevance * weights.fileTypeRelevance +
|
|
180
|
+
signals.taskContextRelevance * weights.taskContextRelevance +
|
|
181
|
+
signals.changeFrequency * weights.changeFrequency;
|
|
182
|
+
return { path: filePath, score, signals };
|
|
183
|
+
});
|
|
184
|
+
results.sort((a, b) => b.score - a.score);
|
|
185
|
+
if (options.limit !== undefined && options.limit > 0) {
|
|
186
|
+
return results.slice(0, options.limit);
|
|
187
|
+
}
|
|
188
|
+
return results;
|
|
189
|
+
}
|
|
190
|
+
function getExtension(filename) {
|
|
191
|
+
const dotIndex = filename.lastIndexOf('.');
|
|
192
|
+
if (dotIndex === -1)
|
|
193
|
+
return '';
|
|
194
|
+
return filename.slice(dotIndex);
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=ranking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ranking.js","sourceRoot":"","sources":["../../src/cache/ranking.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CacheEntry, FileSummary } from '../types.js';
|
|
2
|
+
export declare class CacheStore {
|
|
3
|
+
private readonly projectRoot;
|
|
4
|
+
constructor(projectRoot: string);
|
|
5
|
+
getEntry(path: string): CacheEntry | null;
|
|
6
|
+
setEntry(path: string, hash: string, summary: FileSummary | null): void;
|
|
7
|
+
getAllEntries(): CacheEntry[];
|
|
8
|
+
deleteEntry(path: string): void;
|
|
9
|
+
setEntries(entries: Array<{
|
|
10
|
+
path: string;
|
|
11
|
+
hash: string;
|
|
12
|
+
summary?: FileSummary | null;
|
|
13
|
+
}>): void;
|
|
14
|
+
getStaleEntries(maxAge: number): CacheEntry[];
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/cache/store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAkB3D,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;gBAEzB,WAAW,EAAE,MAAM;IAI/B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IASzC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI,GAAG,IAAI;IAevE,aAAa,IAAI,UAAU,EAAE;IAS7B,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/B,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,CAAA;KAAE,CAAC,GAAG,IAAI;IAsB9F,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE;CAW9C"}
|