@topogram/cli 0.3.76 → 0.3.78

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.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/src/adoption/plan/index.js +12 -1
  3. package/src/cli/commands/import/workspace.js +1 -0
  4. package/src/cli.js +3 -3
  5. package/src/import/core/runner/candidates.js +2 -0
  6. package/src/import/core/runner/reports.js +12 -5
  7. package/src/import/core/runner/tracks.js +1 -1
  8. package/src/import/core/shared/files.js +76 -1
  9. package/src/import/core/shared/next-app.js +2 -2
  10. package/src/import/core/shared/ui-routes.js +106 -0
  11. package/src/import/core/shared.js +8 -1
  12. package/src/import/enrichers/django-rest.js +4 -4
  13. package/src/import/enrichers/rails-controllers.js +3 -3
  14. package/src/import/enrichers/rails-models.js +3 -3
  15. package/src/import/extractors/api/aspnet-core.js +5 -5
  16. package/src/import/extractors/api/django-routes.js +5 -5
  17. package/src/import/extractors/api/express.js +4 -4
  18. package/src/import/extractors/api/fastify.js +7 -7
  19. package/src/import/extractors/api/flutter-dio.js +4 -4
  20. package/src/import/extractors/api/generic-route-fallback.js +3 -2
  21. package/src/import/extractors/api/graphql-code-first.js +3 -3
  22. package/src/import/extractors/api/graphql-sdl.js +5 -5
  23. package/src/import/extractors/api/jaxrs.js +3 -3
  24. package/src/import/extractors/api/micronaut.js +3 -3
  25. package/src/import/extractors/api/openapi-code.js +4 -4
  26. package/src/import/extractors/api/openapi.js +3 -3
  27. package/src/import/extractors/api/rails-routes.js +3 -3
  28. package/src/import/extractors/api/react-native-repository.js +3 -3
  29. package/src/import/extractors/api/retrofit.js +3 -3
  30. package/src/import/extractors/api/spring-web.js +3 -3
  31. package/src/import/extractors/api/swift-webapi.js +3 -3
  32. package/src/import/extractors/api/trpc.js +4 -4
  33. package/src/import/extractors/cli/generic.js +5 -4
  34. package/src/import/extractors/db/django-models.js +4 -4
  35. package/src/import/extractors/db/dotnet-models.js +4 -4
  36. package/src/import/extractors/db/drizzle.js +21 -9
  37. package/src/import/extractors/db/ef-core.js +5 -5
  38. package/src/import/extractors/db/flutter-entities.js +3 -3
  39. package/src/import/extractors/db/jpa.js +3 -3
  40. package/src/import/extractors/db/liquibase.js +3 -3
  41. package/src/import/extractors/db/maintained-seams.js +27 -4
  42. package/src/import/extractors/db/mybatis-xml.js +4 -4
  43. package/src/import/extractors/db/prisma.js +10 -3
  44. package/src/import/extractors/db/rails-schema.js +3 -3
  45. package/src/import/extractors/db/react-native-entities.js +3 -3
  46. package/src/import/extractors/db/room.js +5 -5
  47. package/src/import/extractors/db/snapshot.js +3 -3
  48. package/src/import/extractors/db/sql.js +3 -3
  49. package/src/import/extractors/db/swiftdata.js +3 -3
  50. package/src/import/extractors/ui/android-compose.js +4 -4
  51. package/src/import/extractors/ui/backend-only.js +3 -3
  52. package/src/import/extractors/ui/blazor.js +3 -3
  53. package/src/import/extractors/ui/flutter-screens.js +3 -3
  54. package/src/import/extractors/ui/maui-xaml.js +4 -4
  55. package/src/import/extractors/ui/next-app-router.js +26 -5
  56. package/src/import/extractors/ui/next-pages-router.js +34 -10
  57. package/src/import/extractors/ui/razor-pages.js +3 -3
  58. package/src/import/extractors/ui/react-native-screens.js +4 -4
  59. package/src/import/extractors/ui/react-router.js +34 -6
  60. package/src/import/extractors/ui/sveltekit.js +34 -6
  61. package/src/import/extractors/ui/swiftui.js +4 -3
  62. package/src/import/extractors/ui/uikit.js +3 -3
  63. package/src/workflows/reconcile/bundle-core/index.js +20 -1
  64. package/src/workflows/reconcile/candidate-model.js +13 -1
  65. package/src/workflows/reconcile/impacts/adoption-plan.js +13 -0
  66. package/src/workflows/reconcile/renderers.js +33 -0
  67. package/src/workflows/reconcile/workflow.js +2 -1
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
 
3
3
  import {
4
4
  dedupeCandidateRecords,
5
- findImportFiles,
5
+ findPrimaryImportFiles,
6
6
  inferApiCapabilityIdFromOperation,
7
7
  inferApiEntityIdFromPath,
8
8
  makeCandidateRecord,
@@ -263,8 +263,8 @@ export const fastifyExtractor = {
263
263
  id: "api.fastify",
264
264
  track: "api",
265
265
  detect(context) {
266
- const routeFiles = findImportFiles(context.paths, (filePath) => /src\/routes\/api\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
267
- const packageJsonFiles = findImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath));
266
+ const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
267
+ const packageJsonFiles = findPrimaryImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath));
268
268
  const hasFastifyDependency = packageJsonFiles.some((filePath) => /"fastify"\s*:/.test(context.helpers.readTextIfExists(filePath) || ""));
