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,930 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.Indexer = void 0;
|
|
40
|
+
const fs_1 = __importDefault(require("fs"));
|
|
41
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
42
|
+
const path_1 = __importDefault(require("path"));
|
|
43
|
+
const discovery_js_1 = require("./discovery.js");
|
|
44
|
+
const index_js_1 = require("../parser/index.js");
|
|
45
|
+
const workerpool_js_1 = require("../parser/workerpool.js");
|
|
46
|
+
const pagerank_js_1 = require("../graph/pagerank.js");
|
|
47
|
+
const classify_js_1 = require("./classify.js");
|
|
48
|
+
const modules_js_1 = require("./modules.js");
|
|
49
|
+
const boundaries_js_1 = require("./boundaries.js");
|
|
50
|
+
const shapehash_js_1 = require("./shapehash.js");
|
|
51
|
+
const continuity_js_1 = require("./continuity.js");
|
|
52
|
+
const serviceLinks_js_1 = require("./serviceLinks.js");
|
|
53
|
+
const protoScanner_js_1 = require("./protoScanner.js");
|
|
54
|
+
const serviceHostScanner_js_1 = require("./serviceHostScanner.js");
|
|
55
|
+
const DEFAULT_MAX_FILE_BYTES = 0; // no cap by default — completeness first
|
|
56
|
+
const DEFAULT_IO_CONCURRENCY = 8; // matches the file-handle budget on most OSes comfortably
|
|
57
|
+
const DEFAULT_IO_PREFETCH_BYTES = 64 * 1024 * 1024; // 64 MiB
|
|
58
|
+
const PARALLEL_AUTO_MIN_FILES = 100; // below this, default to serial unless explicitly forced
|
|
59
|
+
// Filenames that are almost always generated boilerplate (Unreal Header Tool
|
|
60
|
+
// produces *.generated.h; protobufs produce *.pb.h / *.pb.cc; etc.). We skip
|
|
61
|
+
// them at the per-file level so the discovery glob can stay simple.
|
|
62
|
+
const SKIP_FILENAME_PATTERNS = [
|
|
63
|
+
/\.generated\.h$/i,
|
|
64
|
+
/\.gen\.cpp$/i,
|
|
65
|
+
/\.gen\.h$/i,
|
|
66
|
+
/\.pb\.cc$/,
|
|
67
|
+
/\.pb\.h$/,
|
|
68
|
+
];
|
|
69
|
+
function shouldSkipFilename(relativePath) {
|
|
70
|
+
return SKIP_FILENAME_PATTERNS.some(re => re.test(relativePath));
|
|
71
|
+
}
|
|
72
|
+
// ── Byte-aware semaphore ────────────────────────────────────────────────────────
|
|
73
|
+
//
|
|
74
|
+
// Caps total bytes of file content held in the prefetch buffer. A waiter is
|
|
75
|
+
// admitted as soon as either (a) the new total fits within `capacity`, or
|
|
76
|
+
// (b) the budget is empty (so a single oversize file never deadlocks — we
|
|
77
|
+
// always allow at least one read in flight).
|
|
78
|
+
//
|
|
79
|
+
// The implementation is deliberately FIFO: we only wake the *head* waiter, so
|
|
80
|
+
// a flood of small reads can't perpetually starve a single large one queued
|
|
81
|
+
// behind them.
|
|
82
|
+
class ByteSemaphore {
|
|
83
|
+
capacity;
|
|
84
|
+
bytes = 0;
|
|
85
|
+
waiters = [];
|
|
86
|
+
constructor(capacity) {
|
|
87
|
+
this.capacity = capacity;
|
|
88
|
+
}
|
|
89
|
+
async acquire(requested) {
|
|
90
|
+
if (this.bytes === 0 || this.bytes + requested <= this.capacity) {
|
|
91
|
+
this.bytes += requested;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
await new Promise(resolve => {
|
|
95
|
+
this.waiters.push({ bytes: requested, resolve });
|
|
96
|
+
});
|
|
97
|
+
// `release` has already added `requested` to `this.bytes` on our behalf
|
|
98
|
+
// before resolving — see `release()` below.
|
|
99
|
+
}
|
|
100
|
+
release(returned) {
|
|
101
|
+
this.bytes -= returned;
|
|
102
|
+
if (this.bytes < 0)
|
|
103
|
+
this.bytes = 0; // defensive: never go negative
|
|
104
|
+
while (this.waiters.length > 0) {
|
|
105
|
+
const next = this.waiters[0];
|
|
106
|
+
if (this.bytes === 0 || this.bytes + next.bytes <= this.capacity) {
|
|
107
|
+
this.bytes += next.bytes;
|
|
108
|
+
this.waiters.shift();
|
|
109
|
+
next.resolve();
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
class Indexer {
|
|
118
|
+
store;
|
|
119
|
+
constructor(store) {
|
|
120
|
+
this.store = store;
|
|
121
|
+
}
|
|
122
|
+
async indexDirectory(repoRoot, options = {}) {
|
|
123
|
+
const start = Date.now();
|
|
124
|
+
const absRoot = path_1.default.resolve(repoRoot);
|
|
125
|
+
const quiet = options.quiet && !options.verbose;
|
|
126
|
+
if (options.verbose) {
|
|
127
|
+
process.stdout.write(`\nDiscovering files in ${absRoot}...\n`);
|
|
128
|
+
}
|
|
129
|
+
const files = await (0, discovery_js_1.discoverFiles)(absRoot, {
|
|
130
|
+
includeVendor: options.includeVendor,
|
|
131
|
+
includeGenerated: options.includeGenerated,
|
|
132
|
+
mode: options.mode,
|
|
133
|
+
});
|
|
134
|
+
const total = files.length;
|
|
135
|
+
const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
136
|
+
const ioConcurrency = Math.max(1, options.ioConcurrency ?? DEFAULT_IO_CONCURRENCY);
|
|
137
|
+
const ioPrefetchBytes = Math.max(1, options.ioPrefetchBytes ?? DEFAULT_IO_PREFETCH_BYTES);
|
|
138
|
+
// Track every file_id we touch this run so we can prune ones left over
|
|
139
|
+
// from a previous run (e.g. files now hidden by a new ignore rule).
|
|
140
|
+
const touchedFileIds = new Set();
|
|
141
|
+
let indexed = 0;
|
|
142
|
+
let reusedFromCache = 0;
|
|
143
|
+
let skipped = 0;
|
|
144
|
+
let skippedTooLarge = 0;
|
|
145
|
+
let parseErrors = 0;
|
|
146
|
+
let workerWasmResets = 0;
|
|
147
|
+
// ── Pre-filter into a work queue ────────────────────────────────────────────
|
|
148
|
+
// Pure CPU work (string ops on the path). Cheap to do all at once so the
|
|
149
|
+
// async prefetcher's index space matches up cleanly with progress counters.
|
|
150
|
+
const work = [];
|
|
151
|
+
for (const file of files) {
|
|
152
|
+
const language = (0, index_js_1.detectLanguage)(file.absolutePath);
|
|
153
|
+
if (!language || shouldSkipFilename(file.relativePath)) {
|
|
154
|
+
skipped++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
work.push({ file, language });
|
|
158
|
+
}
|
|
159
|
+
// Existing pre-v8 DBs can have all source hashes cached but no
|
|
160
|
+
// service_calls rows yet. Force one full parse pass so Track-G evidence is
|
|
161
|
+
// backfilled, then mark completion in finishIndex().
|
|
162
|
+
const forceServiceCallBackfill = this.store.needsServiceCallBackfill();
|
|
163
|
+
// ── Batched transactions ──────────────────────────────────────────────────
|
|
164
|
+
// The Phase-1 design wrapped each file's inserts in its own SQLite
|
|
165
|
+
// transaction. That works but every commit fsyncs the WAL, which adds
|
|
166
|
+
// O(milliseconds) of overhead per file. For 40k+ file repos the commit
|
|
167
|
+
// overhead dominates the per-file budget. Batching N files per transaction
|
|
168
|
+
// amortizes the fsync cost N-fold.
|
|
169
|
+
//
|
|
170
|
+
// Trade-off: a fatal error mid-batch rolls back at most BATCH_SIZE files'
|
|
171
|
+
// worth of inserts (which we'd just re-do on the next run). We never lose
|
|
172
|
+
// user data — only re-do work — so a moderately large batch is safe.
|
|
173
|
+
//
|
|
174
|
+
// The hash-skip path participates in the batch too (its UPDATE indexed_at
|
|
175
|
+
// and DELETE-old-symbols statements were previously running as
|
|
176
|
+
// autocommitted singletons — now they share a transaction with the file's
|
|
177
|
+
// inserts).
|
|
178
|
+
const BATCH_SIZE = 200;
|
|
179
|
+
let batchOpen = false;
|
|
180
|
+
const openBatch = () => {
|
|
181
|
+
if (!batchOpen) {
|
|
182
|
+
this.store.begin();
|
|
183
|
+
batchOpen = true;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const closeBatch = () => {
|
|
187
|
+
if (batchOpen) {
|
|
188
|
+
this.store.commit();
|
|
189
|
+
batchOpen = false;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
const rollbackBatch = () => {
|
|
193
|
+
if (batchOpen) {
|
|
194
|
+
try {
|
|
195
|
+
this.store.rollback();
|
|
196
|
+
}
|
|
197
|
+
catch { /* best effort */ }
|
|
198
|
+
batchOpen = false;
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
if (!options.verbose && !quiet) {
|
|
202
|
+
writeProgress(0, total, '');
|
|
203
|
+
}
|
|
204
|
+
// ── Parallel-parsing branch (worker pool) ───────────────────────────────────
|
|
205
|
+
//
|
|
206
|
+
// When enabled, each file's read + hash + parse runs in a worker_threads
|
|
207
|
+
// worker (its own WASM heap). The pool delivers results to the callback
|
|
208
|
+
// STRICTLY in input order, so symbol-id insertion order — and therefore
|
|
209
|
+
// every cross-run-stable scale-test invariant — is identical to the
|
|
210
|
+
// serial path. DB writes still run single-writer on the main thread.
|
|
211
|
+
//
|
|
212
|
+
// Result-kind contract (matches the serial branch's semantics exactly):
|
|
213
|
+
// parsed → upsertFileWithCache → touchedFileIds.add → insert all
|
|
214
|
+
// parse-error → upsertFileWithCache → touchedFileIds.add → no inserts
|
|
215
|
+
// cached → upsertFileWithCache → touchedFileIds.add → no inserts
|
|
216
|
+
// (worker confirmed hash === expectedHash; upsert sees
|
|
217
|
+
// the same hash and returns unchanged=true)
|
|
218
|
+
// too-large → counter only; file row NOT touched → pruned
|
|
219
|
+
// io-error → counter only; file row NOT touched → pruned
|
|
220
|
+
//
|
|
221
|
+
// The cached/parse-error upsert calls are CRITICAL: without them
|
|
222
|
+
// `touchedFileIds` would not contain those file ids and
|
|
223
|
+
// `pruneFilesNotIn(touchedFileIds)` below would delete every unchanged
|
|
224
|
+
// cached file from the DB.
|
|
225
|
+
// Auto-enabled for normal/large workspaces. Tiny workspaces stay serial by
|
|
226
|
+
// default to avoid worker startup/churn; force workers with `parallel: true`
|
|
227
|
+
// or `SEER_PARALLEL_PARSE=1`. Opt out with `parallel: false` or
|
|
228
|
+
// `SEER_PARALLEL_PARSE=0`.
|
|
229
|
+
const envParallel = typeof process !== 'undefined' && process.env != null
|
|
230
|
+
? (process.env.SEER_PARALLEL_PARSE === '0' ? false
|
|
231
|
+
: process.env.SEER_PARALLEL_PARSE === '1' ? true
|
|
232
|
+
: undefined)
|
|
233
|
+
: undefined;
|
|
234
|
+
const parallelRequested = options.parallel ?? envParallel ?? true;
|
|
235
|
+
const parallelForced = options.parallel === true || envParallel === true;
|
|
236
|
+
const parallelEnabled = parallelRequested && (parallelForced || work.length >= PARALLEL_AUTO_MIN_FILES);
|
|
237
|
+
if (parallelEnabled && work.length > 0) {
|
|
238
|
+
// Snapshot known DB hashes so workers can skip parsing on cache hits.
|
|
239
|
+
const cacheMap = new Map();
|
|
240
|
+
for (const f of this.store.listFiles())
|
|
241
|
+
cacheMap.set(f.path, f.hash);
|
|
242
|
+
const poolItems = work.map(w => ({
|
|
243
|
+
abs: w.file.absolutePath,
|
|
244
|
+
lang: w.language,
|
|
245
|
+
expectedHash: forceServiceCallBackfill ? null : cacheMap.get(w.file.absolutePath) ?? null,
|
|
246
|
+
maxFileBytes,
|
|
247
|
+
}));
|
|
248
|
+
const pool = new workerpool_js_1.WorkerPool({ jobs: options.jobs });
|
|
249
|
+
try {
|
|
250
|
+
await pool.ready();
|
|
251
|
+
let processed = 0;
|
|
252
|
+
await pool.dispatch(poolItems, (seq, result) => {
|
|
253
|
+
processed++;
|
|
254
|
+
const w = work[seq];
|
|
255
|
+
const rel = w.file.relativePath;
|
|
256
|
+
// Counters-only branches (file row stays untouched → pruned).
|
|
257
|
+
if (result.kind === 'too-large') {
|
|
258
|
+
skippedTooLarge++;
|
|
259
|
+
if (options.verbose) {
|
|
260
|
+
process.stdout.write(` ⤬ ${rel} (${(result.size / 1024).toFixed(0)} KiB > ${(maxFileBytes / 1024).toFixed(0)} KiB cap)\n`);
|
|
261
|
+
}
|
|
262
|
+
else if (!quiet)
|
|
263
|
+
writeProgress(processed, total, rel);
|
|
264
|
+
if (processed % BATCH_SIZE === 0)
|
|
265
|
+
closeBatch();
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (result.kind === 'io-error') {
|
|
269
|
+
skipped++;
|
|
270
|
+
if (options.verbose)
|
|
271
|
+
process.stdout.write(` ⚠ ${rel} (read error: ${result.error})\n`);
|
|
272
|
+
else if (!quiet)
|
|
273
|
+
writeProgress(processed, total, rel);
|
|
274
|
+
if (processed % BATCH_SIZE === 0)
|
|
275
|
+
closeBatch();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
// parsed / parse-error / cached all read the file successfully —
|
|
279
|
+
// we have hash + lines. Always upsert so touchedFileIds is updated.
|
|
280
|
+
const hash = result.hash;
|
|
281
|
+
const lines = result.lines;
|
|
282
|
+
openBatch();
|
|
283
|
+
const classification = (0, classify_js_1.classifyFile)(rel);
|
|
284
|
+
const upserted = forceServiceCallBackfill
|
|
285
|
+
? { fileId: this.store.upsertFile(w.file.absolutePath, rel, w.language, hash, lines, classification), unchanged: false }
|
|
286
|
+
: this.store.upsertFileWithCache(w.file.absolutePath, rel, w.language, hash, lines, classification);
|
|
287
|
+
const { fileId, unchanged } = upserted;
|
|
288
|
+
touchedFileIds.add(fileId);
|
|
289
|
+
// Cache hit (worker's hash matched the DB's stored hash). Prior
|
|
290
|
+
// symbols/edges/imports/routes/configKeys stay as-is. Note: an
|
|
291
|
+
// explicit `cached` result always falls into this branch; a `parsed`
|
|
292
|
+
// result whose hash happens to match an in-flight DB update would
|
|
293
|
+
// also land here defensively (we never re-insert when unchanged).
|
|
294
|
+
if (unchanged) {
|
|
295
|
+
reusedFromCache++;
|
|
296
|
+
if (options.verbose)
|
|
297
|
+
process.stdout.write(` = ${rel} (cached)\n`);
|
|
298
|
+
else if (!quiet)
|
|
299
|
+
writeProgress(processed, total, rel);
|
|
300
|
+
if (processed % BATCH_SIZE === 0)
|
|
301
|
+
closeBatch();
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// Only `parsed` carries an extraction. `cached` lands in the
|
|
305
|
+
// unchanged-branch above; `parse-error` and any defensive fall-
|
|
306
|
+
// through here get treated as a parse error (file row exists,
|
|
307
|
+
// no symbols/edges emitted).
|
|
308
|
+
if (result.kind !== 'parsed') {
|
|
309
|
+
parseErrors++;
|
|
310
|
+
if (options.verbose)
|
|
311
|
+
process.stdout.write(` ⚠ ${rel} (parse error)\n`);
|
|
312
|
+
else if (!quiet)
|
|
313
|
+
writeProgress(processed, total, rel);
|
|
314
|
+
if (processed % BATCH_SIZE === 0)
|
|
315
|
+
closeBatch();
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
// parsed: insert all symbols, edges, imports, routes, configKeys.
|
|
319
|
+
const extraction = result.extraction;
|
|
320
|
+
const symbolIdMap = new Map();
|
|
321
|
+
for (const def of extraction.definitions) {
|
|
322
|
+
const symId = this.store.insertSymbol(fileId, def);
|
|
323
|
+
const qname = def.qualifiedName ?? def.name;
|
|
324
|
+
if (!symbolIdMap.has(qname))
|
|
325
|
+
symbolIdMap.set(qname, symId);
|
|
326
|
+
}
|
|
327
|
+
for (const ref of extraction.references) {
|
|
328
|
+
const fromId = ref.callerName ? symbolIdMap.get(ref.callerName) : undefined;
|
|
329
|
+
if (fromId !== undefined) {
|
|
330
|
+
this.store.insertEdge(fromId, ref.calleeName, ref.kind, ref.line);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const mod of extraction.importedModules) {
|
|
334
|
+
this.store.insertFileImport(fileId, mod);
|
|
335
|
+
}
|
|
336
|
+
if (extraction.routes) {
|
|
337
|
+
for (const r of extraction.routes) {
|
|
338
|
+
this.store.insertRoute(fileId, r.method, r.path, r.framework, r.handlerName ?? null, r.line, {
|
|
339
|
+
protocol: r.protocol ?? 'http',
|
|
340
|
+
operation: r.operation ?? null,
|
|
341
|
+
topic: r.topic ?? null,
|
|
342
|
+
queue: r.queue ?? null,
|
|
343
|
+
exchange: r.exchange ?? null,
|
|
344
|
+
service: r.service ?? null,
|
|
345
|
+
broker: r.broker ?? null,
|
|
346
|
+
metadataJson: r.metadataJson ?? null,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (extraction.configKeys) {
|
|
351
|
+
for (const c of extraction.configKeys) {
|
|
352
|
+
const enclosingId = c.callerName ? symbolIdMap.get(c.callerName) ?? null : null;
|
|
353
|
+
this.store.insertConfigKey(c.key, c.source, fileId, enclosingId, c.line);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (extraction.serviceCalls) {
|
|
357
|
+
for (const sc of extraction.serviceCalls) {
|
|
358
|
+
const enclosingId = sc.callerName ? symbolIdMap.get(sc.callerName) ?? null : null;
|
|
359
|
+
// Only run HTTP-shaped normalization when the call is HTTP.
|
|
360
|
+
const norm = sc.protocol === 'http'
|
|
361
|
+
? (0, serviceLinks_js_1.normalizeHttpTarget)(sc.rawTarget)
|
|
362
|
+
: { path: undefined, hostHint: undefined };
|
|
363
|
+
this.store.insertServiceCall({
|
|
364
|
+
fileId,
|
|
365
|
+
symbolId: enclosingId,
|
|
366
|
+
protocol: sc.protocol,
|
|
367
|
+
method: sc.method ?? null,
|
|
368
|
+
rawTarget: sc.rawTarget,
|
|
369
|
+
normalizedPath: sc.normalizedPath ?? norm.path ?? null,
|
|
370
|
+
hostHint: sc.hostHint ?? norm.hostHint ?? null,
|
|
371
|
+
envKey: sc.envKey ?? null,
|
|
372
|
+
framework: sc.framework,
|
|
373
|
+
line: sc.line,
|
|
374
|
+
confidence: sc.confidence,
|
|
375
|
+
operation: sc.operation ?? null,
|
|
376
|
+
topic: sc.topic ?? null,
|
|
377
|
+
queue: sc.queue ?? null,
|
|
378
|
+
exchange: sc.exchange ?? null,
|
|
379
|
+
service: sc.service ?? null,
|
|
380
|
+
broker: sc.broker ?? null,
|
|
381
|
+
metadataJson: sc.metadataJson ?? null,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (processed % BATCH_SIZE === 0)
|
|
386
|
+
closeBatch();
|
|
387
|
+
indexed++;
|
|
388
|
+
if (options.verbose) {
|
|
389
|
+
process.stdout.write(` ✓ ${rel} (${extraction.definitions.length} symbols, ${extraction.references.length} refs)\n`);
|
|
390
|
+
}
|
|
391
|
+
else if (!quiet) {
|
|
392
|
+
writeProgress(processed, total, rel);
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
workerWasmResets = pool.wasmResetCount();
|
|
396
|
+
closeBatch();
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
rollbackBatch();
|
|
400
|
+
await pool.terminate().catch(() => { });
|
|
401
|
+
throw err;
|
|
402
|
+
}
|
|
403
|
+
await pool.shutdown();
|
|
404
|
+
if (!options.verbose && !quiet)
|
|
405
|
+
process.stdout.write('\n');
|
|
406
|
+
// Skip the serial prefetcher block below.
|
|
407
|
+
return await this.finishIndex(absRoot, start, total, indexed, reusedFromCache, skipped, skippedTooLarge, parseErrors, touchedFileIds, { verbose: options.verbose, quiet: !!quiet, workerWasmResets });
|
|
408
|
+
}
|
|
409
|
+
// ── Bounded async prefetcher (serial branch) ────────────────────────────────
|
|
410
|
+
//
|
|
411
|
+
// Producer side: a fixed sliding window of up to `ioConcurrency` in-flight
|
|
412
|
+
// `prefetchOne()` calls, each one bounded by `byteSem` so cumulative
|
|
413
|
+
// buffered content never exceeds `ioPrefetchBytes`.
|
|
414
|
+
//
|
|
415
|
+
// Consumer side: the main loop awaits prefetched results IN ORDER (so
|
|
416
|
+
// batching/progress/determinism match the old sync loop exactly), parses
|
|
417
|
+
// serially (single shared WASM module — see parser/index.ts), and writes
|
|
418
|
+
// serially (single SQLite connection).
|
|
419
|
+
//
|
|
420
|
+
// The byte budget is released only AFTER parse completes, because parse
|
|
421
|
+
// reads from the in-memory string. That means while a slow file parses we
|
|
422
|
+
// hold its budget — which is intentional: backpressures the prefetcher
|
|
423
|
+
// exactly when the parser falls behind.
|
|
424
|
+
const byteSem = new ByteSemaphore(ioPrefetchBytes);
|
|
425
|
+
const prefetchOne = async (idx) => {
|
|
426
|
+
const item = work[idx];
|
|
427
|
+
const abs = item.file.absolutePath;
|
|
428
|
+
// Only stat when there's an actual size cap to enforce. Saves one
|
|
429
|
+
// syscall per file in the (default) `maxFileBytes === 0` mode.
|
|
430
|
+
if (maxFileBytes > 0) {
|
|
431
|
+
let size;
|
|
432
|
+
try {
|
|
433
|
+
size = (await fs_1.default.promises.stat(abs)).size;
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return { kind: 'io-error', item };
|
|
437
|
+
}
|
|
438
|
+
if (size > maxFileBytes) {
|
|
439
|
+
return { kind: 'too-large', item, size };
|
|
440
|
+
}
|
|
441
|
+
await byteSem.acquire(Math.max(size, 1));
|
|
442
|
+
let content;
|
|
443
|
+
try {
|
|
444
|
+
content = await fs_1.default.promises.readFile(abs, 'utf8');
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
byteSem.release(Math.max(size, 1));
|
|
448
|
+
return { kind: 'io-error', item };
|
|
449
|
+
}
|
|
450
|
+
// Update the held budget if the on-disk size disagreed with the
|
|
451
|
+
// decoded string length (Buffer length, after UTF-8 → UTF-16). We
|
|
452
|
+
// re-anchor to the actual content length so future releases match.
|
|
453
|
+
const actual = Buffer.byteLength(content, 'utf8');
|
|
454
|
+
if (actual !== size) {
|
|
455
|
+
byteSem.release(Math.max(size, 1));
|
|
456
|
+
await byteSem.acquire(Math.max(actual, 1));
|
|
457
|
+
}
|
|
458
|
+
return { kind: 'ok', item, content, size: actual };
|
|
459
|
+
}
|
|
460
|
+
// No size cap → skip the stat entirely. We don't know the size up
|
|
461
|
+
// front, so reserve a conservative slot (1 byte), read, then re-acquire
|
|
462
|
+
// the true size. This keeps a single huge file from blocking us before
|
|
463
|
+
// we even know it's huge.
|
|
464
|
+
await byteSem.acquire(1);
|
|
465
|
+
let content;
|
|
466
|
+
try {
|
|
467
|
+
content = await fs_1.default.promises.readFile(abs, 'utf8');
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
byteSem.release(1);
|
|
471
|
+
return { kind: 'io-error', item };
|
|
472
|
+
}
|
|
473
|
+
const size = Buffer.byteLength(content, 'utf8');
|
|
474
|
+
// Re-anchor budget to actual size.
|
|
475
|
+
byteSem.release(1);
|
|
476
|
+
await byteSem.acquire(Math.max(size, 1));
|
|
477
|
+
return { kind: 'ok', item, content, size };
|
|
478
|
+
};
|
|
479
|
+
// Sliding window of in-flight prefetches, indexed by their position in `work`.
|
|
480
|
+
// We `slots.shift()` after awaiting so the array stays small (≤ ioConcurrency).
|
|
481
|
+
const slots = [];
|
|
482
|
+
let nextToLaunch = 0;
|
|
483
|
+
const launchUpTo = (window) => {
|
|
484
|
+
while (slots.length < window && nextToLaunch < work.length) {
|
|
485
|
+
slots.push(prefetchOne(nextToLaunch));
|
|
486
|
+
nextToLaunch++;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
// Prime the pipeline.
|
|
490
|
+
launchUpTo(ioConcurrency);
|
|
491
|
+
let processed = 0;
|
|
492
|
+
try {
|
|
493
|
+
while (slots.length > 0) {
|
|
494
|
+
const prefetched = await slots.shift();
|
|
495
|
+
processed++;
|
|
496
|
+
// Whatever happens to this file, the moment we're done with the
|
|
497
|
+
// string we MUST release its byte budget so the next prefetch can
|
|
498
|
+
// start. We accumulate the released amount and release in `finally`
|
|
499
|
+
// at the end of each iteration.
|
|
500
|
+
const heldBytes = prefetched.kind === 'ok' ? Math.max(prefetched.size, 1) :
|
|
501
|
+
prefetched.kind === 'too-large' ? 0 : // never acquired
|
|
502
|
+
0; // io-error already released
|
|
503
|
+
try {
|
|
504
|
+
if (prefetched.kind === 'too-large') {
|
|
505
|
+
skippedTooLarge++;
|
|
506
|
+
if (options.verbose) {
|
|
507
|
+
process.stdout.write(` ⤬ ${prefetched.item.file.relativePath} (${(prefetched.size / 1024).toFixed(0)} KiB > ${(maxFileBytes / 1024).toFixed(0)} KiB cap)\n`);
|
|
508
|
+
}
|
|
509
|
+
else if (!quiet) {
|
|
510
|
+
writeProgress(processed, total, prefetched.item.file.relativePath);
|
|
511
|
+
}
|
|
512
|
+
if (processed % BATCH_SIZE === 0)
|
|
513
|
+
closeBatch();
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
if (prefetched.kind === 'io-error') {
|
|
517
|
+
skipped++;
|
|
518
|
+
if (options.verbose) {
|
|
519
|
+
process.stdout.write(` ⚠ ${prefetched.item.file.relativePath} (read error)\n`);
|
|
520
|
+
}
|
|
521
|
+
else if (!quiet) {
|
|
522
|
+
writeProgress(processed, total, prefetched.item.file.relativePath);
|
|
523
|
+
}
|
|
524
|
+
if (processed % BATCH_SIZE === 0)
|
|
525
|
+
closeBatch();
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const { item, content } = prefetched;
|
|
529
|
+
const { file, language } = item;
|
|
530
|
+
const hash = sha256(content);
|
|
531
|
+
const lines = content.split('\n').length;
|
|
532
|
+
openBatch();
|
|
533
|
+
const classification = (0, classify_js_1.classifyFile)(file.relativePath);
|
|
534
|
+
const upserted = forceServiceCallBackfill
|
|
535
|
+
? { fileId: this.store.upsertFile(file.absolutePath, file.relativePath, language, hash, lines, classification), unchanged: false }
|
|
536
|
+
: this.store.upsertFileWithCache(file.absolutePath, file.relativePath, language, hash, lines, classification);
|
|
537
|
+
const { fileId, unchanged } = upserted;
|
|
538
|
+
touchedFileIds.add(fileId);
|
|
539
|
+
// Hash-based cache hit: same content as last run → keep symbols, edges,
|
|
540
|
+
// and file_imports as-is. Edge to_ids that point to symbols that have
|
|
541
|
+
// since been deleted got NULLed by the FK cascade, so resolveEdges()
|
|
542
|
+
// below will still re-link them.
|
|
543
|
+
if (unchanged) {
|
|
544
|
+
reusedFromCache++;
|
|
545
|
+
if (options.verbose) {
|
|
546
|
+
process.stdout.write(` = ${file.relativePath} (cached)\n`);
|
|
547
|
+
}
|
|
548
|
+
else if (!quiet) {
|
|
549
|
+
writeProgress(processed, total, file.relativePath);
|
|
550
|
+
}
|
|
551
|
+
if (processed % BATCH_SIZE === 0)
|
|
552
|
+
closeBatch();
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
555
|
+
const extraction = await (0, index_js_1.parseFile)(content, file.absolutePath, language);
|
|
556
|
+
if (!extraction) {
|
|
557
|
+
parseErrors++;
|
|
558
|
+
if (options.verbose) {
|
|
559
|
+
process.stdout.write(` ⚠ ${file.relativePath} (parse error)\n`);
|
|
560
|
+
}
|
|
561
|
+
else if (!quiet) {
|
|
562
|
+
writeProgress(processed, total, file.relativePath);
|
|
563
|
+
}
|
|
564
|
+
if (processed % BATCH_SIZE === 0)
|
|
565
|
+
closeBatch();
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
const symbolIdMap = new Map(); // qualifiedName → id
|
|
569
|
+
for (const def of extraction.definitions) {
|
|
570
|
+
const symId = this.store.insertSymbol(fileId, def);
|
|
571
|
+
const qname = def.qualifiedName ?? def.name;
|
|
572
|
+
if (!symbolIdMap.has(qname))
|
|
573
|
+
symbolIdMap.set(qname, symId);
|
|
574
|
+
}
|
|
575
|
+
for (const ref of extraction.references) {
|
|
576
|
+
const fromId = ref.callerName ? symbolIdMap.get(ref.callerName) : undefined;
|
|
577
|
+
if (fromId !== undefined) {
|
|
578
|
+
this.store.insertEdge(fromId, ref.calleeName, ref.kind, ref.line);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
for (const mod of extraction.importedModules) {
|
|
582
|
+
this.store.insertFileImport(fileId, mod);
|
|
583
|
+
}
|
|
584
|
+
if (extraction.routes) {
|
|
585
|
+
for (const r of extraction.routes) {
|
|
586
|
+
this.store.insertRoute(fileId, r.method, r.path, r.framework, r.handlerName ?? null, r.line, {
|
|
587
|
+
protocol: r.protocol ?? 'http',
|
|
588
|
+
operation: r.operation ?? null,
|
|
589
|
+
topic: r.topic ?? null,
|
|
590
|
+
queue: r.queue ?? null,
|
|
591
|
+
exchange: r.exchange ?? null,
|
|
592
|
+
service: r.service ?? null,
|
|
593
|
+
broker: r.broker ?? null,
|
|
594
|
+
metadataJson: r.metadataJson ?? null,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (extraction.configKeys) {
|
|
599
|
+
for (const c of extraction.configKeys) {
|
|
600
|
+
const enclosingId = c.callerName ? symbolIdMap.get(c.callerName) ?? null : null;
|
|
601
|
+
this.store.insertConfigKey(c.key, c.source, fileId, enclosingId, c.line);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (extraction.serviceCalls) {
|
|
605
|
+
for (const sc of extraction.serviceCalls) {
|
|
606
|
+
const enclosingId = sc.callerName ? symbolIdMap.get(sc.callerName) ?? null : null;
|
|
607
|
+
// Only run HTTP-shaped normalization when the call is HTTP.
|
|
608
|
+
const norm = sc.protocol === 'http'
|
|
609
|
+
? (0, serviceLinks_js_1.normalizeHttpTarget)(sc.rawTarget)
|
|
610
|
+
: { path: undefined, hostHint: undefined };
|
|
611
|
+
this.store.insertServiceCall({
|
|
612
|
+
fileId,
|
|
613
|
+
symbolId: enclosingId,
|
|
614
|
+
protocol: sc.protocol,
|
|
615
|
+
method: sc.method ?? null,
|
|
616
|
+
rawTarget: sc.rawTarget,
|
|
617
|
+
normalizedPath: sc.normalizedPath ?? norm.path ?? null,
|
|
618
|
+
hostHint: sc.hostHint ?? norm.hostHint ?? null,
|
|
619
|
+
envKey: sc.envKey ?? null,
|
|
620
|
+
framework: sc.framework,
|
|
621
|
+
line: sc.line,
|
|
622
|
+
confidence: sc.confidence,
|
|
623
|
+
operation: sc.operation ?? null,
|
|
624
|
+
topic: sc.topic ?? null,
|
|
625
|
+
queue: sc.queue ?? null,
|
|
626
|
+
exchange: sc.exchange ?? null,
|
|
627
|
+
service: sc.service ?? null,
|
|
628
|
+
broker: sc.broker ?? null,
|
|
629
|
+
metadataJson: sc.metadataJson ?? null,
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (processed % BATCH_SIZE === 0)
|
|
634
|
+
closeBatch();
|
|
635
|
+
indexed++;
|
|
636
|
+
if (options.verbose) {
|
|
637
|
+
const symCount = extraction.definitions.length;
|
|
638
|
+
const refCount = extraction.references.length;
|
|
639
|
+
process.stdout.write(` ✓ ${file.relativePath} (${symCount} symbols, ${refCount} refs)\n`);
|
|
640
|
+
}
|
|
641
|
+
else if (!quiet) {
|
|
642
|
+
writeProgress(processed, total, file.relativePath);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
finally {
|
|
646
|
+
// Return this file's bytes to the prefetcher budget BEFORE launching
|
|
647
|
+
// the next slot, so the launch decision sees an up-to-date balance.
|
|
648
|
+
if (heldBytes > 0)
|
|
649
|
+
byteSem.release(heldBytes);
|
|
650
|
+
// Refill the sliding window now that one slot drained.
|
|
651
|
+
launchUpTo(ioConcurrency);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
// Close the last partial batch before kicking off post-processing
|
|
655
|
+
// (resolveImports / resolveEdges / pruneFilesNotIn all start their own
|
|
656
|
+
// transactions and would crash if one is already open).
|
|
657
|
+
closeBatch();
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
// Don't leave a transaction dangling — post-processing's BEGIN would
|
|
661
|
+
// throw and mask the original error.
|
|
662
|
+
rollbackBatch();
|
|
663
|
+
// Drain any in-flight prefetches so their byte budget is returned and
|
|
664
|
+
// open FDs / promises don't leak as unhandled rejections.
|
|
665
|
+
while (slots.length > 0) {
|
|
666
|
+
try {
|
|
667
|
+
const p = await slots.shift();
|
|
668
|
+
if (p.kind === 'ok')
|
|
669
|
+
byteSem.release(Math.max(p.size, 1));
|
|
670
|
+
}
|
|
671
|
+
catch { /* swallow */ }
|
|
672
|
+
}
|
|
673
|
+
throw err;
|
|
674
|
+
}
|
|
675
|
+
if (!options.verbose && !quiet)
|
|
676
|
+
process.stdout.write('\n');
|
|
677
|
+
return await this.finishIndex(absRoot, start, total, indexed, reusedFromCache, skipped, skippedTooLarge, parseErrors, touchedFileIds, { verbose: options.verbose, quiet: !!quiet });
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Post-parse pipeline shared by the serial and parallel branches: prune
|
|
681
|
+
* stale files, resolve imports/edges/routes/config-keys, synthesize test
|
|
682
|
+
* edges, refresh external dependencies, lazily recompute PageRank, and
|
|
683
|
+
* assemble the `IndexResult`.
|
|
684
|
+
*/
|
|
685
|
+
async finishIndex(absRoot, start, total, indexed, reusedFromCache, skipped, skippedTooLarge, parseErrors, touchedFileIds, opts) {
|
|
686
|
+
const { verbose, quiet } = opts;
|
|
687
|
+
// v9 Track-H: scan .proto files for gRPC service definitions BEFORE the
|
|
688
|
+
// stale-file prune and service-link resolver run. .proto files are not
|
|
689
|
+
// part of normal tree-sitter discovery, so they must be added to
|
|
690
|
+
// touchedFileIds here; otherwise cached re-indexes would prune and
|
|
691
|
+
// recreate proto rows every time.
|
|
692
|
+
try {
|
|
693
|
+
const protoScan = await (0, protoScanner_js_1.scanProtoFiles)(absRoot, this.store);
|
|
694
|
+
for (const fileId of protoScan.fileIds)
|
|
695
|
+
touchedFileIds.add(fileId);
|
|
696
|
+
}
|
|
697
|
+
catch (err) {
|
|
698
|
+
if (verbose)
|
|
699
|
+
process.stdout.write(` ⚠ proto scanner failed: ${err}\n`);
|
|
700
|
+
}
|
|
701
|
+
// Drop files that existed in a prior run but didn't show up this time
|
|
702
|
+
// (e.g. user added a new ignore rule, or files were removed from disk).
|
|
703
|
+
// FK cascades remove their symbols, edges, and file_imports too.
|
|
704
|
+
const prunedFiles = this.store.pruneFilesNotIn(touchedFileIds);
|
|
705
|
+
if (prunedFiles > 0 && !quiet) {
|
|
706
|
+
process.stdout.write(` Pruned ${prunedFiles.toLocaleString()} stale file(s) from prior run\n`);
|
|
707
|
+
}
|
|
708
|
+
// Post-processing passes
|
|
709
|
+
if (!quiet)
|
|
710
|
+
process.stdout.write(' Resolving imports...\n');
|
|
711
|
+
const resolvedImports = this.store.resolveImports();
|
|
712
|
+
if (!quiet)
|
|
713
|
+
process.stdout.write(' Resolving call edges...\n');
|
|
714
|
+
const resolution = this.store.resolveEdges();
|
|
715
|
+
// Track-C: link routes to handlers, config_keys to enclosing symbol,
|
|
716
|
+
// synthesize tests edges from test-file → non-test-file calls.
|
|
717
|
+
const routesResolved = this.store.resolveRouteHandlers();
|
|
718
|
+
const configKeysResolved = this.store.resolveConfigKeySymbols();
|
|
719
|
+
const testEdgesAdded = this.store.synthesizeTestEdges();
|
|
720
|
+
// v9 Track-H: scan k8s manifests + Docker Compose for service hostnames.
|
|
721
|
+
// Passed to the resolver as evidence — host_hint hits get a confidence
|
|
722
|
+
// boost and may be classified as `service_host` link matches.
|
|
723
|
+
let hostMap;
|
|
724
|
+
try {
|
|
725
|
+
hostMap = await (0, serviceHostScanner_js_1.scanServiceHosts)(absRoot);
|
|
726
|
+
}
|
|
727
|
+
catch (err) {
|
|
728
|
+
if (verbose)
|
|
729
|
+
process.stdout.write(` ⚠ service-host scanner failed: ${err}\n`);
|
|
730
|
+
}
|
|
731
|
+
// Track-G: deterministic service-link resolution. Runs every time, since
|
|
732
|
+
// any change in service_calls OR routes can shift link membership. The
|
|
733
|
+
// resolver itself wipes service_links before rebuilding so it's
|
|
734
|
+
// idempotent.
|
|
735
|
+
let serviceLinks = 0;
|
|
736
|
+
let serviceLinksByKind = {};
|
|
737
|
+
try {
|
|
738
|
+
const sr = (0, serviceLinks_js_1.resolveServiceLinks)(this.store, { hostMap });
|
|
739
|
+
serviceLinks = sr.linksInserted;
|
|
740
|
+
serviceLinksByKind = sr.byKind;
|
|
741
|
+
this.store.markServiceCallsBackfilled();
|
|
742
|
+
}
|
|
743
|
+
catch (err) {
|
|
744
|
+
if (verbose)
|
|
745
|
+
process.stdout.write(` ⚠ service-link resolution failed: ${err}\n`);
|
|
746
|
+
}
|
|
747
|
+
// External dependency extraction from manifests/lockfiles. This is cheap
|
|
748
|
+
// and idempotent — clear and re-insert every full pass so deletions are
|
|
749
|
+
// reflected. We pass absRoot so the extractor finds package.json /
|
|
750
|
+
// Cargo.toml / etc. at the repo root and walks down for monorepos.
|
|
751
|
+
try {
|
|
752
|
+
const { extractExternalDependencies } = await Promise.resolve().then(() => __importStar(require('./externaldeps.js')));
|
|
753
|
+
await extractExternalDependencies(absRoot, this.store);
|
|
754
|
+
}
|
|
755
|
+
catch (err) {
|
|
756
|
+
if (verbose) {
|
|
757
|
+
process.stdout.write(` ⚠ external dep extraction failed: ${err}\n`);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
// ── Lazy PageRank ───────────────────────────────────────────────────────────
|
|
761
|
+
// PageRank values are a pure function of the resolved edge graph. If nothing
|
|
762
|
+
// in that graph changed this run, every previously-stored rank is still
|
|
763
|
+
// correct and we can skip the O(iterations × edges) recomputation.
|
|
764
|
+
//
|
|
765
|
+
// "Nothing changed" requires ALL of the following:
|
|
766
|
+
// - no file was newly indexed (no new symbols/edges/imports inserted)
|
|
767
|
+
// - no stale file was pruned (would have cascaded FK deletes,
|
|
768
|
+
// potentially NULLing inbound edge `to_id`s)
|
|
769
|
+
// - resolveEdges() promoted zero NULL `to_id`s to a real id
|
|
770
|
+
// - resolveImports() promoted zero NULL `resolved_file_id`s
|
|
771
|
+
//
|
|
772
|
+
// If any of those is nonzero, the symbol set OR the resolved-edge graph
|
|
773
|
+
// could have shifted, so we recompute. This is the same correctness
|
|
774
|
+
// contract that the scale-test's "top-symbol id stability" check enforces
|
|
775
|
+
// — drift there means the predicate below missed a case.
|
|
776
|
+
//
|
|
777
|
+
// Why this is safe even on first run: when the DB is fresh, `indexed > 0`
|
|
778
|
+
// (everything is new), so the predicate fires and PageRank is computed.
|
|
779
|
+
const graphChanged = indexed > 0 ||
|
|
780
|
+
prunedFiles > 0 ||
|
|
781
|
+
resolution.sameFile + resolution.imported + resolution.global > 0 ||
|
|
782
|
+
resolvedImports > 0;
|
|
783
|
+
let pagerankRecomputed = false;
|
|
784
|
+
if (graphChanged) {
|
|
785
|
+
if (!quiet)
|
|
786
|
+
process.stdout.write(' Computing PageRank...\n');
|
|
787
|
+
const symbolIds = this.store.getAllSymbolIds();
|
|
788
|
+
const edges = this.store.getAllEdges();
|
|
789
|
+
const ranks = (0, pagerank_js_1.computePageRank)(symbolIds, edges);
|
|
790
|
+
this.store.updatePageRanks(ranks);
|
|
791
|
+
pagerankRecomputed = true;
|
|
792
|
+
}
|
|
793
|
+
else if (!quiet) {
|
|
794
|
+
process.stdout.write(' Skipping PageRank (graph unchanged)\n');
|
|
795
|
+
}
|
|
796
|
+
// ── Lazy module clustering ──────────────────────────────────────────────
|
|
797
|
+
// Same skip predicate as PageRank: the cluster is a function of the file
|
|
798
|
+
// graph + symbol PageRank, both of which stay stable when nothing changed.
|
|
799
|
+
// Always build when modules table is empty so the first opt-in to v6 runs
|
|
800
|
+
// it once, even when the index itself was a no-op.
|
|
801
|
+
let modulesRecomputed = false;
|
|
802
|
+
if (graphChanged || !this.store.hasModulesData()) {
|
|
803
|
+
if (!quiet)
|
|
804
|
+
process.stdout.write(' Clustering modules...\n');
|
|
805
|
+
try {
|
|
806
|
+
(0, modules_js_1.buildModules)(this.store);
|
|
807
|
+
modulesRecomputed = true;
|
|
808
|
+
}
|
|
809
|
+
catch (err) {
|
|
810
|
+
if (verbose)
|
|
811
|
+
process.stdout.write(` ⚠ module clustering failed: ${err}\n`);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
else if (!quiet) {
|
|
815
|
+
process.stdout.write(' Skipping module clustering (graph unchanged)\n');
|
|
816
|
+
}
|
|
817
|
+
// ── v10 boundary detection ──────────────────────────────────────────────
|
|
818
|
+
// Always run when graphChanged (new files / pruned files / new edges)
|
|
819
|
+
// OR when the boundaries table is empty (first opt-in after migrating
|
|
820
|
+
// an existing DB to v10).
|
|
821
|
+
let boundariesRecomputed = false;
|
|
822
|
+
try {
|
|
823
|
+
if (graphChanged || !this.store.hasBoundariesData()) {
|
|
824
|
+
if (!quiet)
|
|
825
|
+
process.stdout.write(' Detecting boundaries...\n');
|
|
826
|
+
const r = (0, boundaries_js_1.buildBoundaries)(absRoot, this.store);
|
|
827
|
+
this.store.replaceBoundaries(r.boundaries, r.edges);
|
|
828
|
+
boundariesRecomputed = true;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
catch (err) {
|
|
832
|
+
if (verbose)
|
|
833
|
+
process.stdout.write(` ⚠ boundary detection failed: ${err}\n`);
|
|
834
|
+
}
|
|
835
|
+
void boundariesRecomputed;
|
|
836
|
+
// ── Lazy shape-hash pass (Track-F structural SimHash) ──────────────────
|
|
837
|
+
// Re-indexed files delete their old symbols (no shape_hash on the new
|
|
838
|
+
// rows yet) so graphChanged covers normal updates. We ALSO run when any
|
|
839
|
+
// eligible symbol is missing a hash even on a cached/no-op run — this is
|
|
840
|
+
// the case after a pre-v7 → v7 migration where every existing file is
|
|
841
|
+
// "cached" (content hash unchanged) but the new shape_hash column starts
|
|
842
|
+
// NULL on every row. Without this second predicate the backfill would
|
|
843
|
+
// never run and `seer_duplicates` would silently return nothing.
|
|
844
|
+
let shapeHashesAdded = 0;
|
|
845
|
+
const needsHashBackfill = this.store.hasMissingShapeHashes();
|
|
846
|
+
if (graphChanged || needsHashBackfill) {
|
|
847
|
+
if (!quiet) {
|
|
848
|
+
process.stdout.write(graphChanged
|
|
849
|
+
? ' Computing shape hashes...\n'
|
|
850
|
+
: ' Backfilling shape hashes...\n');
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
const r = (0, shapehash_js_1.buildShapeHashes)(this.store);
|
|
854
|
+
shapeHashesAdded = r.symbolsHashed;
|
|
855
|
+
}
|
|
856
|
+
catch (err) {
|
|
857
|
+
if (verbose)
|
|
858
|
+
process.stdout.write(` ⚠ shape-hash pass failed: ${err}\n`);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
else if (!quiet) {
|
|
862
|
+
process.stdout.write(' Skipping shape hashes (graph unchanged, no backfill needed)\n');
|
|
863
|
+
}
|
|
864
|
+
// v10 — rename/move continuity heuristics. Runs whenever shape hashes
|
|
865
|
+
// were computed; opt-in mode (includeAllSymbols=true) is reserved for
|
|
866
|
+
// the explicit `seer continuity` CLI. The default pass only attaches
|
|
867
|
+
// candidates to symbols whose recorded history is shallow (< 1 commit).
|
|
868
|
+
if (graphChanged && this.store.hasV10()) {
|
|
869
|
+
try {
|
|
870
|
+
(0, continuity_js_1.buildContinuity)(this.store, { includeAllSymbols: true });
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
if (verbose)
|
|
874
|
+
process.stdout.write(` ⚠ continuity pass failed: ${err}\n`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const stats = this.store.getStats();
|
|
878
|
+
const elapsedMs = Date.now() - start;
|
|
879
|
+
return {
|
|
880
|
+
filesDiscovered: total,
|
|
881
|
+
filesIndexed: indexed,
|
|
882
|
+
filesReusedFromCache: reusedFromCache,
|
|
883
|
+
filesSkipped: skipped,
|
|
884
|
+
filesSkippedTooLarge: skippedTooLarge,
|
|
885
|
+
filesParseError: parseErrors,
|
|
886
|
+
wasmResets: (0, index_js_1.wasmResetCount)() + (opts.workerWasmResets ?? 0),
|
|
887
|
+
symbols: stats.symbols,
|
|
888
|
+
edges: stats.edges,
|
|
889
|
+
// stats.resolvedEdges is the running DB total; resolution.{sameFile,
|
|
890
|
+
// imported, global} below reports only the *delta* — what this run
|
|
891
|
+
// newly resolved (mostly nonzero on first run, near-zero on a cached
|
|
892
|
+
// re-run where everything was already resolved).
|
|
893
|
+
resolvedEdges: stats.resolvedEdges,
|
|
894
|
+
resolvedImports,
|
|
895
|
+
edgeResolution: {
|
|
896
|
+
sameFile: resolution.sameFile,
|
|
897
|
+
imported: resolution.imported,
|
|
898
|
+
global: resolution.global,
|
|
899
|
+
},
|
|
900
|
+
pagerankRecomputed,
|
|
901
|
+
routesResolved,
|
|
902
|
+
configKeysResolved,
|
|
903
|
+
testEdgesAdded,
|
|
904
|
+
externalDependencies: stats.externalDependencies,
|
|
905
|
+
modules: stats.modules,
|
|
906
|
+
modulesRecomputed,
|
|
907
|
+
shapeHashesAdded,
|
|
908
|
+
serviceLinks,
|
|
909
|
+
serviceLinksByKind,
|
|
910
|
+
elapsedMs,
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
exports.Indexer = Indexer;
|
|
915
|
+
// ── Utilities ──────────────────────────────────────────────────────────────────
|
|
916
|
+
function sha256(content) {
|
|
917
|
+
return crypto_1.default.createHash('sha256').update(content, 'utf8').digest('hex').slice(0, 16);
|
|
918
|
+
}
|
|
919
|
+
function writeProgress(current, total, label) {
|
|
920
|
+
if (!process.stdout.isTTY)
|
|
921
|
+
return;
|
|
922
|
+
const width = 28;
|
|
923
|
+
const pct = total > 0 ? current / total : 0;
|
|
924
|
+
const filled = Math.round(pct * width);
|
|
925
|
+
const bar = '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
926
|
+
const pctStr = Math.round(pct * 100).toString().padStart(3);
|
|
927
|
+
const short = label.length > 35 ? '…' + label.slice(-34) : label.padEnd(35);
|
|
928
|
+
process.stdout.write(`\r [${bar}] ${pctStr}% (${current}/${total}) ${short}`);
|
|
929
|
+
}
|
|
930
|
+
//# sourceMappingURL=index.js.map
|