seer-mcp 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/.vscode/settings.json +3 -0
- package/LICENSE +176 -0
- package/README.md +272 -0
- package/README_dev.md +199 -0
- package/dist/bundle/ci.d.ts +47 -0
- package/dist/bundle/ci.d.ts.map +1 -0
- package/dist/bundle/ci.js +113 -0
- package/dist/bundle/ci.js.map +1 -0
- package/dist/bundle/contract.d.ts +111 -0
- package/dist/bundle/contract.d.ts.map +1 -0
- package/dist/bundle/contract.js +352 -0
- package/dist/bundle/contract.js.map +1 -0
- package/dist/bundle/export.d.ts +36 -0
- package/dist/bundle/export.d.ts.map +1 -0
- package/dist/bundle/export.js +152 -0
- package/dist/bundle/export.js.map +1 -0
- package/dist/bundle/external.d.ts +66 -0
- package/dist/bundle/external.d.ts.map +1 -0
- package/dist/bundle/external.js +238 -0
- package/dist/bundle/external.js.map +1 -0
- package/dist/bundle/format.d.ts +94 -0
- package/dist/bundle/format.d.ts.map +1 -0
- package/dist/bundle/format.js +42 -0
- package/dist/bundle/format.js.map +1 -0
- package/dist/bundle/import.d.ts +49 -0
- package/dist/bundle/import.d.ts.map +1 -0
- package/dist/bundle/import.js +116 -0
- package/dist/bundle/import.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1402 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/init.d.ts +48 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +284 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/db/schema.d.ts +3 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +616 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/store.d.ts +1011 -0
- package/dist/db/store.d.ts.map +1 -0
- package/dist/db/store.js +3888 -0
- package/dist/db/store.js.map +1 -0
- package/dist/graph/pagerank.d.ts +9 -0
- package/dist/graph/pagerank.d.ts.map +1 -0
- package/dist/graph/pagerank.js +47 -0
- package/dist/graph/pagerank.js.map +1 -0
- package/dist/indexer/architecture.d.ts +72 -0
- package/dist/indexer/architecture.d.ts.map +1 -0
- package/dist/indexer/architecture.js +112 -0
- package/dist/indexer/architecture.js.map +1 -0
- package/dist/indexer/behavior.d.ts +75 -0
- package/dist/indexer/behavior.d.ts.map +1 -0
- package/dist/indexer/behavior.js +395 -0
- package/dist/indexer/behavior.js.map +1 -0
- package/dist/indexer/boundaries.d.ts +60 -0
- package/dist/indexer/boundaries.d.ts.map +1 -0
- package/dist/indexer/boundaries.js +366 -0
- package/dist/indexer/boundaries.js.map +1 -0
- package/dist/indexer/churn.d.ts +15 -0
- package/dist/indexer/churn.d.ts.map +1 -0
- package/dist/indexer/churn.js +49 -0
- package/dist/indexer/churn.js.map +1 -0
- package/dist/indexer/classify.d.ts +9 -0
- package/dist/indexer/classify.d.ts.map +1 -0
- package/dist/indexer/classify.js +90 -0
- package/dist/indexer/classify.js.map +1 -0
- package/dist/indexer/context.d.ts +176 -0
- package/dist/indexer/context.d.ts.map +1 -0
- package/dist/indexer/context.js +193 -0
- package/dist/indexer/context.js.map +1 -0
- package/dist/indexer/continuity.d.ts +67 -0
- package/dist/indexer/continuity.d.ts.map +1 -0
- package/dist/indexer/continuity.js +288 -0
- package/dist/indexer/continuity.js.map +1 -0
- package/dist/indexer/detectchanges.d.ts +32 -0
- package/dist/indexer/detectchanges.d.ts.map +1 -0
- package/dist/indexer/detectchanges.js +74 -0
- package/dist/indexer/detectchanges.js.map +1 -0
- package/dist/indexer/discovery.d.ts +37 -0
- package/dist/indexer/discovery.d.ts.map +1 -0
- package/dist/indexer/discovery.js +136 -0
- package/dist/indexer/discovery.js.map +1 -0
- package/dist/indexer/externaldeps.d.ts +18 -0
- package/dist/indexer/externaldeps.d.ts.map +1 -0
- package/dist/indexer/externaldeps.js +288 -0
- package/dist/indexer/externaldeps.js.map +1 -0
- package/dist/indexer/freshness.d.ts +48 -0
- package/dist/indexer/freshness.d.ts.map +1 -0
- package/dist/indexer/freshness.js +128 -0
- package/dist/indexer/freshness.js.map +1 -0
- package/dist/indexer/git.d.ts +144 -0
- package/dist/indexer/git.d.ts.map +1 -0
- package/dist/indexer/git.js +444 -0
- package/dist/indexer/git.js.map +1 -0
- package/dist/indexer/index.d.ts +145 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +930 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/modules.d.ts +62 -0
- package/dist/indexer/modules.d.ts.map +1 -0
- package/dist/indexer/modules.js +293 -0
- package/dist/indexer/modules.js.map +1 -0
- package/dist/indexer/preflight.d.ts +154 -0
- package/dist/indexer/preflight.d.ts.map +1 -0
- package/dist/indexer/preflight.js +399 -0
- package/dist/indexer/preflight.js.map +1 -0
- package/dist/indexer/protoScanner.d.ts +34 -0
- package/dist/indexer/protoScanner.d.ts.map +1 -0
- package/dist/indexer/protoScanner.js +133 -0
- package/dist/indexer/protoScanner.js.map +1 -0
- package/dist/indexer/risk.d.ts +115 -0
- package/dist/indexer/risk.d.ts.map +1 -0
- package/dist/indexer/risk.js +194 -0
- package/dist/indexer/risk.js.map +1 -0
- package/dist/indexer/serviceHostScanner.d.ts +25 -0
- package/dist/indexer/serviceHostScanner.d.ts.map +1 -0
- package/dist/indexer/serviceHostScanner.js +95 -0
- package/dist/indexer/serviceHostScanner.js.map +1 -0
- package/dist/indexer/serviceLinks.d.ts +105 -0
- package/dist/indexer/serviceLinks.d.ts.map +1 -0
- package/dist/indexer/serviceLinks.js +509 -0
- package/dist/indexer/serviceLinks.js.map +1 -0
- package/dist/indexer/shapehash.d.ts +98 -0
- package/dist/indexer/shapehash.d.ts.map +1 -0
- package/dist/indexer/shapehash.js +354 -0
- package/dist/indexer/shapehash.js.map +1 -0
- package/dist/indexer/skeleton.d.ts +15 -0
- package/dist/indexer/skeleton.d.ts.map +1 -0
- package/dist/indexer/skeleton.js +136 -0
- package/dist/indexer/skeleton.js.map +1 -0
- package/dist/indexer/symbolhistory.d.ts +41 -0
- package/dist/indexer/symbolhistory.d.ts.map +1 -0
- package/dist/indexer/symbolhistory.js +124 -0
- package/dist/indexer/symbolhistory.js.map +1 -0
- package/dist/indexer/watcher.d.ts +68 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +179 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/mcp/server.d.ts +80 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +1610 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/parser/index.d.ts +8 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +33 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/languages/cpp.d.ts +3 -0
- package/dist/parser/languages/cpp.d.ts.map +1 -0
- package/dist/parser/languages/cpp.js +350 -0
- package/dist/parser/languages/cpp.js.map +1 -0
- package/dist/parser/languages/csharp.d.ts +3 -0
- package/dist/parser/languages/csharp.d.ts.map +1 -0
- package/dist/parser/languages/csharp.js +239 -0
- package/dist/parser/languages/csharp.js.map +1 -0
- package/dist/parser/languages/go.d.ts +3 -0
- package/dist/parser/languages/go.d.ts.map +1 -0
- package/dist/parser/languages/go.js +259 -0
- package/dist/parser/languages/go.js.map +1 -0
- package/dist/parser/languages/java.d.ts +3 -0
- package/dist/parser/languages/java.d.ts.map +1 -0
- package/dist/parser/languages/java.js +391 -0
- package/dist/parser/languages/java.js.map +1 -0
- package/dist/parser/languages/python.d.ts +3 -0
- package/dist/parser/languages/python.d.ts.map +1 -0
- package/dist/parser/languages/python.js +396 -0
- package/dist/parser/languages/python.js.map +1 -0
- package/dist/parser/languages/rust.d.ts +3 -0
- package/dist/parser/languages/rust.d.ts.map +1 -0
- package/dist/parser/languages/rust.js +159 -0
- package/dist/parser/languages/rust.js.map +1 -0
- package/dist/parser/languages/typescript.d.ts +3 -0
- package/dist/parser/languages/typescript.d.ts.map +1 -0
- package/dist/parser/languages/typescript.js +1442 -0
- package/dist/parser/languages/typescript.js.map +1 -0
- package/dist/parser/parserContext.d.ts +77 -0
- package/dist/parser/parserContext.d.ts.map +1 -0
- package/dist/parser/parserContext.js +354 -0
- package/dist/parser/parserContext.js.map +1 -0
- package/dist/parser/walker.d.ts +81 -0
- package/dist/parser/walker.d.ts.map +1 -0
- package/dist/parser/walker.js +217 -0
- package/dist/parser/walker.js.map +1 -0
- package/dist/parser/worker.d.ts +66 -0
- package/dist/parser/worker.d.ts.map +1 -0
- package/dist/parser/worker.js +129 -0
- package/dist/parser/worker.js.map +1 -0
- package/dist/parser/workerpool.d.ts +107 -0
- package/dist/parser/workerpool.d.ts.map +1 -0
- package/dist/parser/workerpool.js +383 -0
- package/dist/parser/workerpool.js.map +1 -0
- package/dist/scip/format.d.ts +87 -0
- package/dist/scip/format.d.ts.map +1 -0
- package/dist/scip/format.js +31 -0
- package/dist/scip/format.js.map +1 -0
- package/dist/scip/import.d.ts +37 -0
- package/dist/scip/import.d.ts.map +1 -0
- package/dist/scip/import.js +180 -0
- package/dist/scip/import.js.map +1 -0
- package/dist/types.d.ts +392 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/docs/architecture.md +105 -0
- package/docs/benchmarks/methodology.md +134 -0
- package/docs/benchmarks/raw-results.md +71 -0
- package/docs/benchmarks.md +74 -0
- package/docs/cli.md +148 -0
- package/docs/examples/behavior-tests.md +70 -0
- package/docs/examples/change-history.md +85 -0
- package/docs/examples/pre-edit-context.md +81 -0
- package/docs/examples/service-links.md +88 -0
- package/docs/examples.md +80 -0
- package/docs/faq.md +70 -0
- package/docs/internals.md +104 -0
- package/docs/languages.md +70 -0
- package/docs/limits.md +52 -0
- package/docs/mcp.md +199 -0
- package/docs/quickstart.md +119 -0
- package/docs/testing.md +123 -0
- package/docs/tools.md +115 -0
- package/package.json +52 -0
- package/research-codebase.md +578 -0
- package/seer-cli-docs.md +326 -0
- package/seer-master-guide.md +246 -0
- package/src/bundle/ci.ts +141 -0
- package/src/bundle/contract.ts +387 -0
- package/src/bundle/export.ts +175 -0
- package/src/bundle/external.ts +285 -0
- package/src/bundle/format.ts +92 -0
- package/src/bundle/import.ts +157 -0
- package/src/cli/index.ts +1249 -0
- package/src/cli/init.ts +389 -0
- package/src/db/schema.ts +614 -0
- package/src/db/store.ts +4306 -0
- package/src/graph/pagerank.ts +53 -0
- package/src/indexer/architecture.ts +148 -0
- package/src/indexer/behavior.ts +466 -0
- package/src/indexer/boundaries.ts +374 -0
- package/src/indexer/churn.ts +58 -0
- package/src/indexer/classify.ts +96 -0
- package/src/indexer/context.ts +340 -0
- package/src/indexer/continuity.ts +322 -0
- package/src/indexer/detectchanges.ts +94 -0
- package/src/indexer/discovery.ts +176 -0
- package/src/indexer/externaldeps.ts +243 -0
- package/src/indexer/freshness.ts +166 -0
- package/src/indexer/git.ts +453 -0
- package/src/indexer/index.ts +1092 -0
- package/src/indexer/modules.ts +358 -0
- package/src/indexer/preflight.ts +548 -0
- package/src/indexer/protoScanner.ts +147 -0
- package/src/indexer/risk.ts +304 -0
- package/src/indexer/serviceHostScanner.ts +92 -0
- package/src/indexer/serviceLinks.ts +543 -0
- package/src/indexer/shapehash.ts +370 -0
- package/src/indexer/skeleton.ts +169 -0
- package/src/indexer/symbolhistory.ts +172 -0
- package/src/indexer/watcher.ts +206 -0
- package/src/mcp/server.ts +1659 -0
- package/src/parser/index.ts +37 -0
- package/src/parser/languages/cpp.ts +361 -0
- package/src/parser/languages/csharp.ts +235 -0
- package/src/parser/languages/go.ts +259 -0
- package/src/parser/languages/java.ts +382 -0
- package/src/parser/languages/python.ts +370 -0
- package/src/parser/languages/rust.ts +164 -0
- package/src/parser/languages/typescript.ts +1435 -0
- package/src/parser/parserContext.ts +392 -0
- package/src/parser/walker.ts +306 -0
- package/src/parser/worker.ts +181 -0
- package/src/parser/workerpool.ts +448 -0
- package/src/scip/format.ts +83 -0
- package/src/scip/import.ts +216 -0
- package/src/types.ts +457 -0
- package/tests/benchmark-service-links.ts +244 -0
- package/tests/bug-regressions.ts +626 -0
- package/tests/filters.ts +264 -0
- package/tests/fixtures/Counter.tsx +38 -0
- package/tests/fixtures/caller.ts +7 -0
- package/tests/fixtures/collisions.ts +23 -0
- package/tests/fixtures/local_helper.ts +5 -0
- package/tests/fixtures/overloads.java +17 -0
- package/tests/fixtures/remote_helper.ts +4 -0
- package/tests/fixtures/sample.c +15 -0
- package/tests/fixtures/sample.cpp +47 -0
- package/tests/fixtures/sample.cs +62 -0
- package/tests/fixtures/sample.go +68 -0
- package/tests/fixtures/sample.h +30 -0
- package/tests/fixtures/sample.java +85 -0
- package/tests/fixtures/sample.py +46 -0
- package/tests/fixtures/sample.rs +78 -0
- package/tests/fixtures/sample.ts +76 -0
- package/tests/fixtures-service/HttpClients.cs +30 -0
- package/tests/fixtures-service/HttpClients.java +24 -0
- package/tests/fixtures-service/billing.ts +15 -0
- package/tests/fixtures-service/docker-compose.yml +15 -0
- package/tests/fixtures-service/gateway.ts +10 -0
- package/tests/fixtures-service/get_user.ts +11 -0
- package/tests/fixtures-service/graphql_client.ts +63 -0
- package/tests/fixtures-service/graphql_server.ts +30 -0
- package/tests/fixtures-service/grpc_client.go +30 -0
- package/tests/fixtures-service/http_clients.go +23 -0
- package/tests/fixtures-service/http_clients.py +38 -0
- package/tests/fixtures-service/http_clients.ts +49 -0
- package/tests/fixtures-service/k8s/payment-service.yaml +22 -0
- package/tests/fixtures-service/k8s_calls.ts +20 -0
- package/tests/fixtures-service/messaging.ts +87 -0
- package/tests/fixtures-service/trpc_client.ts +39 -0
- package/tests/fixtures-service/trpc_server.ts +39 -0
- package/tests/fixtures-service/user_service.proto +33 -0
- package/tests/fixtures-trackcd/Cargo.toml +11 -0
- package/tests/fixtures-trackcd/SpringController.java +36 -0
- package/tests/fixtures-trackcd/auth_service.ts +19 -0
- package/tests/fixtures-trackcd/complex_module.py +50 -0
- package/tests/fixtures-trackcd/express_app.js +30 -0
- package/tests/fixtures-trackcd/fastapi_app.py +49 -0
- package/tests/fixtures-trackcd/fastify_object_routes.js +32 -0
- package/tests/fixtures-trackcd/go.mod +8 -0
- package/tests/fixtures-trackcd/package.json +15 -0
- package/tests/fixtures-trackcd/requirements.txt +4 -0
- package/tests/fixtures-trackcd/tests/auth_service.test.ts +13 -0
- package/tests/fixtures-tracke/auth/AuthService.ts +23 -0
- package/tests/fixtures-tracke/auth/crypto.ts +7 -0
- package/tests/fixtures-tracke/billing/Billing.ts +20 -0
- package/tests/fixtures-tracke/billing/Invoice.ts +10 -0
- package/tests/fixtures-tracke/billing/server.ts +17 -0
- package/tests/fixtures-tracke/package.json +7 -0
- package/tests/fixtures-tracke/tests/auth.test.ts +23 -0
- package/tests/fixtures-tracke/tests/billing.test.ts +14 -0
- package/tests/fixtures-trackf/package.json +5 -0
- package/tests/fixtures-trackf/src/auth.ts +26 -0
- package/tests/fixtures-trackf/src/handlers.ts +35 -0
- package/tests/fixtures-tracki/billing/routes.ts +12 -0
- package/tests/fixtures-tracki/gateway/client.ts +13 -0
- package/tests/git-features.ts +267 -0
- package/tests/init.ts +141 -0
- package/tests/mcp-jit.ts +130 -0
- package/tests/mcp-smoke.ts +191 -0
- package/tests/mcp-trackcd.ts +169 -0
- package/tests/mcp-tracke.ts +229 -0
- package/tests/mcp-trackf.ts +330 -0
- package/tests/mcp-trackg.ts +219 -0
- package/tests/mcp-tracki.ts +174 -0
- package/tests/mcp-watcher.ts +126 -0
- package/tests/optspec.ts +194 -0
- package/tests/parallel-index.ts +333 -0
- package/tests/parallel-read.ts +125 -0
- package/tests/parallel-recovery.ts +241 -0
- package/tests/perf-callers.ts +145 -0
- package/tests/query-parity.ts +184 -0
- package/tests/query-perf.ts +55 -0
- package/tests/scale-parallel-parity.ts +225 -0
- package/tests/scale-test.ts +523 -0
- package/tests/smoke.ts +396 -0
- package/tests/trackcd.ts +325 -0
- package/tests/tracke-collisions.ts +255 -0
- package/tests/tracke.ts +314 -0
- package/tests/trackf-bugs.ts +406 -0
- package/tests/trackf.ts +390 -0
- package/tests/trackg.ts +1372 -0
- package/tests/tracki-boundaries.ts +202 -0
- package/tests/tracki-continuity.ts +253 -0
- package/tests/tracki-contract-diff.ts +249 -0
- package/tests/tracki-external-bundles.ts +341 -0
- package/tests/tracki-preflight.ts +251 -0
- package/tests/verify-roles.ts +51 -0
- package/tests/worker-parity.ts +286 -0
- package/tests/worker-pool.ts +262 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Store } from '../db/store.js';
|
|
2
|
+
import {
|
|
3
|
+
commitsForFile, fileDiffInfo, isGitRepo, gitHeadSha, gitRemoteUrl,
|
|
4
|
+
extractPrNumber, githubPrUrl,
|
|
5
|
+
} from './git.js';
|
|
6
|
+
import { buildContinuity } from './continuity.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Symbol-history pass — for every indexed function/method/constructor/class
|
|
10
|
+
* symbol, walk the commits that touched its file and record the ones whose
|
|
11
|
+
* diff hunks overlap the symbol's current line span.
|
|
12
|
+
*
|
|
13
|
+
* Limitations (documented honestly):
|
|
14
|
+
* - Renames within a file: we do NOT reparse historical file versions, so
|
|
15
|
+
* a function rename will appear as "history starts here" at the rename
|
|
16
|
+
* commit. The symbol_key column captures `kind:qualified_name` so a
|
|
17
|
+
* future enhancement (key-match on historical AST parse) could recover
|
|
18
|
+
* pre-rename history without touching this schema.
|
|
19
|
+
* - File renames: `git log --follow` is used per-file so file renames
|
|
20
|
+
* ARE preserved; but cross-file moves are not.
|
|
21
|
+
* - Line numbers shift across history. We compare a commit's hunks against
|
|
22
|
+
* the current symbol's line range — older overlaps are approximate.
|
|
23
|
+
* match_strategy = 'overlap' / confidence < 1.0 reflect that.
|
|
24
|
+
*
|
|
25
|
+
* Performance: bounded by `maxCommitsPerFile` (default 200) and per-call
|
|
26
|
+
* timeout. For a 10k-symbol codebase this runs in ~1-2 min once; afterwards
|
|
27
|
+
* the `git_index_state` lets us short-circuit if HEAD is unchanged.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
export interface SymbolHistoryOptions {
|
|
31
|
+
/** Cap commits processed per file. Default 200. */
|
|
32
|
+
maxCommitsPerFile?: number;
|
|
33
|
+
/** Cap history-since lookback in seconds. Default no limit. */
|
|
34
|
+
since?: number;
|
|
35
|
+
/** Only re-run if HEAD differs from `git_index_state.last_head_sha`. Default true. */
|
|
36
|
+
skipIfHeadUnchanged?: boolean;
|
|
37
|
+
/** Logger; defaults to stderr. */
|
|
38
|
+
log?: (msg: string) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface SymbolHistoryResult {
|
|
42
|
+
filesProcessed: number;
|
|
43
|
+
symbolsProcessed: number;
|
|
44
|
+
historyRowsInserted: number;
|
|
45
|
+
skipped: boolean;
|
|
46
|
+
elapsedMs: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function buildSymbolHistory(
|
|
50
|
+
repoRoot: string,
|
|
51
|
+
store: Store,
|
|
52
|
+
options: SymbolHistoryOptions = {},
|
|
53
|
+
): Promise<SymbolHistoryResult> {
|
|
54
|
+
const start = Date.now();
|
|
55
|
+
const log = options.log ?? ((m: string) => process.stderr.write(`[symbol-history] ${m}\n`));
|
|
56
|
+
if (!isGitRepo(repoRoot)) {
|
|
57
|
+
log('not a git repo, skipping');
|
|
58
|
+
return { filesProcessed: 0, symbolsProcessed: 0, historyRowsInserted: 0, skipped: true, elapsedMs: Date.now() - start };
|
|
59
|
+
}
|
|
60
|
+
const head = gitHeadSha(repoRoot);
|
|
61
|
+
const state = store.getGitIndexState();
|
|
62
|
+
// Skip only if symbol-history specifically has already been built against
|
|
63
|
+
// this HEAD. Reading `lastHistoryHeadSha` instead of the generic
|
|
64
|
+
// `lastHeadSha` means a previous file-level churn pass (which also stamps
|
|
65
|
+
// git_index_state) can't trick us into skipping when history was never
|
|
66
|
+
// actually built.
|
|
67
|
+
if (options.skipIfHeadUnchanged !== false && state && state.lastHistoryHeadSha === head && head !== null) {
|
|
68
|
+
log(`HEAD ${head?.slice(0, 8)} unchanged; skipping`);
|
|
69
|
+
return { filesProcessed: 0, symbolsProcessed: 0, historyRowsInserted: 0, skipped: true, elapsedMs: Date.now() - start };
|
|
70
|
+
}
|
|
71
|
+
const remote = gitRemoteUrl(repoRoot);
|
|
72
|
+
const maxCommits = options.maxCommitsPerFile ?? 200;
|
|
73
|
+
const symbols = store.listSymbolsForHistoryIndex();
|
|
74
|
+
if (symbols.length === 0) {
|
|
75
|
+
return { filesProcessed: 0, symbolsProcessed: 0, historyRowsInserted: 0, skipped: false, elapsedMs: Date.now() - start };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Group symbols by file path so we walk `git log` once per file.
|
|
79
|
+
const byFile = new Map<string, typeof symbols>();
|
|
80
|
+
for (const s of symbols) {
|
|
81
|
+
let arr = byFile.get(s.filePath);
|
|
82
|
+
if (!arr) { arr = []; byFile.set(s.filePath, arr); }
|
|
83
|
+
arr.push(s);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let totalInserts = 0;
|
|
87
|
+
let processedFiles = 0;
|
|
88
|
+
for (const [filePath, fileSymbols] of byFile) {
|
|
89
|
+
processedFiles++;
|
|
90
|
+
const commits = await commitsForFile(repoRoot, filePath, { limit: maxCommits, since: options.since });
|
|
91
|
+
if (commits.length === 0) continue;
|
|
92
|
+
// For each commit, fetch its diff hunks (in the new-file coordinate
|
|
93
|
+
// space). A symbol whose [line_start..line_end] range overlaps any hunk
|
|
94
|
+
// is considered touched.
|
|
95
|
+
for (let i = 0; i < commits.length; i++) {
|
|
96
|
+
const c = commits[i];
|
|
97
|
+
// Use the per-commit path resolved from `git log --follow --name-status`.
|
|
98
|
+
// If that wasn't available (older git or no rename detected), fall back
|
|
99
|
+
// to the current path — that's correct for commits where the file
|
|
100
|
+
// hadn't been renamed.
|
|
101
|
+
const lookupPath = c.pathAtCommit ?? filePath;
|
|
102
|
+
const info = await fileDiffInfo(repoRoot, c.sha, lookupPath);
|
|
103
|
+
if (info.hunks.length === 0 && !info.isFileAddition) continue;
|
|
104
|
+
const totalAdded = info.hunks.reduce((acc, h) => acc + h.newLines, 0);
|
|
105
|
+
const totalRemoved = info.hunks.reduce((acc, h) => acc + h.oldLines, 0);
|
|
106
|
+
const prNum = extractPrNumber(c.message);
|
|
107
|
+
const prUrl = prNum != null ? githubPrUrl(remote, prNum) : null;
|
|
108
|
+
for (const sym of fileSymbols) {
|
|
109
|
+
// The current symbol's line span is in the HEAD file. As commits get
|
|
110
|
+
// older, line numbers drift — so we accept any overlap in any commit
|
|
111
|
+
// as "this commit touched the line range that now holds this symbol".
|
|
112
|
+
// confidence drops with commit age to flag the drift.
|
|
113
|
+
//
|
|
114
|
+
// Special case: if this commit added the file (isFileAddition), the
|
|
115
|
+
// pre-commit state had no symbols at all — every current symbol
|
|
116
|
+
// got its initial existence from this commit, so attribute all of
|
|
117
|
+
// them regardless of where the hunk lines up. Match strategy
|
|
118
|
+
// 'file-addition' so the caller can tell apart "this commit
|
|
119
|
+
// created the file" from "this commit overlapped the symbol".
|
|
120
|
+
const symStart = sym.lineStart + 1; // git is 1-indexed
|
|
121
|
+
const symEnd = sym.lineEnd + 1;
|
|
122
|
+
let strategy: 'overlap' | 'file-addition';
|
|
123
|
+
if (info.isFileAddition) {
|
|
124
|
+
strategy = 'file-addition';
|
|
125
|
+
} else {
|
|
126
|
+
const overlaps = info.hunks.some(h => {
|
|
127
|
+
const hStart = h.newStart;
|
|
128
|
+
const hEnd = h.newStart + Math.max(0, h.newLines - 1);
|
|
129
|
+
return hStart <= symEnd && hEnd >= symStart;
|
|
130
|
+
});
|
|
131
|
+
if (!overlaps) continue;
|
|
132
|
+
strategy = 'overlap';
|
|
133
|
+
}
|
|
134
|
+
const ageDays = Math.max(0, (Date.now() / 1000 - c.committedAt) / 86400);
|
|
135
|
+
// Confidence: 1.0 for HEAD..HEAD~1, decays with age but never below 0.3.
|
|
136
|
+
const confidence = Math.max(0.3, 1.0 - Math.min(0.7, ageDays / 365 * 0.1));
|
|
137
|
+
store.insertSymbolHistory(
|
|
138
|
+
sym.id, sym.symbolKey, c.sha,
|
|
139
|
+
c.authorName, c.authorEmail, c.committedAt, c.message,
|
|
140
|
+
totalAdded, totalRemoved,
|
|
141
|
+
prNum, prUrl,
|
|
142
|
+
strategy, confidence,
|
|
143
|
+
);
|
|
144
|
+
totalInserts++;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// v10 — run the continuity heuristics for symbols whose recorded history is
|
|
150
|
+
// shallow. Strictly additive (lives in symbol_history_continuity); never
|
|
151
|
+
// touches symbol_history rows.
|
|
152
|
+
try {
|
|
153
|
+
if (store.hasV10()) {
|
|
154
|
+
const cont = buildContinuity(store, { historyThreshold: 1 });
|
|
155
|
+
log(`continuity: considered=${cont.candidatesConsidered}, inserted=${cont.inserted}, skipped=${cont.skipped}`);
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
log(`continuity pass failed: ${(err as Error).message}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Stamp the history-specific HEAD marker (not the generic one) so a future
|
|
162
|
+
// run can skip this work without colliding with file-level churn's stamp.
|
|
163
|
+
store.setHistoryHeadSha(repoRoot, head, remote);
|
|
164
|
+
log(`done: ${processedFiles} files, ${totalInserts} history rows`);
|
|
165
|
+
return {
|
|
166
|
+
filesProcessed: processedFiles,
|
|
167
|
+
symbolsProcessed: symbols.length,
|
|
168
|
+
historyRowsInserted: totalInserts,
|
|
169
|
+
skipped: false,
|
|
170
|
+
elapsedMs: Date.now() - start,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { Store } from '../db/store.js';
|
|
3
|
+
import { Indexer } from './index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Background file watcher that keeps the index warm.
|
|
7
|
+
*
|
|
8
|
+
* Design split:
|
|
9
|
+
* - Watcher (this module) reacts to file events and marks files dirty in a
|
|
10
|
+
* shared in-memory set. Debounces bursts so a `git checkout` doesn't
|
|
11
|
+
* hammer the indexer.
|
|
12
|
+
* - JIT sync ([./freshness.ts]) does the actual correctness work — when an
|
|
13
|
+
* MCP query comes in, it consults the dirty set + on-disk hashes and
|
|
14
|
+
* reindexes just what needs it.
|
|
15
|
+
*
|
|
16
|
+
* Why both? The watcher alone is racy (filesystem events on Windows arrive
|
|
17
|
+
* out of order, can be missed under load) and the JIT alone is slow on
|
|
18
|
+
* burst-y workloads (every query re-walks the workspace). Together the
|
|
19
|
+
* watcher absorbs steady-state edits so most JIT runs are no-ops, while JIT
|
|
20
|
+
* provides the per-query correctness guarantee.
|
|
21
|
+
*
|
|
22
|
+
* We use chokidar because it abstracts the OS-level oddities (FSEvents on
|
|
23
|
+
* macOS, ReadDirectoryChangesW on Windows, inotify on Linux) and provides
|
|
24
|
+
* a uniform API. It's the same library Aider uses for its watch mode.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import chokidar, { FSWatcher } from 'chokidar';
|
|
28
|
+
|
|
29
|
+
export interface WatcherOptions {
|
|
30
|
+
/** Debounce window in ms before triggering a reindex pass. Default 250ms. */
|
|
31
|
+
debounceMs?: number;
|
|
32
|
+
/** Logger; default writes to stderr (so stdio MCP isn't polluted). */
|
|
33
|
+
log?: (msg: string) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Skip these paths. Defaults match the indexer's ignore list — we don't
|
|
36
|
+
* want chokidar to fire on `node_modules/` updates that the index would
|
|
37
|
+
* skip anyway. The list is intentionally narrower than the full glob; the
|
|
38
|
+
* indexer's `discoverFiles()` re-applies all rules at reindex time.
|
|
39
|
+
*/
|
|
40
|
+
ignored?: (string | RegExp)[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class SeerWatcher {
|
|
44
|
+
private watcher: FSWatcher | null = null;
|
|
45
|
+
private dirty = new Set<string>();
|
|
46
|
+
private debounceTimer: NodeJS.Timeout | null = null;
|
|
47
|
+
private indexing = false;
|
|
48
|
+
private indexingQueue = false;
|
|
49
|
+
private lastSyncMs: number = 0;
|
|
50
|
+
private lastSyncResult: { dirtyReindexed: number; added: number; removed: number; elapsedMs: number } | null = null;
|
|
51
|
+
private logFn: (msg: string) => void;
|
|
52
|
+
private debounceMs: number;
|
|
53
|
+
|
|
54
|
+
constructor(
|
|
55
|
+
private repoRoot: string,
|
|
56
|
+
private store: Store,
|
|
57
|
+
private indexer: Indexer,
|
|
58
|
+
options: WatcherOptions = {},
|
|
59
|
+
) {
|
|
60
|
+
this.debounceMs = options.debounceMs ?? 250;
|
|
61
|
+
this.logFn = options.log ?? ((m) => process.stderr.write(`[watcher] ${m}\n`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Start watching. Idempotent: calling twice is a no-op.
|
|
66
|
+
*/
|
|
67
|
+
start(): void {
|
|
68
|
+
if (this.watcher) return;
|
|
69
|
+
const abs = path.resolve(this.repoRoot);
|
|
70
|
+
this.watcher = chokidar.watch(abs, {
|
|
71
|
+
ignored: [
|
|
72
|
+
// chokidar accepts globs and functions. We use simple substring tests
|
|
73
|
+
// because the chokidar glob engine is finicky on Windows paths.
|
|
74
|
+
(p: string) => {
|
|
75
|
+
const norm = p.replace(/\\/g, '/');
|
|
76
|
+
return (
|
|
77
|
+
norm.includes('/node_modules/') ||
|
|
78
|
+
norm.includes('/.git/') ||
|
|
79
|
+
norm.includes('/dist/') ||
|
|
80
|
+
norm.includes('/build/') ||
|
|
81
|
+
norm.includes('/.seer/') ||
|
|
82
|
+
norm.includes('/target/') ||
|
|
83
|
+
norm.includes('/obj/')
|
|
84
|
+
);
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
persistent: true,
|
|
88
|
+
ignoreInitial: true, // Don't fire for files that already exist
|
|
89
|
+
awaitWriteFinish: {
|
|
90
|
+
// Some editors write through a tmp + rename dance. Wait for the file
|
|
91
|
+
// to stop growing before considering it changed.
|
|
92
|
+
stabilityThreshold: 100,
|
|
93
|
+
pollInterval: 50,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const onChange = (filePath: string): void => {
|
|
98
|
+
this.dirty.add(filePath);
|
|
99
|
+
this.scheduleSync();
|
|
100
|
+
};
|
|
101
|
+
this.watcher.on('add', onChange);
|
|
102
|
+
this.watcher.on('change', onChange);
|
|
103
|
+
this.watcher.on('unlink', onChange);
|
|
104
|
+
this.watcher.on('error', (err: unknown) => this.logFn(`error: ${err}`));
|
|
105
|
+
this.logFn(`watching ${abs}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async stop(): Promise<void> {
|
|
109
|
+
if (this.debounceTimer) {
|
|
110
|
+
clearTimeout(this.debounceTimer);
|
|
111
|
+
this.debounceTimer = null;
|
|
112
|
+
}
|
|
113
|
+
if (this.watcher) {
|
|
114
|
+
await this.watcher.close();
|
|
115
|
+
this.watcher = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Whether anything is currently waiting to be reindexed. */
|
|
120
|
+
isDirty(): boolean {
|
|
121
|
+
return this.dirty.size > 0 || this.indexing || this.indexingQueue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
dirtyCount(): number { return this.dirty.size; }
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Snapshot for `seer_health`: when did we last reconcile, how many files,
|
|
128
|
+
* how long did it take. Lets agents tell whether the index they're querying
|
|
129
|
+
* is "this happened just now" vs "we haven't run in 10 minutes."
|
|
130
|
+
*/
|
|
131
|
+
syncStatus(): {
|
|
132
|
+
watching: boolean;
|
|
133
|
+
dirtyCount: number;
|
|
134
|
+
indexing: boolean;
|
|
135
|
+
lastSyncMs: number;
|
|
136
|
+
lastSync: { dirtyReindexed: number; added: number; removed: number; elapsedMs: number } | null;
|
|
137
|
+
} {
|
|
138
|
+
return {
|
|
139
|
+
watching: this.watcher !== null,
|
|
140
|
+
dirtyCount: this.dirty.size,
|
|
141
|
+
indexing: this.indexing,
|
|
142
|
+
lastSyncMs: this.lastSyncMs,
|
|
143
|
+
lastSync: this.lastSyncResult,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Force an immediate sync, bypassing the debounce. Used by `seer_reindex`
|
|
149
|
+
* and JIT — both need the dirty set drained NOW, not when a timer fires.
|
|
150
|
+
*/
|
|
151
|
+
async syncNow(): Promise<void> {
|
|
152
|
+
if (this.debounceTimer) {
|
|
153
|
+
clearTimeout(this.debounceTimer);
|
|
154
|
+
this.debounceTimer = null;
|
|
155
|
+
}
|
|
156
|
+
await this.runSync();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private scheduleSync(): void {
|
|
160
|
+
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
161
|
+
this.debounceTimer = setTimeout(() => {
|
|
162
|
+
this.debounceTimer = null;
|
|
163
|
+
// Fire-and-forget — uncaught rejection would just kill the process.
|
|
164
|
+
// Errors are reported through the log function.
|
|
165
|
+
this.runSync().catch(err => this.logFn(`sync failed: ${err.message ?? err}`));
|
|
166
|
+
}, this.debounceMs);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Reindex the workspace. Serialized — if a sync is already running, we
|
|
171
|
+
* queue at most one follow-up and coalesce further requests into it. This
|
|
172
|
+
* matches the indexer's contract that only one writer can be active.
|
|
173
|
+
*/
|
|
174
|
+
private async runSync(): Promise<void> {
|
|
175
|
+
if (this.indexing) {
|
|
176
|
+
this.indexingQueue = true;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
this.indexing = true;
|
|
180
|
+
try {
|
|
181
|
+
const dirtyCount = this.dirty.size;
|
|
182
|
+
// Clear the dirty set BEFORE running the index so any events arriving
|
|
183
|
+
// during the index are caught for the next pass instead of being lost.
|
|
184
|
+
this.dirty.clear();
|
|
185
|
+
const start = Date.now();
|
|
186
|
+
const result = await this.indexer.indexDirectory(this.repoRoot, { quiet: true });
|
|
187
|
+
this.lastSyncMs = Date.now();
|
|
188
|
+
this.lastSyncResult = {
|
|
189
|
+
dirtyReindexed: dirtyCount,
|
|
190
|
+
added: result.filesIndexed,
|
|
191
|
+
removed: 0, // indexer doesn't expose pruned count; OK for now
|
|
192
|
+
elapsedMs: result.elapsedMs,
|
|
193
|
+
};
|
|
194
|
+
this.logFn(
|
|
195
|
+
`sync: ${dirtyCount} dirty event(s), ${result.filesIndexed} reindexed, ` +
|
|
196
|
+
`${result.filesReusedFromCache} reused, ${result.elapsedMs}ms`,
|
|
197
|
+
);
|
|
198
|
+
} finally {
|
|
199
|
+
this.indexing = false;
|
|
200
|
+
if (this.indexingQueue) {
|
|
201
|
+
this.indexingQueue = false;
|
|
202
|
+
this.runSync().catch(err => this.logFn(`requeued sync failed: ${err.message ?? err}`));
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|