269
269
  return {
270
270
  score: routeFiles.length > 0 && hasFastifyDependency ? 86 : 0,
@@ -272,16 +272,16 @@ export const fastifyExtractor = {
272
272
  };
273
273
  },
274
274
  extract(context) {
275
- const apiRoutesRoot = findImportFiles(context.paths, (filePath) => /src\/routes\/api\/index\.(ts|js|mjs|cjs)$/i.test(filePath))[0]
276
- ? path.join(path.dirname(findImportFiles(context.paths, (filePath) => /src\/routes\/api\/index\.(ts|js|mjs|cjs)$/i.test(filePath))[0]))
275
+ const apiRoutesRoot = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/index\.(ts|js|mjs|cjs)$/i.test(filePath))[0]
276
+ ? path.join(path.dirname(findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/index\.(ts|js|mjs|cjs)$/i.test(filePath))[0]))
277
277
  : null;
278
278
  if (!apiRoutesRoot) {
279
279
  return { findings: [], candidates: { capabilities: [], routes: [], stacks: [] } };
280
280
  }
281
281
 
282
- const routeFiles = findImportFiles(context.paths, (filePath) => /src\/routes\/api\/.+\.(ts|js|mjs|cjs)$/i.test(filePath))
282
+ const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/routes\/api\/.+\.(ts|js|mjs|cjs)$/i.test(filePath))
283
283
  .filter((filePath) => !/\/autohooks\.(ts|js|mjs|cjs)$/i.test(filePath));
284
- const schemaFiles = findImportFiles(context.paths, (filePath) => /src\/schemas\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
284
+ const schemaFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/schemas\/.+\.(ts|js|mjs|cjs)$/i.test(filePath));
285
285
  const namedSchemas = parseNamedTypeboxSchemas(schemaFiles, context.helpers.readTextIfExists);
286
286
 
287
287
  const routes = [];
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  makeCandidateRecord,
6
6
  pluralizeCandidateTerm,
7
7
  relativeTo,
@@ -31,7 +31,7 @@ function capabilityIdFor(featureStem, methodName, httpMethod) {
31
31
  }
32
32
 
