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,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* v10 — Monorepo package/service boundary detection.
|
|
3
|
+
*
|
|
4
|
+
* Source signals (in priority order — earlier sources win on overlap):
|
|
5
|
+
* 1. Nested manifest files:
|
|
6
|
+
* - package.json (npm/yarn/pnpm workspaces)
|
|
7
|
+
* - pyproject.toml
|
|
8
|
+
* - Cargo.toml
|
|
9
|
+
* - go.mod
|
|
10
|
+
* - composer.json
|
|
11
|
+
* 2. Workspace-declared globs:
|
|
12
|
+
* - package.json:workspaces
|
|
13
|
+
* - pnpm-workspace.yaml
|
|
14
|
+
* - turbo.json / nx.json pipelines
|
|
15
|
+
* - go.work
|
|
16
|
+
* - Cargo workspace members (parent Cargo.toml [workspace])
|
|
17
|
+
* 3. Convention fallback:
|
|
18
|
+
* - packages/<name>/
|
|
19
|
+
* - services/<name>/
|
|
20
|
+
* - apps/<name>/
|
|
21
|
+
* - libs/<name>/
|
|
22
|
+
*
|
|
23
|
+
* Each detected boundary owns a contiguous subtree of files. The TRUE root
|
|
24
|
+
* is the deepest manifest/glob root — so `packages/core/src/lib/foo.ts`
|
|
25
|
+
* belongs to `packages/core/` if a package.json sits there.
|
|
26
|
+
*
|
|
27
|
+
* Boundary `label` is derived from the manifest name (`@scope/pkg` → `pkg`,
|
|
28
|
+
* etc.) when present, else from the root_rel_path segment.
|
|
29
|
+
*
|
|
30
|
+
* Boundary dependencies come from cross-boundary call/import/service edges
|
|
31
|
+
* aggregated across resolved graphs. Strictly advisory — never gates anything.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import fs from 'fs';
|
|
35
|
+
import path from 'path';
|
|
36
|
+
import { Store } from '../db/store.js';
|
|
37
|
+
|
|
38
|
+
export interface BoundaryDef {
|
|
39
|
+
label: string;
|
|
40
|
+
kind: 'package' | 'service' | 'app' | 'lib' | 'workspace-root' | 'convention';
|
|
41
|
+
rootRelPath: string;
|
|
42
|
+
manifestPath: string | null;
|
|
43
|
+
ecosystem: string | null;
|
|
44
|
+
fileIds: number[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface BoundaryEdgeDef {
|
|
48
|
+
fromIndex: number;
|
|
49
|
+
toIndex: number;
|
|
50
|
+
kind: 'call' | 'import' | 'service';
|
|
51
|
+
weight: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface BoundaryBuildResult {
|
|
55
|
+
boundaries: BoundaryDef[];
|
|
56
|
+
edges: BoundaryEdgeDef[];
|
|
57
|
+
/** Files that didn't match any boundary. */
|
|
58
|
+
orphanFiles: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface ManifestHit {
|
|
62
|
+
relRoot: string;
|
|
63
|
+
manifestPath: string;
|
|
64
|
+
label: string;
|
|
65
|
+
kind: BoundaryDef['kind'];
|
|
66
|
+
ecosystem: string | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Detect boundaries by walking the workspace once for manifests + convention
|
|
71
|
+
* fallback, then assigning each indexed file to the deepest matching
|
|
72
|
+
* boundary root.
|
|
73
|
+
*/
|
|
74
|
+
export function buildBoundaries(workspace: string, store: Store): BoundaryBuildResult {
|
|
75
|
+
const absRoot = path.resolve(workspace);
|
|
76
|
+
|
|
77
|
+
// Discover manifest hits.
|
|
78
|
+
const hits = discoverManifests(absRoot);
|
|
79
|
+
// Also seed convention-based hits when no manifest matches a directory.
|
|
80
|
+
seedConventionRoots(absRoot, hits);
|
|
81
|
+
|
|
82
|
+
// Materialize hits as boundary defs. De-dup by relRoot — manifest wins
|
|
83
|
+
// over convention.
|
|
84
|
+
const byRel = new Map<string, ManifestHit>();
|
|
85
|
+
for (const h of hits) {
|
|
86
|
+
const prev = byRel.get(h.relRoot);
|
|
87
|
+
if (!prev || rank(h) > rank(prev)) byRel.set(h.relRoot, h);
|
|
88
|
+
}
|
|
89
|
+
const sortedHits = Array.from(byRel.values()).sort((a, b) =>
|
|
90
|
+
b.relRoot.length - a.relRoot.length || (a.relRoot < b.relRoot ? -1 : 1));
|
|
91
|
+
|
|
92
|
+
// Assign every indexed file to the deepest matching hit.
|
|
93
|
+
const files = store.listFiles();
|
|
94
|
+
const fileToHit = new Map<number, ManifestHit | null>();
|
|
95
|
+
for (const f of files) {
|
|
96
|
+
let assigned: ManifestHit | null = null;
|
|
97
|
+
const rel = normalizePath(f.relPath);
|
|
98
|
+
for (const h of sortedHits) {
|
|
99
|
+
const root = h.relRoot;
|
|
100
|
+
if (root === '' || root === '.') continue;
|
|
101
|
+
if (rel === root || rel.startsWith(root + '/')) {
|
|
102
|
+
assigned = h;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
fileToHit.set(f.id, assigned);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Build boundary list (only include hits that own at least one file).
|
|
110
|
+
const boundariesByRoot = new Map<string, BoundaryDef & { _index: number }>();
|
|
111
|
+
const definitions: BoundaryDef[] = [];
|
|
112
|
+
let nextIndex = 0;
|
|
113
|
+
for (const h of sortedHits) {
|
|
114
|
+
const def: BoundaryDef = {
|
|
115
|
+
label: h.label, kind: h.kind, rootRelPath: h.relRoot,
|
|
116
|
+
manifestPath: h.manifestPath || null, ecosystem: h.ecosystem,
|
|
117
|
+
fileIds: [],
|
|
118
|
+
};
|
|
119
|
+
boundariesByRoot.set(h.relRoot, { ...def, _index: nextIndex });
|
|
120
|
+
nextIndex++;
|
|
121
|
+
}
|
|
122
|
+
for (const [fileId, hit] of fileToHit) {
|
|
123
|
+
if (!hit) continue;
|
|
124
|
+
const b = boundariesByRoot.get(hit.relRoot);
|
|
125
|
+
if (!b) continue;
|
|
126
|
+
b.fileIds.push(fileId);
|
|
127
|
+
}
|
|
128
|
+
// Drop empty boundaries (e.g. convention `services/` parent with no files).
|
|
129
|
+
for (const b of boundariesByRoot.values()) {
|
|
130
|
+
if (b.fileIds.length === 0) continue;
|
|
131
|
+
definitions.push({
|
|
132
|
+
label: b.label, kind: b.kind, rootRelPath: b.rootRelPath,
|
|
133
|
+
manifestPath: b.manifestPath, ecosystem: b.ecosystem, fileIds: b.fileIds,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Re-index after dropping.
|
|
137
|
+
const indexByRoot = new Map<string, number>();
|
|
138
|
+
definitions.forEach((d, i) => indexByRoot.set(d.rootRelPath, i));
|
|
139
|
+
|
|
140
|
+
// Build boundary→boundary edges from the resolved file-call / file-import /
|
|
141
|
+
// service-link graphs.
|
|
142
|
+
const edges = aggregateBoundaryEdges(store, fileToHit, indexByRoot);
|
|
143
|
+
|
|
144
|
+
let orphan = 0;
|
|
145
|
+
for (const [_id, h] of fileToHit) if (!h) orphan++;
|
|
146
|
+
|
|
147
|
+
return { boundaries: definitions, edges, orphanFiles: orphan };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function rank(h: ManifestHit): number {
|
|
151
|
+
// Manifest > convention.
|
|
152
|
+
return h.kind === 'convention' ? 0 : 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function aggregateBoundaryEdges(
|
|
156
|
+
store: Store,
|
|
157
|
+
fileToHit: Map<number, ManifestHit | null>,
|
|
158
|
+
indexByRoot: Map<string, number>,
|
|
159
|
+
): BoundaryEdgeDef[] {
|
|
160
|
+
const buckets = new Map<string, BoundaryEdgeDef>();
|
|
161
|
+
const lookup = (fileId: number): number | null => {
|
|
162
|
+
const h = fileToHit.get(fileId) ?? null;
|
|
163
|
+
if (!h) return null;
|
|
164
|
+
const idx = indexByRoot.get(h.relRoot);
|
|
165
|
+
return idx == null ? null : idx;
|
|
166
|
+
};
|
|
167
|
+
const push = (from: number, to: number, kind: BoundaryEdgeDef['kind'], weight: number): void => {
|
|
168
|
+
if (from === to) return;
|
|
169
|
+
const key = `${from}|${to}|${kind}`;
|
|
170
|
+
const existing = buckets.get(key);
|
|
171
|
+
if (existing) existing.weight += weight;
|
|
172
|
+
else buckets.set(key, { fromIndex: from, toIndex: to, kind, weight });
|
|
173
|
+
};
|
|
174
|
+
for (const e of store.fileCallEdgeWeights()) {
|
|
175
|
+
const a = lookup(e.from); const b = lookup(e.to);
|
|
176
|
+
if (a != null && b != null) push(a, b, 'call', e.weight);
|
|
177
|
+
}
|
|
178
|
+
for (const e of store.fileImportEdgeWeights()) {
|
|
179
|
+
const a = lookup(e.from); const b = lookup(e.to);
|
|
180
|
+
if (a != null && b != null) push(a, b, 'import', e.weight);
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
for (const e of store.fileServiceLinkEdgeWeights()) {
|
|
184
|
+
const a = lookup(e.from); const b = lookup(e.to);
|
|
185
|
+
if (a != null && b != null) push(a, b, 'service', e.weight);
|
|
186
|
+
}
|
|
187
|
+
} catch { /* */ }
|
|
188
|
+
return Array.from(buckets.values()).sort((a, b) =>
|
|
189
|
+
a.fromIndex - b.fromIndex || a.toIndex - b.toIndex || (a.kind < b.kind ? -1 : 1));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Manifest discovery ──────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
function discoverManifests(absRoot: string): ManifestHit[] {
|
|
195
|
+
const hits: ManifestHit[] = [];
|
|
196
|
+
// Skip dirs that never own boundaries.
|
|
197
|
+
const SKIP = new Set([
|
|
198
|
+
'node_modules', '.git', 'dist', 'build', 'out', '.next', '.nuxt',
|
|
199
|
+
'target', 'obj', '.gradle', '__pycache__', '.cache', '.idea',
|
|
200
|
+
'.vs', '.seer',
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
function walk(absDir: string, relDir: string, depth: number): void {
|
|
204
|
+
if (depth > 6) return; // bound recursion — boundaries beyond ~6 levels are rare
|
|
205
|
+
let entries: string[];
|
|
206
|
+
try { entries = fs.readdirSync(absDir); }
|
|
207
|
+
catch { return; }
|
|
208
|
+
const fileSet = new Set(entries);
|
|
209
|
+
let claimed = false;
|
|
210
|
+
|
|
211
|
+
// package.json — may declare workspaces.
|
|
212
|
+
if (fileSet.has('package.json')) {
|
|
213
|
+
const manifestRel = relDir === '' ? 'package.json' : `${relDir}/package.json`;
|
|
214
|
+
try {
|
|
215
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(absDir, 'package.json'), 'utf8'));
|
|
216
|
+
const label = derivePackageName(pkg, relDir);
|
|
217
|
+
const isRoot = Array.isArray(pkg.workspaces) || (pkg.workspaces && Array.isArray(pkg.workspaces.packages));
|
|
218
|
+
hits.push({
|
|
219
|
+
relRoot: relDir,
|
|
220
|
+
manifestPath: manifestRel,
|
|
221
|
+
label,
|
|
222
|
+
kind: isRoot ? 'workspace-root' : 'package',
|
|
223
|
+
ecosystem: 'npm',
|
|
224
|
+
});
|
|
225
|
+
claimed = true;
|
|
226
|
+
} catch { /* */ }
|
|
227
|
+
}
|
|
228
|
+
// pyproject.toml
|
|
229
|
+
if (fileSet.has('pyproject.toml')) {
|
|
230
|
+
const manifestRel = relDir === '' ? 'pyproject.toml' : `${relDir}/pyproject.toml`;
|
|
231
|
+
hits.push({
|
|
232
|
+
relRoot: relDir,
|
|
233
|
+
manifestPath: manifestRel,
|
|
234
|
+
label: derivePyProjectLabel(absDir, relDir),
|
|
235
|
+
kind: 'package',
|
|
236
|
+
ecosystem: 'pypi',
|
|
237
|
+
});
|
|
238
|
+
claimed = true;
|
|
239
|
+
}
|
|
240
|
+
// Cargo.toml
|
|
241
|
+
if (fileSet.has('Cargo.toml')) {
|
|
242
|
+
const manifestRel = relDir === '' ? 'Cargo.toml' : `${relDir}/Cargo.toml`;
|
|
243
|
+
hits.push({
|
|
244
|
+
relRoot: relDir,
|
|
245
|
+
manifestPath: manifestRel,
|
|
246
|
+
label: deriveCargoLabel(absDir, relDir),
|
|
247
|
+
kind: 'package',
|
|
248
|
+
ecosystem: 'cargo',
|
|
249
|
+
});
|
|
250
|
+
claimed = true;
|
|
251
|
+
}
|
|
252
|
+
// go.mod
|
|
253
|
+
if (fileSet.has('go.mod')) {
|
|
254
|
+
const manifestRel = relDir === '' ? 'go.mod' : `${relDir}/go.mod`;
|
|
255
|
+
hits.push({
|
|
256
|
+
relRoot: relDir,
|
|
257
|
+
manifestPath: manifestRel,
|
|
258
|
+
label: deriveGoModuleLabel(absDir, relDir),
|
|
259
|
+
kind: 'package',
|
|
260
|
+
ecosystem: 'go',
|
|
261
|
+
});
|
|
262
|
+
claimed = true;
|
|
263
|
+
}
|
|
264
|
+
// composer.json
|
|
265
|
+
if (fileSet.has('composer.json')) {
|
|
266
|
+
const manifestRel = relDir === '' ? 'composer.json' : `${relDir}/composer.json`;
|
|
267
|
+
hits.push({
|
|
268
|
+
relRoot: relDir,
|
|
269
|
+
manifestPath: manifestRel,
|
|
270
|
+
label: path.basename(relDir || '.'),
|
|
271
|
+
kind: 'package',
|
|
272
|
+
ecosystem: 'composer',
|
|
273
|
+
});
|
|
274
|
+
claimed = true;
|
|
275
|
+
}
|
|
276
|
+
void claimed;
|
|
277
|
+
|
|
278
|
+
// Recurse into subdirectories. Always recurse if THIS level didn't
|
|
279
|
+
// declare a non-root package — that's how packages/<x> work — but DO
|
|
280
|
+
// recurse anyway through workspace-root or convention dirs.
|
|
281
|
+
for (const entry of entries) {
|
|
282
|
+
if (SKIP.has(entry)) continue;
|
|
283
|
+
const abs = path.join(absDir, entry);
|
|
284
|
+
let st: fs.Stats;
|
|
285
|
+
try { st = fs.statSync(abs); }
|
|
286
|
+
catch { continue; }
|
|
287
|
+
if (!st.isDirectory()) continue;
|
|
288
|
+
const sub = relDir === '' ? entry : `${relDir}/${entry}`;
|
|
289
|
+
walk(abs, sub, depth + 1);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
walk(absRoot, '', 0);
|
|
294
|
+
return hits;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function seedConventionRoots(absRoot: string, hits: ManifestHit[]): void {
|
|
298
|
+
// For each <conventionDir>/<sub>/ that exists and isn't already a manifest
|
|
299
|
+
// root, register a fallback boundary so services/* / packages/* still get
|
|
300
|
+
// surfaced even without a manifest.
|
|
301
|
+
const conventionDirs: Array<{ dir: string; kind: BoundaryDef['kind'] }> = [
|
|
302
|
+
{ dir: 'services', kind: 'service' },
|
|
303
|
+
{ dir: 'packages', kind: 'package' },
|
|
304
|
+
{ dir: 'apps', kind: 'app' },
|
|
305
|
+
{ dir: 'libs', kind: 'lib' },
|
|
306
|
+
];
|
|
307
|
+
const existingRoots = new Set(hits.map(h => h.relRoot));
|
|
308
|
+
for (const c of conventionDirs) {
|
|
309
|
+
const abs = path.join(absRoot, c.dir);
|
|
310
|
+
if (!fs.existsSync(abs)) continue;
|
|
311
|
+
let entries: string[];
|
|
312
|
+
try { entries = fs.readdirSync(abs); }
|
|
313
|
+
catch { continue; }
|
|
314
|
+
for (const e of entries) {
|
|
315
|
+
const subAbs = path.join(abs, e);
|
|
316
|
+
try {
|
|
317
|
+
if (!fs.statSync(subAbs).isDirectory()) continue;
|
|
318
|
+
} catch { continue; }
|
|
319
|
+
const rel = `${c.dir}/${e}`;
|
|
320
|
+
if (existingRoots.has(rel)) continue;
|
|
321
|
+
hits.push({
|
|
322
|
+
relRoot: rel,
|
|
323
|
+
manifestPath: '',
|
|
324
|
+
label: e,
|
|
325
|
+
kind: c.kind,
|
|
326
|
+
ecosystem: null,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function derivePackageName(pkg: any, relDir: string): string {
|
|
333
|
+
const name = (pkg && pkg.name && typeof pkg.name === 'string') ? pkg.name : null;
|
|
334
|
+
if (!name) return path.basename(relDir || '.');
|
|
335
|
+
// Strip @scope/
|
|
336
|
+
const m = /^@[^/]+\/(.+)$/.exec(name);
|
|
337
|
+
return m ? m[1] : name;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function derivePyProjectLabel(absDir: string, relDir: string): string {
|
|
341
|
+
try {
|
|
342
|
+
const text = fs.readFileSync(path.join(absDir, 'pyproject.toml'), 'utf8');
|
|
343
|
+
const m = /^\s*name\s*=\s*['"]([^'"]+)['"]/m.exec(text);
|
|
344
|
+
if (m) return m[1];
|
|
345
|
+
} catch { /* */ }
|
|
346
|
+
return path.basename(relDir || '.');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function deriveCargoLabel(absDir: string, relDir: string): string {
|
|
350
|
+
try {
|
|
351
|
+
const text = fs.readFileSync(path.join(absDir, 'Cargo.toml'), 'utf8');
|
|
352
|
+
// Capture [package].name; skip [workspace] sections.
|
|
353
|
+
const pkgSection = /^\s*\[package\][\s\S]*?(?=^\s*\[)/m.exec(text)?.[0] ?? text;
|
|
354
|
+
const m = /^\s*name\s*=\s*['"]([^'"]+)['"]/m.exec(pkgSection);
|
|
355
|
+
if (m) return m[1];
|
|
356
|
+
} catch { /* */ }
|
|
357
|
+
return path.basename(relDir || '.');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function deriveGoModuleLabel(absDir: string, relDir: string): string {
|
|
361
|
+
try {
|
|
362
|
+
const text = fs.readFileSync(path.join(absDir, 'go.mod'), 'utf8');
|
|
363
|
+
const m = /^\s*module\s+([^\s]+)/m.exec(text);
|
|
364
|
+
if (m) {
|
|
365
|
+
// Take the last path segment.
|
|
366
|
+
return path.basename(m[1]);
|
|
367
|
+
}
|
|
368
|
+
} catch { /* */ }
|
|
369
|
+
return path.basename(relDir || '.');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function normalizePath(p: string): string {
|
|
373
|
+
return p.replace(/\\/g, '/');
|
|
374
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Store } from '../db/store.js';
|
|
2
|
+
import { collectFileChurn, gitHeadSha, gitRemoteUrl, isGitRepo } from './git.js';
|
|
3
|
+
|
|
4
|
+
export interface ChurnResult {
|
|
5
|
+
filesAnalyzed: number;
|
|
6
|
+
filesWithChurn: number;
|
|
7
|
+
headSha: string | null;
|
|
8
|
+
elapsedMs: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* File-level git churn pass — populates `file_churn` for every indexed file.
|
|
13
|
+
* Independent of the symbol-history pass: file churn is cheap (~one git log
|
|
14
|
+
* over the whole repo) and useful on its own as a "what's risky to edit"
|
|
15
|
+
* signal even before the per-symbol history pass.
|
|
16
|
+
*/
|
|
17
|
+
export async function collectChurn(repoRoot: string, store: Store): Promise<ChurnResult> {
|
|
18
|
+
const start = Date.now();
|
|
19
|
+
if (!isGitRepo(repoRoot)) {
|
|
20
|
+
return { filesAnalyzed: 0, filesWithChurn: 0, headSha: null, elapsedMs: Date.now() - start };
|
|
21
|
+
}
|
|
22
|
+
const files = store.listFiles();
|
|
23
|
+
if (files.length === 0) {
|
|
24
|
+
return { filesAnalyzed: 0, filesWithChurn: 0, headSha: gitHeadSha(repoRoot), elapsedMs: Date.now() - start };
|
|
25
|
+
}
|
|
26
|
+
const churn = await collectFileChurn(repoRoot, files.map(f => f.path));
|
|
27
|
+
let withChurn = 0;
|
|
28
|
+
// Normalize path comparison the same way collectFileChurn does internally.
|
|
29
|
+
const norm = (p: string): string => {
|
|
30
|
+
const n = p.replace(/\\/g, '/');
|
|
31
|
+
return process.platform === 'win32' ? n.toLowerCase() : n;
|
|
32
|
+
};
|
|
33
|
+
const churnByNorm = new Map<string, ReturnType<typeof churn.get>>();
|
|
34
|
+
for (const [k, v] of churn) churnByNorm.set(norm(k), v);
|
|
35
|
+
|
|
36
|
+
for (const f of files) {
|
|
37
|
+
const stats = churnByNorm.get(norm(f.path));
|
|
38
|
+
if (!stats || stats.commitCount === 0) continue;
|
|
39
|
+
store.upsertFileChurn(
|
|
40
|
+
f.id, stats.commitCount, stats.lastCommitSha, stats.lastCommitAt,
|
|
41
|
+
stats.topAuthor, stats.secondAuthor,
|
|
42
|
+
);
|
|
43
|
+
withChurn++;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Stamp git_index_state with HEAD so detect_changes can compute "since
|
|
47
|
+
// last index" diffs.
|
|
48
|
+
const head = gitHeadSha(repoRoot);
|
|
49
|
+
const remote = gitRemoteUrl(repoRoot);
|
|
50
|
+
store.setGitIndexState(repoRoot, head, remote);
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
filesAnalyzed: files.length,
|
|
54
|
+
filesWithChurn: withChurn,
|
|
55
|
+
headSha: head,
|
|
56
|
+
elapsedMs: Date.now() - start,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { FileClassification, FileRole } from '../db/store.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Classify a discovered file path as project-owned, vendored, generated, or
|
|
5
|
+
* test. The result is stored on `files.role` and used to keep PageRank,
|
|
6
|
+
* top-symbol queries, and search defaults focused on first-party code while
|
|
7
|
+
* still letting users opt into vendored/generated results explicitly.
|
|
8
|
+
*
|
|
9
|
+
* Conservative defaults — when in doubt, return 'project' so we don't quietly
|
|
10
|
+
* hide first-party code. The patterns here are kept tight on purpose; broader
|
|
11
|
+
* heuristics (e.g. "anything inside a directory called `lib`") would cause
|
|
12
|
+
* too many false positives across the polyglot scale-test corpus.
|
|
13
|
+
*
|
|
14
|
+
* The function works purely on the relative path string; no filesystem
|
|
15
|
+
* access. That keeps it cheap to call once per discovered file in the
|
|
16
|
+
* indexer hot loop.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// Vendored dependency directories at any nesting depth. The discovery layer
|
|
20
|
+
// already excludes most of these from the glob, but a few make it through
|
|
21
|
+
// (e.g. project-local copies of small utilities placed under `lib/vendor/`).
|
|
22
|
+
// We still tag those that get past discovery so the stored classification is
|
|
23
|
+
// useful even on unusual repo layouts.
|
|
24
|
+
const VENDOR_DIR_PATTERNS = [
|
|
25
|
+
/(^|[\\/])vendor[\\/]/i,
|
|
26
|
+
/(^|[\\/])vendored[\\/]/i,
|
|
27
|
+
/(^|[\\/])third[_-]?party[\\/]/i,
|
|
28
|
+
/(^|[\\/])external[\\/]/i,
|
|
29
|
+
/(^|[\\/])node_modules[\\/]/i,
|
|
30
|
+
/(^|[\\/])bower_components[\\/]/i,
|
|
31
|
+
// Common engine-specific vendored locations.
|
|
32
|
+
/(^|[\\/])Engine[\\/]Source[\\/]ThirdParty[\\/]/i,
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// Generated boilerplate. Filename patterns covering protobuf, Unreal header
|
|
36
|
+
// tool, gRPC, gqlgen, and a handful of common code-generators. We also tag
|
|
37
|
+
// files that live under a `generated/` directory since most projects put
|
|
38
|
+
// emitter output there.
|
|
39
|
+
const GENERATED_DIR_PATTERNS = [
|
|
40
|
+
/(^|[\\/])generated[\\/]/i,
|
|
41
|
+
/(^|[\\/])Generated[\\/]/, // Unreal Engine convention
|
|
42
|
+
/(^|[\\/])\.next[\\/]/,
|
|
43
|
+
/(^|[\\/])\.nuxt[\\/]/,
|
|
44
|
+
/(^|[\\/])__generated__[\\/]/,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const GENERATED_FILENAME_PATTERNS = [
|
|
48
|
+
/\.generated\.[a-z]+$/i,
|
|
49
|
+
/\.gen\.[a-z]+$/i,
|
|
50
|
+
/\.pb\.[a-z]+$/i, // protobuf .pb.go / .pb.h / .pb.ts
|
|
51
|
+
/\.pb\.go$/,
|
|
52
|
+
/_pb\.[a-z]+$/i, // gqlgen / Python grpc style
|
|
53
|
+
/\.min\.(js|css)$/i,
|
|
54
|
+
/\.bundle\.js$/i,
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
// Test directories — exposed for completeness even though we don't yet use
|
|
58
|
+
// the 'test' role to filter anywhere. Future work: surface test files in
|
|
59
|
+
// `seer_behavior` as a behavioral contract.
|
|
60
|
+
const TEST_DIR_PATTERNS = [
|
|
61
|
+
/(^|[\\/])tests?[\\/]/i,
|
|
62
|
+
/(^|[\\/])__tests__[\\/]/,
|
|
63
|
+
/(^|[\\/])spec[\\/]/i,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const TEST_FILENAME_PATTERNS = [
|
|
67
|
+
/\.test\.[a-z]+$/i,
|
|
68
|
+
/\.spec\.[a-z]+$/i,
|
|
69
|
+
/_test\.[a-z]+$/i, // Go convention: foo_test.go
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Compute classification flags for a discovered file. The role precedence is
|
|
74
|
+
* vendor → generated → test → project. Vendor wins over generated because a
|
|
75
|
+
* generated file inside a vendor tree is still vendored code we don't own;
|
|
76
|
+
* the `is_generated` flag remains true so users can still query for it.
|
|
77
|
+
*/
|
|
78
|
+
export function classifyFile(relativePath: string): FileClassification {
|
|
79
|
+
const isVendor = VENDOR_DIR_PATTERNS.some(p => p.test(relativePath)) ? 1 : 0;
|
|
80
|
+
const isGenerated = (
|
|
81
|
+
GENERATED_DIR_PATTERNS.some(p => p.test(relativePath)) ||
|
|
82
|
+
GENERATED_FILENAME_PATTERNS.some(p => p.test(relativePath))
|
|
83
|
+
) ? 1 : 0;
|
|
84
|
+
const isTest = (
|
|
85
|
+
TEST_DIR_PATTERNS.some(p => p.test(relativePath)) ||
|
|
86
|
+
TEST_FILENAME_PATTERNS.some(p => p.test(relativePath))
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
let role: FileRole;
|
|
90
|
+
if (isVendor) role = 'vendor';
|
|
91
|
+
else if (isGenerated) role = 'generated';
|
|
92
|
+
else if (isTest) role = 'test';
|
|
93
|
+
else role = 'project';
|
|
94
|
+
|
|
95
|
+
return { role, isVendor: isVendor as 0 | 1, isGenerated: isGenerated as 0 | 1 };
|
|
96
|
+
}
|