33
33
  function extractApiConfigPaths(context) {
34
- const configFile = findImportFiles(context.paths, (filePath) => /\/lib\/common\/network\/api_config\.dart$/i.test(filePath))[0];
34
+ const configFile = findPrimaryImportFiles(context.paths, (filePath) => /\/lib\/common\/network\/api_config\.dart$/i.test(filePath))[0];
35
35
  const mapping = new Map();
36
36
  if (!configFile) return mapping;
37
37
  const text = context.helpers.readTextIfExists(configFile) || "";
@@ -87,7 +87,7 @@ export const flutterDioExtractor = {
87
87
  id: "api.flutter-dio",
88
88
  track: "api",
89
89
  detect(context) {
90
- const files = findImportFiles(
90
+ const files = findPrimaryImportFiles(
91
91
  context.paths,
92
92
  (filePath) => /\/lib\/features\/.+\/data\/datasources\/.+_remote_data_source\.dart$/i.test(filePath)
93
93
  );
@@ -98,7 +98,7 @@ export const flutterDioExtractor = {
98
98
  };
99
99
  },
100
100
  extract(context) {
101
- const files = findImportFiles(
101
+ const files = findPrimaryImportFiles(
102
102
  context.paths,
103
103
  (filePath) => /\/lib\/features\/.+\/data\/datasources\/.+_remote_data_source\.dart$/i.test(filePath)
104
104
  );
@@ -1,4 +1,4 @@
1
- import { findImportFiles, inferRouteAuthHint, inferRouteCapabilityId, inferRouteQueryParams, makeCandidateRecord, normalizeOpenApiPath, relativeTo } from "../../core/shared.js";
1
+ import { findPrimaryImportFiles, inferRouteAuthHint, inferRouteCapabilityId, inferRouteQueryParams, isPrimaryImportSource, makeCandidateRecord, normalizeOpenApiPath, relativeTo } from "../../core/shared.js";
2
2
 
3
3
  function extractHandlerContext(text, handlerName) {
4
4
  if (!handlerName) return "";
@@ -15,10 +15,11 @@ function extractHandlerContext(text, handlerName) {
15
15
 
16
16
  function inferServerRoutes(context) {
17
17
  const routes = [];
18
- const routeFiles = findImportFiles(
18
+ const routeFiles = findPrimaryImportFiles(
19
19
  context.paths,
20
20
  (filePath) =>
21
21
  /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath) &&
22
+ isPrimaryImportSource(context.paths, filePath) &&
22
23
  /(server|api|routes|src)/i.test(filePath)
23
24
  );
24
25
  for (const filePath of routeFiles) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  idHintify,
6
6
  makeCandidateRecord,
7
7
  pluralizeCandidateTerm,
@@ -464,7 +464,7 @@ export const graphQlCodeFirstExtractor = {
464
464
  id: "api.graphql-code-first",
465
465
  track: "api",
466
466
  detect(context) {
467
- const files = findImportFiles(context.paths, (filePath) => /(\/src\/.+|\/pages\/api\/.+)\.(ts|tsx|js|jsx)$/i.test(filePath));
467
+ const files = findPrimaryImportFiles(context.paths, (filePath) => /(\/src\/.+|\/pages\/api\/.+)\.(ts|tsx|js|jsx)$/i.test(filePath));
468
468
  const hasNestResolvers = files.some((filePath) => {
469
469
  const text = readTextIfExists(filePath) || "";
470
470
  return /@nestjs\/graphql/.test(text) && /@(Query|Mutation)\s*\(/.test(text);
@@ -489,7 +489,7 @@ export const graphQlCodeFirstExtractor = {
489
489
  };
490
490
  },
491
491
  extract(context) {
492
- const files = findImportFiles(context.paths, (filePath) => /(\/src\/.+|\/pages\/api\/.+)\.(ts|tsx|js|jsx)$/i.test(filePath)).filter((filePath) => !/\.test\./i.test(filePath));
492
+ const files = findPrimaryImportFiles(context.paths, (filePath) => /(\/src\/.+|\/pages\/api\/.+)\.(ts|tsx|js|jsx)$/i.test(filePath)).filter((filePath) => !/\.test\./i.test(filePath));
493
493
  const nestTypes = parseNestTypes(files);
494
494
  const pothosTypes = parsePothosTypes(files);
495
495
  const nexusTypes = parseNexusTypes(files);
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  idHintify,
6
6
  makeCandidateRecord,
7
7
  pluralizeCandidateTerm,
@@ -174,7 +174,7 @@ function inferEndpointPath(context, graphqlFiles) {
174
174
  return endpointMatch[1];
175
175
  }
176
176
  }
177
- const packageJsonPath = findImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath))[0];
177
+ const packageJsonPath = findPrimaryImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath))[0];
178
178
  const packageText = packageJsonPath ? readTextIfExists(packageJsonPath) : null;
179
179
  if (packageText && /graphql-yoga|apollo-server|@apollo\/server/.test(packageText)) {
180
180
  return "/graphql";
@@ -183,7 +183,7 @@ function inferEndpointPath(context, graphqlFiles) {
183
183
  }
184
184
 
185
185
  function extractGraphqlSchemaSources(context) {
186
- const files = findImportFiles(
186
+ const files = findPrimaryImportFiles(
187
187
  context.paths,
188
188
  (filePath) =>
189
189
  /\/src\/.+\.(ts|tsx|js|jsx)$/i.test(filePath) ||
@@ -210,7 +210,7 @@ export const graphQlSdlExtractor = {
210
210
  detect(context) {
211
211
  const schemaSources = extractGraphqlSchemaSources(context);
212
212
  const hasOperations = schemaSources.some(({ schema }) => /\btype\s+Query\b|\btype\s+Mutation\b/.test(schema));
213
- const packageJsonPath = findImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath))[0];
213
+ const packageJsonPath = findPrimaryImportFiles(context.paths, (filePath) => /package\.json$/i.test(filePath))[0];
214
214
  const packageText = packageJsonPath ? readTextIfExists(packageJsonPath) : "";
215
215
  const hasGraphqlRuntime = /graphql-yoga|graphql|apollo-server|@apollo\/server/.test(packageText || "");
216
216
  return {
@@ -220,7 +220,7 @@ export const graphQlSdlExtractor = {
220
220
  },
221
221
  extract(context) {
222
222
  const schemaSources = extractGraphqlSchemaSources(context);
223
- const endpointPath = inferEndpointPath(context, findImportFiles(context.paths, (filePath) => /\/src\/.+\.(ts|tsx|js|jsx)$/i.test(filePath)));
223
+ const endpointPath = inferEndpointPath(context, findPrimaryImportFiles(context.paths, (filePath) => /\/src\/.+\.(ts|tsx|js|jsx)$/i.test(filePath)));
224
224
  const mergedSchema = schemaSources.map(({ schema }) => schema).join("\n\n");
225
225
  const blocks = parseGraphqlSchemaBlocks(mergedSchema);
226
226
  const inputTypes = new Map([...blocks.entries()].filter(([, block]) => block.kind === "input"));
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  dedupeCandidateRecords,
3
- findImportFiles,
3
+ findPrimaryImportFiles,
4
4
  inferApiEntityIdFromPath,
5
5
  inferRouteCapabilityId,
6
6
  makeCandidateRecord,
@@ -11,7 +11,7 @@ import {
11
11
  } from "../../core/shared.js";
12
12
 
13
13
  function buildJavaFileIndex(paths) {
14
- const files = findImportFiles(paths, (filePath) => /\.java$/i.test(filePath));
14
+ const files = findPrimaryImportFiles(paths, (filePath) => /\.java$/i.test(filePath));
15
15
  return files.map((filePath) => ({
16
16
  filePath,
17
17
  relativePath: relativeTo(paths.repoRoot, filePath),
@@ -253,7 +253,7 @@ export const jaxRsExtractor = {
253
253
  id: "api.jaxrs",
254
254
  track: "api",
255
255
  detect(context) {
256
- const javaFiles = findImportFiles(context.paths, (filePath) => /\.java$/i.test(filePath));
256
+ const javaFiles = findPrimaryImportFiles(context.paths, (filePath) => /\.java$/i.test(filePath));
257
257
  const jaxrsCount = javaFiles.filter((filePath) => /@Path\(|@(GET|POST|PUT|PATCH|DELETE)\b/.test(readTextIfExists(filePath) || "")).length;
258
258
  return {
259
259
  score: jaxrsCount > 0 ? 92 : 0,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  dedupeCandidateRecords,
3
- findImportFiles,
3
+ findPrimaryImportFiles,
4
4
  inferApiEntityIdFromPath,
5
5
  inferRouteCapabilityId,
6
6
  makeCandidateRecord,
@@ -11,7 +11,7 @@ import {
11
11
  } from "../../core/shared.js";
12
12
 
13
13
  function buildJavaFileIndex(paths) {
14
- const files = findImportFiles(paths, (filePath) => /\.java$/i.test(filePath));
14
+ const files = findPrimaryImportFiles(paths, (filePath) => /\.java$/i.test(filePath));
15
15
  return files.map((filePath) => ({
16
16
  filePath,
17
17
  relativePath: relativeTo(paths.repoRoot, filePath),
@@ -163,7 +163,7 @@ export const micronautExtractor = {
163
163
  id: "api.micronaut",
164
164
  track: "api",
165
165
  detect(context) {
166
- const javaFiles = findImportFiles(context.paths, (filePath) => /\.java$/i.test(filePath));
166
+ const javaFiles = findPrimaryImportFiles(context.paths, (filePath) => /\.java$/i.test(filePath));
167
167
  const count = javaFiles.filter((filePath) => /io\.micronaut\.http\.annotation|@Controller\(|@Get\(|@Post\(|@Put\(|@Delete\(/.test(readTextIfExists(filePath) || "")).length;
168
168
  return {
169
169
  score: count > 0 ? 95 : 0,
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
 
3
3
  import {
4
4
  dedupeCandidateRecords,
5
- findImportFiles,
5
+ findPrimaryImportFiles,
6
6
  inferApiCapabilityIdFromOperation,
7
7
  inferApiEntityIdFromPath,
8
8
  makeCandidateRecord,
@@ -183,15 +183,15 @@ export const openApiCodeExtractor = {
183
183
  id: "api.openapi-code",
184
184
  track: "api",
185
185
  detect(context) {
186
- const openApiFiles = findImportFiles(context.paths, (filePath) => /src\/docs\/openapi\.(ts|js|mjs|cjs)$/i.test(filePath));
186
+ const openApiFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/docs\/openapi\.(ts|js|mjs|cjs)$/i.test(filePath));
187
187
  return {
188
188
  score: openApiFiles.length > 0 ? 92 : 0,
189
189
  reasons: openApiFiles.length > 0 ? ["Found code-generated OpenAPI source"] : []
190
190
  };
191
191
  },
192
192
  extract(context) {
193
- const openApiFiles = findImportFiles(context.paths, (filePath) => /src\/docs\/openapi\.(ts|js|mjs|cjs)$/i.test(filePath));
194
- const schemaFile = findImportFiles(context.paths, (filePath) => /src\/docs\/openapi-schemas\.(ts|js|mjs|cjs)$/i.test(filePath))[0];
193
+ const openApiFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/docs\/openapi\.(ts|js|mjs|cjs)$/i.test(filePath));
194
+ const schemaFile = findPrimaryImportFiles(context.paths, (filePath) => /src\/docs\/openapi-schemas\.(ts|js|mjs|cjs)$/i.test(filePath))[0];
195
195
  const schemaFields = schemaFile ? parseZodObjectSchemaFields(context.helpers.readTextIfExists(schemaFile) || "") : new Map();
196
196
  const findings = [];
197
197
  const candidates = { capabilities: [], routes: [], stacks: [] };
@@ -1,4 +1,4 @@
1
- import { findImportFiles, makeCandidateRecord, normalizeOpenApiPath, relativeTo, selectPreferredImportFiles, slugify, titleCase } from "../../core/shared.js";
1
+ import { findPrimaryImportFiles, isPrimaryImportSource, makeCandidateRecord, normalizeOpenApiPath, relativeTo, selectPreferredImportFiles, slugify, titleCase } from "../../core/shared.js";
2
2
 
3
3
  function openApiRefName(ref) {
4
4
  return typeof ref === "string" ? ref.split("/").pop() || null : null;
@@ -196,7 +196,7 @@ export const openApiExtractor = {
196
196
  detect(context) {
197
197
  const files = selectPreferredImportFiles(
198
198
  context.paths,
199
- findImportFiles(context.paths, (filePath) => /(openapi|swagger)\.(json|ya?ml)$/i.test(filePath)),
199
+ findPrimaryImportFiles(context.paths, (filePath) => /(openapi|swagger)\.(json|ya?ml)$/i.test(filePath) && isPrimaryImportSource(context.paths, filePath)),
200
200
  "openapi"
201
201
  );
202
202
  return {
@@ -207,7 +207,7 @@ export const openApiExtractor = {
207
207
  extract(context) {
208
208
  const openApiFiles = selectPreferredImportFiles(
209
209
  context.paths,
210
- findImportFiles(context.paths, (filePath) => /(openapi|swagger)\.(json|ya?ml)$/i.test(filePath)),
210
+ findPrimaryImportFiles(context.paths, (filePath) => /(openapi|swagger)\.(json|ya?ml)$/i.test(filePath) && isPrimaryImportSource(context.paths, filePath)),
211
211
  "openapi"
212
212
  );
213
213
  const findings = [];
@@ -2,7 +2,7 @@ import path from "node:path";
2
2
 
3
3
  import {
4
4
  dedupeCandidateRecords,
5
- findImportFiles,
5
+ findPrimaryImportFiles,
6
6
  inferApiEntityIdFromPath,
7
7
  inferRouteCapabilityId,
8
8
  makeCandidateRecord,
@@ -167,14 +167,14 @@ export const railsRoutesExtractor = {
167
167
  id: "api.rails-routes",
168
168
  track: "api",
169
169
  detect(context) {
170
- const routeFiles = findImportFiles(context.paths, (filePath) => /config\/routes\.rb$/i.test(filePath));
170
+ const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /config\/routes\.rb$/i.test(filePath));
171
171
  return {
172
172
  score: routeFiles.length > 0 ? 90 : 0,
173
173
  reasons: routeFiles.length > 0 ? ["Found Rails routes.rb"] : []
174
174
  };
175
175
  },
176
176
  extract(context) {
177
- const routeFiles = findImportFiles(context.paths, (filePath) => /config\/routes\.rb$/i.test(filePath));
177
+ const routeFiles = findPrimaryImportFiles(context.paths, (filePath) => /config\/routes\.rb$/i.test(filePath));
178
178
  const findings = [];
179
179
  const candidates = { capabilities: [], routes: [], stacks: [] };
180
180
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  makeCandidateRecord,
6
6
  pluralizeCandidateTerm,
7
7
  relativeTo,
@@ -82,7 +82,7 @@ export const reactNativeRepositoryExtractor = {
82
82
  id: "api.react-native-repository",
83
83
  track: "api",
84
84
  detect(context) {
85
- const files = findImportFiles(
85
+ const files = findPrimaryImportFiles(
86
86
  context.paths,
87
87
  (filePath) => /\/src\/.+\/infrastructure\/implementations\/.+Repository\.ts$/i.test(filePath)
88
88
  );
@@ -93,7 +93,7 @@ export const reactNativeRepositoryExtractor = {
93
93
  };
94
94
  },
95
95
  extract(context) {
96
- const files = findImportFiles(
96
+ const files = findPrimaryImportFiles(
97
97
  context.paths,
98
98
  (filePath) => /\/src\/.+\/infrastructure\/implementations\/.+Repository\.ts$/i.test(filePath)
99
99
  );
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  makeCandidateRecord,
6
6
  relativeTo,
7
7
  titleCase
@@ -66,7 +66,7 @@ export const retrofitExtractor = {
66
66
  id: "api.retrofit",
67
67
  track: "api",
68
68
  detect(context) {
69
- const serviceFiles = findImportFiles(context.paths, (filePath) => /Service\.kt$/i.test(filePath) || /retrofit\/.+\.kt$/i.test(filePath));
69
+ const serviceFiles = findPrimaryImportFiles(context.paths, (filePath) => /Service\.kt$/i.test(filePath) || /retrofit\/.+\.kt$/i.test(filePath));
70
70
  const score = serviceFiles.some((filePath) => /@GET|@POST|@PUT|@PATCH|@DELETE/.test(context.helpers.readTextIfExists(filePath) || "")) ? 87 : 0;
71
71
  return {
72
72
  score,
@@ -74,7 +74,7 @@ export const retrofitExtractor = {
74
74
  };
75
75
  },
76
76
  extract(context) {
77
- const serviceFiles = findImportFiles(context.paths, (filePath) => /Service\.kt$/i.test(filePath) || /retrofit\/.+\.kt$/i.test(filePath));
77
+ const serviceFiles = findPrimaryImportFiles(context.paths, (filePath) => /Service\.kt$/i.test(filePath) || /retrofit\/.+\.kt$/i.test(filePath));
78
78
  const capabilities = [];
79
79
  for (const filePath of serviceFiles) {
80
80
  const provenance = relativeTo(context.paths.repoRoot, filePath);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  dedupeCandidateRecords,
3
- findImportFiles,
3
+ findPrimaryImportFiles,
4
4
  inferApiEntityIdFromPath,
5
5
  inferRouteCapabilityId,
6
6
  makeCandidateRecord,
@@ -13,7 +13,7 @@ import {
13
13
  const JAVA_ANNOTATION_PATTERN = String.raw`@[\w.]+(?:\((?:[^()]|\([^)]*\))*\))?`;
14
14
 
15
15
  function buildJavaFileIndex(paths) {
16
- const files = findImportFiles(paths, (filePath) => /\.java$/i.test(filePath));
16
+ const files = findPrimaryImportFiles(paths, (filePath) => /\.java$/i.test(filePath));
17
17
  return files.map((filePath) => ({
18
18
  filePath,
19
19
  relativePath: relativeTo(paths.repoRoot, filePath),
@@ -318,7 +318,7 @@ export const springWebExtractor = {
318
318
  id: "api.spring-web",
319
319
  track: "api",
320
320
  detect(context) {
321
- const javaFiles = findImportFiles(context.paths, (filePath) => /\.java$/i.test(filePath));
321
+ const javaFiles = findPrimaryImportFiles(context.paths, (filePath) => /\.java$/i.test(filePath));
322
322
  const springCount = javaFiles.filter((filePath) => /@(RestController|Controller)|@(?:Get|Post|Put|Patch|Delete)Exchange|@(GetMapping|PostMapping|PutMapping|PatchMapping|DeleteMapping)|@RequestMapping/.test(readTextIfExists(filePath) || "")).length;
323
323
  return {
324
324
  score: springCount > 0 ? 90 : 0,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  idHintify,
6
6
  makeCandidateRecord,
7
7
  relativeTo,
@@ -78,7 +78,7 @@ export const swiftWebApiExtractor = {
78
78
  id: "api.swift-webapi",
79
79
  track: "api",
80
80
  detect(context) {
81
- const files = findImportFiles(context.paths, (filePath) => /WebRepository\.swift$/i.test(filePath) || /WebAPI\/.+\.swift$/i.test(filePath));
81
+ const files = findPrimaryImportFiles(context.paths, (filePath) => /WebRepository\.swift$/i.test(filePath) || /WebAPI\/.+\.swift$/i.test(filePath));
82
82
  const score = files.some((filePath) => /APICall|URLSession/.test(context.helpers.readTextIfExists(filePath) || "")) ? 85 : 0;
83
83
  return {
84
84
  score,
@@ -86,7 +86,7 @@ export const swiftWebApiExtractor = {
86
86
  };
87
87
  },
88
88
  extract(context) {
89
- const files = findImportFiles(context.paths, (filePath) => /WebAPI\/.+\.swift$/i.test(filePath))
89
+ const files = findPrimaryImportFiles(context.paths, (filePath) => /WebAPI\/.+\.swift$/i.test(filePath))
90
90
  .filter((filePath) => /struct\s+Real/.test(context.helpers.readTextIfExists(filePath) || ""));
91
91
  const capabilities = [];
92
92
  for (const filePath of files) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  dedupeCandidateRecords,
3
- findImportFiles,
3
+ findPrimaryImportFiles,
4
4
  inferApiEntityIdFromPath,
5
5
  makeCandidateRecord,
6
6
  pluralizeCandidateTerm,
@@ -156,15 +156,15 @@ export const trpcExtractor = {
156
156
  id: "api.trpc",
157
157
  track: "api",
158
158
  detect(context) {
159
- const routerFiles = findImportFiles(context.paths, (filePath) => /src\/server\/routers\/.+\.(ts|tsx|js|jsx)$/i.test(filePath));
160
- const trpcHandler = findImportFiles(context.paths, (filePath) => /src\/pages\/api\/trpc\/\[trpc\]\.(ts|tsx|js|jsx)$/i.test(filePath));
159
+ const routerFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/server\/routers\/.+\.(ts|tsx|js|jsx)$/i.test(filePath));
160
+ const trpcHandler = findPrimaryImportFiles(context.paths, (filePath) => /src\/pages\/api\/trpc\/\[trpc\]\.(ts|tsx|js|jsx)$/i.test(filePath));
161
161
  return {
162
162
  score: routerFiles.length > 0 && trpcHandler.length > 0 ? 88 : 0,
163
163
  reasons: routerFiles.length > 0 && trpcHandler.length > 0 ? ["Found tRPC router modules and Next.js tRPC handler"] : []
164
164
  };
165
165
  },
166
166
  extract(context) {
167
- const routerFiles = findImportFiles(context.paths, (filePath) => /src\/server\/routers\/.+\.(ts|tsx|js|jsx)$/i.test(filePath))
167
+ const routerFiles = findPrimaryImportFiles(context.paths, (filePath) => /src\/server\/routers\/.+\.(ts|tsx|js|jsx)$/i.test(filePath))
168
168
  .filter((filePath) => !/\/_app\.(ts|tsx|js|jsx)$/i.test(filePath) && !/\.test\./i.test(filePath));
169
169
  const procedures = routerFiles.flatMap((filePath) => parseRouterProcedures(filePath, context.helpers.readTextIfExists(filePath) || ""));
170
170
  const findings = [];
@@ -3,8 +3,9 @@
3
3
  import path from "node:path";
4
4
 
5
5
  import {
6
- findImportFiles,
6
+ findPrimaryImportFiles,
7
7
  idHintify,
8
+ isPrimaryImportSource,
8
9
  makeCandidateRecord,
9
10
  normalizeImportRelativePath,
10
11
  readJsonIfExists,
@@ -33,7 +34,7 @@ function normalizePath(value) {
33
34
  */
34
35
  function isAuthoritativeCliSource(paths, filePath) {
35
36
  const normalized = normalizePath(normalizeImportRelativePath(paths, filePath));
36
- return !NON_AUTHORITATIVE_CLI_PATH_PATTERN.test(normalized);
37
+ return isPrimaryImportSource(paths, filePath) && !NON_AUTHORITATIVE_CLI_PATH_PATTERN.test(normalized);
37
38
  }
38
39
 
39
40
  /**
@@ -250,9 +251,9 @@ function inspectPackageCliMetadata(context, packageFiles) {
250
251
  * @returns {{ packageFiles: string[], sourceFiles: string[], binNames: Set<string>, findings: any[], provenance: string[] }}
251
252
  */
252
253
  function discoverCliSources(context) {
253
- const packageFiles = findImportFiles(context.paths, (/** @type {string} */ filePath) => /package\.json$/i.test(filePath));
254
+ const packageFiles = findPrimaryImportFiles(context.paths, (/** @type {string} */ filePath) => /package\.json$/i.test(filePath));
254
255
  const packageMetadata = inspectPackageCliMetadata(context, packageFiles);
255
- const sourceFiles = findImportFiles(context.paths, (/** @type {string} */ filePath) => {
256
+ const sourceFiles = findPrimaryImportFiles(context.paths, (/** @type {string} */ filePath) => {
256
257
  const normalized = normalizePath(filePath);
257
258
  if (!isAuthoritativeCliSource(context.paths, filePath)) {
258
259
  return false;
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import {
4
4
  canonicalCandidateTerm,
5
5
  dedupeCandidateRecords,
6
- findImportFiles,
6
+ findPrimaryImportFiles,
7
7
  idHintify,
8
8
  makeCandidateRecord,
9
9
  relativeTo,
@@ -169,8 +169,8 @@ export const djangoModelsExtractor = {
169
169
  id: "db.django-models",
170
170
  track: "db",
171
171
  detect(context) {
172
- const modelFiles = findImportFiles(context.paths, (filePath) => /\/models\.py$/i.test(filePath));
173
- const manageFiles = findImportFiles(context.paths, (filePath) => /\/manage\.py$/i.test(filePath));
172
+ const modelFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/models\.py$/i.test(filePath));
173
+ const manageFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/manage\.py$/i.test(filePath));
174
174
  const score = modelFiles.length > 0 && manageFiles.length > 0 ? 91 : 0;
175
175
  return {
176
176
  score,
@@ -178,7 +178,7 @@ export const djangoModelsExtractor = {
178
178
  };
179
179
  },
180
180
  extract(context) {
181
- const modelFiles = findImportFiles(context.paths, (filePath) => /\/models\.py$/i.test(filePath));
181
+ const modelFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/models\.py$/i.test(filePath));
182
182
  const findings = [];
183
183
  const candidates = { entities: [], enums: [], relations: [], indexes: [] };
184
184
 
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  idHintify,
6
6
  makeCandidateRecord,
7
7
  relativeTo,
@@ -61,8 +61,8 @@ export const dotnetModelsExtractor = {
61
61
  id: "db.dotnet-models",
62
62
  track: "db",
63
63
  detect(context) {
64
- const modelFiles = findImportFiles(context.paths, (filePath) => /\/Models\/.+\.cs$/i.test(filePath));
65
- const csprojFiles = findImportFiles(context.paths, (filePath) => /\.csproj$/i.test(filePath));
64
+ const modelFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/Models\/.+\.cs$/i.test(filePath));
65
+ const csprojFiles = findPrimaryImportFiles(context.paths, (filePath) => /\.csproj$/i.test(filePath));
66
66
  const score = modelFiles.length > 0 && csprojFiles.length > 0 ? 78 : 0;
67
67
  return {
68
68
  score,
@@ -70,7 +70,7 @@ export const dotnetModelsExtractor = {
70
70
  };
71
71
  },
72
72
  extract(context) {
73
- const modelFiles = findImportFiles(context.paths, (filePath) => /\/Models\/.+\.cs$/i.test(filePath));
73
+ const modelFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/Models\/.+\.cs$/i.test(filePath));
74
74
  const findings = [];
75
75
  const entities = [];
76
76
  for (const filePath of modelFiles) {
@@ -3,8 +3,9 @@ import path from "node:path";
3
3
  import {
4
4
  canonicalCandidateTerm,
5
5
  dedupeCandidateRecords,
6
- findImportFiles,
6
+ findPrimaryImportFiles,
7
7
  idHintify,
8
+ isPrimaryImportSource,
8
9
  makeCandidateRecord,
9
10
  relativeTo,
10
11
  slugify,
@@ -167,7 +168,10 @@ function parseDrizzleTables(schemaText) {
167
168
  }
168
169
 
169
170
  function drizzleConfigFiles(context) {
170
- return findImportFiles(context.paths, (filePath) => /drizzle\.config\.(ts|js|mjs|cjs)$/i.test(path.basename(filePath)));
171
+ return findPrimaryImportFiles(context.paths, (filePath) =>
172
+ /drizzle\.config\.(ts|js|mjs|cjs)$/i.test(path.basename(filePath)) &&
173
+ isPrimaryImportSource(context.paths, filePath)
174
+ );
171
175
  }
172
176
 
173
177
  function configuredSchemaFiles(context, configFiles) {
@@ -176,7 +180,10 @@ function configuredSchemaFiles(context, configFiles) {
176
180
  const configText = context.helpers.readTextIfExists(configFile) || "";
177
181
  for (const match of configText.matchAll(/\bschema\s*:\s*["'`]([^"'`*]+)["'`]/g)) {
178
182
  const absoluteSchemaPath = path.resolve(path.dirname(configFile), match[1]);
179
- if (context.helpers.readTextIfExists(absoluteSchemaPath) !== null) {
183
+ if (
184
+ context.helpers.readTextIfExists(absoluteSchemaPath) !== null &&
185
+ isPrimaryImportSource(context.paths, absoluteSchemaPath)
186
+ ) {
180
187
  files.push(absoluteSchemaPath);
181
188
  }
182
189
  }
@@ -184,26 +191,31 @@ function configuredSchemaFiles(context, configFiles) {
184
191
  return files;
185
192
  }
186
193
 
194
+ function isDrizzleSchemaSource(context, filePath) {
195
+ const text = context.helpers.readTextIfExists(filePath) || "";
196
+ return /\b(?:pgTable|sqliteTable|mysqlTable)\s*\(/.test(text);
197
+ }
198
+
187
199
  function findDrizzleSchemaFiles(context) {
188
200
  const configFiles = drizzleConfigFiles(context);
189
- const conventionalSchemaFiles = findImportFiles(context.paths, (filePath) =>
190
- /(?:^|\/)(?:src\/db\/schema|src\/schema|db\/schema|schema)\.(ts|js|mjs|cjs)$/i.test(relativeTo(context.paths.workspaceRoot, filePath).replaceAll(path.sep, "/"))
201
+ const conventionalSchemaFiles = findPrimaryImportFiles(context.paths, (filePath) =>
202
+ /(?:^|\/)(?:src\/db\/schema|src\/schema|db\/schema|schema)\.(ts|js|mjs|cjs)$/i.test(relativeTo(context.paths.workspaceRoot, filePath).replaceAll(path.sep, "/")) &&
203
+ isPrimaryImportSource(context.paths, filePath)
191
204
  );
192
205
  return [...new Set([
193
206
  ...configuredSchemaFiles(context, configFiles),
194
207
  ...conventionalSchemaFiles
195
- ])].sort();
208
+ ])].filter((filePath) => isDrizzleSchemaSource(context, filePath)).sort();
196
209
  }
197
210
 
198
211
  export const drizzleExtractor = {
199
212
  id: "db.drizzle",
200
213
  track: "db",
201
214
  detect(context) {
202
- const hasConfig = drizzleConfigFiles(context).length > 0;
203
215
  const hasSchema = findDrizzleSchemaFiles(context).length > 0;
204
216
  return {
205
- score: hasConfig || hasSchema ? 95 : 0,
206
- reasons: hasConfig || hasSchema ? ["Found Drizzle config/schema source"] : []
217
+ score: hasSchema ? 95 : 0,
218
+ reasons: hasSchema ? ["Found Drizzle schema source"] : []
207
219
  };
208
220
  },
209
221
  extract(context) {
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  canonicalCandidateTerm,
3
3
  dedupeCandidateRecords,
4
- findImportFiles,
4
+ findPrimaryImportFiles,
5
5
  makeCandidateRecord,
6
6
  readTextIfExists,
7
7
  relativeTo,
@@ -155,8 +155,8 @@ export const efCoreExtractor = {
155
155
  id: "db.ef-core",
156
156
  track: "db",
157
157
  detect(context) {
158
- const csprojFiles = findImportFiles(context.paths, (filePath) => /\.csproj$/i.test(filePath));
159
- const dbContextFiles = findImportFiles(context.paths, (filePath) => /DbContext|Context\.cs$/i.test(filePath));
158
+ const csprojFiles = findPrimaryImportFiles(context.paths, (filePath) => /\.csproj$/i.test(filePath));
159
+ const dbContextFiles = findPrimaryImportFiles(context.paths, (filePath) => /DbContext|Context\.cs$/i.test(filePath));
160
160
  const score = csprojFiles.length > 0 && dbContextFiles.some((filePath) => /DbContext/.test(readTextIfExists(filePath) || "")) ? 89 : 0;
161
161
  return {
162
162
  score,
@@ -164,8 +164,8 @@ export const efCoreExtractor = {
164
164
  };
165
165
  },
166
166
  extract(context) {
167
- const dbContextFiles = findImportFiles(context.paths, (filePath) => /DbContext|Context\.cs$/i.test(filePath));
168
- const domainFiles = findImportFiles(context.paths, (filePath) => /\/Domain\/.+\.cs$/i.test(filePath));
167
+ const dbContextFiles = findPrimaryImportFiles(context.paths, (filePath) => /DbContext|Context\.cs$/i.test(filePath));
168
+ const domainFiles = findPrimaryImportFiles(context.paths, (filePath) => /\/Domain\/.+\.cs$/i.test(filePath));
169
169
  const findings = [];
170
170
  const candidates = { entities: [], enums: [], relations: [], indexes: [] };
171
171