@zivis/cli 0.1.0-alpha.4 → 0.1.0-alpha.40

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 (124) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +31 -0
  3. package/dist/commands/app/index.d.ts +0 -8
  4. package/dist/commands/app/index.js +4 -16
  5. package/dist/commands/auth/bind.d.ts +4 -0
  6. package/dist/commands/auth/bind.js +97 -0
  7. package/dist/commands/auth/index.js +20 -2
  8. package/dist/commands/auth/init.d.ts +1 -0
  9. package/dist/commands/auth/init.js +245 -73
  10. package/dist/commands/auth/login.d.ts +2 -0
  11. package/dist/commands/auth/login.js +136 -18
  12. package/dist/commands/auth/logout.js +19 -7
  13. package/dist/commands/auth/sessions.js +7 -4
  14. package/dist/commands/auth/status.js +33 -12
  15. package/dist/commands/auth/token.d.ts +4 -0
  16. package/dist/commands/auth/token.js +73 -0
  17. package/dist/commands/check/index.d.ts +2 -0
  18. package/dist/commands/check/index.js +463 -0
  19. package/dist/commands/credits/index.js +2 -3
  20. package/dist/commands/inspect/index.d.ts +2 -0
  21. package/dist/commands/inspect/index.js +136 -0
  22. package/dist/commands/mcp/index.js +81 -14
  23. package/dist/commands/open/index.d.ts +0 -8
  24. package/dist/commands/open/index.js +0 -11
  25. package/dist/commands/scan/index.js +3 -8
  26. package/dist/commands/target/index.js +0 -3
  27. package/dist/commands/tm/index.js +3 -10
  28. package/dist/index.js +13 -1
  29. package/dist/internal/binding-token-mint.d.ts +33 -0
  30. package/dist/internal/binding-token-mint.js +174 -0
  31. package/dist/internal/extractor/adapters/go/index.d.ts +0 -4
  32. package/dist/internal/extractor/adapters/go/index.js +0 -6
  33. package/dist/internal/extractor/adapters/go/manifest.d.ts +0 -17
  34. package/dist/internal/extractor/adapters/go/manifest.js +19 -25
  35. package/dist/internal/extractor/adapters/go/parser.d.ts +0 -4
  36. package/dist/internal/extractor/adapters/go/parser.js +0 -5
  37. package/dist/internal/extractor/adapters/go/scanner.d.ts +0 -20
  38. package/dist/internal/extractor/adapters/go/scanner.js +0 -43
  39. package/dist/internal/extractor/adapters/python/index.d.ts +0 -3
  40. package/dist/internal/extractor/adapters/python/index.js +0 -4
  41. package/dist/internal/extractor/adapters/python/manifest.d.ts +0 -8
  42. package/dist/internal/extractor/adapters/python/manifest.js +21 -18
  43. package/dist/internal/extractor/adapters/python/parser.d.ts +0 -3
  44. package/dist/internal/extractor/adapters/python/parser.js +0 -4
  45. package/dist/internal/extractor/adapters/python/scanner.d.ts +0 -18
  46. package/dist/internal/extractor/adapters/python/scanner.js +1 -54
  47. package/dist/internal/extractor/adapters/typescript/index.d.ts +0 -7
  48. package/dist/internal/extractor/adapters/typescript/index.js +1 -21
  49. package/dist/internal/extractor/adapters/typescript/manifest.d.ts +0 -14
  50. package/dist/internal/extractor/adapters/typescript/manifest.js +26 -17
  51. package/dist/internal/extractor/adapters/typescript/parser.d.ts +0 -8
  52. package/dist/internal/extractor/adapters/typescript/parser.js +0 -9
  53. package/dist/internal/extractor/adapters/typescript/scanner.d.ts +0 -24
  54. package/dist/internal/extractor/adapters/typescript/scanner.js +0 -43
  55. package/dist/internal/extractor/graph-builder.d.ts +10 -0
  56. package/dist/internal/extractor/graph-builder.js +245 -0
  57. package/dist/internal/extractor/graph-types.d.ts +89 -0
  58. package/dist/internal/extractor/graph-types.js +1 -0
  59. package/dist/internal/extractor/index.d.ts +0 -14
  60. package/dist/internal/extractor/index.js +0 -13
  61. package/dist/internal/extractor/types.d.ts +0 -45
  62. package/dist/internal/extractor/types.js +0 -27
  63. package/dist/internal/extractor/walker.d.ts +0 -18
  64. package/dist/internal/extractor/walker.js +0 -18
  65. package/dist/internal/github-linkage-prompt.d.ts +7 -0
  66. package/dist/internal/github-linkage-prompt.js +95 -0
  67. package/dist/internal/ide-setup.d.ts +16 -7
  68. package/dist/internal/ide-setup.js +320 -46
  69. package/dist/internal/org-picker.d.ts +11 -0
  70. package/dist/internal/org-picker.js +34 -0
  71. package/dist/internal/orgs.js +0 -1
  72. package/dist/internal/project-binding.d.ts +1 -0
  73. package/dist/internal/project-binding.js +14 -9
  74. package/dist/internal/target-auth/api-config.d.ts +0 -17
  75. package/dist/internal/target-auth/api-config.js +0 -18
  76. package/dist/internal/target-auth/config.d.ts +0 -12
  77. package/dist/internal/target-auth/config.js +0 -13
  78. package/dist/internal/target-auth/index.d.ts +0 -18
  79. package/dist/internal/target-auth/index.js +0 -19
  80. package/dist/internal/target-auth/keychain.d.ts +0 -18
  81. package/dist/internal/target-auth/keychain.js +3 -23
  82. package/dist/internal/target-auth/types.d.ts +0 -26
  83. package/dist/internal/target-auth/types.js +0 -10
  84. package/dist/internal/target-auth/workos.d.ts +0 -15
  85. package/dist/internal/target-auth/workos.js +9 -18
  86. package/package.json +19 -5
  87. package/dist/commands/app/index.js.map +0 -1
  88. package/dist/commands/auth/index.js.map +0 -1
  89. package/dist/commands/auth/init.js.map +0 -1
  90. package/dist/commands/auth/login.js.map +0 -1
  91. package/dist/commands/auth/logout.js.map +0 -1
  92. package/dist/commands/auth/sessions.js.map +0 -1
  93. package/dist/commands/auth/status.js.map +0 -1
  94. package/dist/commands/credits/index.js.map +0 -1
  95. package/dist/commands/mcp/index.js.map +0 -1
  96. package/dist/commands/open/index.js.map +0 -1
  97. package/dist/commands/scan/index.js.map +0 -1
  98. package/dist/commands/target/index.js.map +0 -1
  99. package/dist/commands/tm/index.js.map +0 -1
  100. package/dist/index.js.map +0 -1
  101. package/dist/internal/extractor/adapters/go/index.js.map +0 -1
  102. package/dist/internal/extractor/adapters/go/manifest.js.map +0 -1
  103. package/dist/internal/extractor/adapters/go/parser.js.map +0 -1
  104. package/dist/internal/extractor/adapters/go/scanner.js.map +0 -1
  105. package/dist/internal/extractor/adapters/python/index.js.map +0 -1
  106. package/dist/internal/extractor/adapters/python/manifest.js.map +0 -1
  107. package/dist/internal/extractor/adapters/python/parser.js.map +0 -1
  108. package/dist/internal/extractor/adapters/python/scanner.js.map +0 -1
  109. package/dist/internal/extractor/adapters/typescript/index.js.map +0 -1
  110. package/dist/internal/extractor/adapters/typescript/manifest.js.map +0 -1
  111. package/dist/internal/extractor/adapters/typescript/parser.js.map +0 -1
  112. package/dist/internal/extractor/adapters/typescript/scanner.js.map +0 -1
  113. package/dist/internal/extractor/index.js.map +0 -1
  114. package/dist/internal/extractor/types.js.map +0 -1
  115. package/dist/internal/extractor/walker.js.map +0 -1
  116. package/dist/internal/ide-setup.js.map +0 -1
  117. package/dist/internal/orgs.js.map +0 -1
  118. package/dist/internal/project-binding.js.map +0 -1
  119. package/dist/internal/target-auth/api-config.js.map +0 -1
  120. package/dist/internal/target-auth/config.js.map +0 -1
  121. package/dist/internal/target-auth/index.js.map +0 -1
  122. package/dist/internal/target-auth/keychain.js.map +0 -1
  123. package/dist/internal/target-auth/types.js.map +0 -1
  124. package/dist/internal/target-auth/workos.js.map +0 -1
@@ -1,10 +1,3 @@
1
- /**
2
- * TypeScript / JavaScript language adapter.
3
- *
4
- * Handles .ts/.tsx/.js/.jsx + package.json. Combines manifest analysis
5
- * (cheap, runs against package.json) with AST scanning (per source file)
6
- * to produce components, external services, and data stores.
7
- */
8
1
  import * as path from "node:path";
9
2
  import { readManifests, summarizeManifests, findExternalServices, findDataStores, detectFrameworkFromManifest, } from "./manifest.js";
10
3
  import { scanFile, aggregate } from "./scanner.js";
@@ -15,13 +8,10 @@ export const typescriptAdapter = {
15
8
  manifests: ["package.json"],
16
9
  async extract(input) {
17
10
  const { rootDir, sourceFiles, manifestFiles } = input;
18
- // Manifest pass — fast, runs entirely on package.json contents.
19
11
  const parsedManifests = await readManifests(rootDir, manifestFiles);
20
12
  const manifestSummaries = summarizeManifests(parsedManifests);
21
13
  const manifestExternalServices = findExternalServices(parsedManifests);
22
14
  const manifestDataStores = findDataStores(parsedManifests);
23
- // AST pass — only on source files. Cap at MAX_SOURCE_FILES per run
24
- // for the Phase J <60s budget on a 100k-LOC monorepo.
25
15
  const MAX_SOURCE_FILES = 5_000;
26
16
  const filesToScan = sourceFiles.slice(0, MAX_SOURCE_FILES);
27
17
  const scans = await Promise.all(filesToScan.map(async (abs) => {
@@ -33,10 +23,7 @@ export const typescriptAdapter = {
33
23
  }
34
24
  }));
35
25
  const agg = aggregate(scans);
36
- // Map scanner output → ExtractedComponent shape.
37
26
  const components = agg.components.map((c) => {
38
- // Best-effort framework inference: scanner picks up imports; manifest
39
- // picks up the dep. Either signal counts.
40
27
  const framework = c.framework ?? frameworkFromOwningManifest(c.sourceFile, parsedManifests);
41
28
  return {
42
29
  name: c.name,
@@ -47,13 +34,11 @@ export const typescriptAdapter = {
47
34
  evidence: [...c.routes, ...c.imports],
48
35
  };
49
36
  });
50
- // Augment manifest-derived ExternalServices with AST call sites.
51
37
  for (const svc of manifestExternalServices) {
52
38
  const sites = agg.aiSdkCallSites.get(svc.package);
53
39
  if (sites)
54
40
  svc.callSites.push(...sites);
55
41
  }
56
- // Augment manifest-derived DataStores with import-evidence from source.
57
42
  for (const store of manifestDataStores) {
58
43
  if (!store.client)
59
44
  continue;
@@ -69,16 +54,11 @@ export const typescriptAdapter = {
69
54
  };
70
55
  },
71
56
  };
72
- /**
73
- * Walk up from a source file's path to find the closest package.json
74
- * we parsed; return the framework hint from that manifest.
75
- */
76
57
  function frameworkFromOwningManifest(sourceFile, manifests) {
77
58
  const sourceDir = path.posix.dirname(sourceFile);
78
59
  const candidates = manifests
79
60
  .map((m) => ({ m, dir: path.posix.dirname(m.filePath) }))
80
61
  .filter(({ dir }) => sourceDir === dir || sourceDir.startsWith(dir + "/"))
81
- .sort((a, b) => b.dir.length - a.dir.length); // closest wins
62
+ .sort((a, b) => b.dir.length - a.dir.length);
82
63
  return candidates[0] ? detectFrameworkFromManifest(candidates[0].m) : undefined;
83
64
  }
84
- //# sourceMappingURL=index.js.map
@@ -1,11 +1,3 @@
1
- /**
2
- * TypeScript / JavaScript manifest analysis.
3
- *
4
- * Reads package.json files (root + workspaces) to classify dependencies
5
- * into the categories the threat-model graph cares about. Used both for
6
- * the manifests block of the output and as a hint for which AST queries
7
- * are worth running on which files.
8
- */
9
1
  import type { ExtractedExternalService, ExtractedDataStore, ManifestSummary } from "../../types.js";
10
2
  export interface PackageJson {
11
3
  name?: string;
@@ -28,14 +20,8 @@ export interface ParsedManifest {
28
20
  packageName?: string;
29
21
  deps: Record<string, string>;
30
22
  }
31
- /**
32
- * Read every package.json, return per-file dependency maps. Combines
33
- * dependencies + devDependencies (we want both — devDeps include things
34
- * like @types that signal use, plus dev-only auth packages).
35
- */
36
23
  export declare function readManifests(rootDir: string, manifestFiles: string[]): Promise<ParsedManifest[]>;
37
24
  export declare function summarizeManifests(parsed: ParsedManifest[]): ManifestSummary[];
38
25
  export declare function findExternalServices(parsed: ParsedManifest[]): ExtractedExternalService[];
39
26
  export declare function findDataStores(parsed: ParsedManifest[]): ExtractedDataStore[];
40
- /** Pick the most likely web framework for a manifest's owning package. */
41
27
  export declare function detectFrameworkFromManifest(m: ParsedManifest): string | undefined;
@@ -1,11 +1,3 @@
1
- /**
2
- * TypeScript / JavaScript manifest analysis.
3
- *
4
- * Reads package.json files (root + workspaces) to classify dependencies
5
- * into the categories the threat-model graph cares about. Used both for
6
- * the manifests block of the output and as a hint for which AST queries
7
- * are worth running on which files.
8
- */
9
1
  import * as fs from "node:fs/promises";
10
2
  import * as path from "node:path";
11
3
  export const WEB_FRAMEWORK_PACKAGES = {
@@ -15,9 +7,14 @@ export const WEB_FRAMEWORK_PACKAGES = {
15
7
  "@trpc/server": "trpc",
16
8
  hono: "hono",
17
9
  koa: "koa",
10
+ "@nestjs/core": "nestjs",
11
+ "@nestjs/common": "nestjs",
12
+ "@hapi/hapi": "hapi",
13
+ "express-graphql": "express",
14
+ "apollo-server-express": "express",
18
15
  };
19
16
  export const DATA_STORE_PACKAGES = {
20
- "@prisma/client": { kind: "postgres", client: "@prisma/client" }, // assumes postgres; could be MySQL/SQLite via schema
17
+ "@prisma/client": { kind: "postgres", client: "@prisma/client" },
21
18
  pg: { kind: "postgres", client: "pg" },
22
19
  postgres: { kind: "postgres", client: "postgres" },
23
20
  mongoose: { kind: "mongo", client: "mongoose" },
@@ -38,11 +35,6 @@ export const AI_SDK_PACKAGES = {
38
35
  "@langchain/core": "LangChain",
39
36
  llamaindex: "LlamaIndex",
40
37
  };
41
- /**
42
- * Read every package.json, return per-file dependency maps. Combines
43
- * dependencies + devDependencies (we want both — devDeps include things
44
- * like @types that signal use, plus dev-only auth packages).
45
- */
46
38
  export async function readManifests(rootDir, manifestFiles) {
47
39
  const out = [];
48
40
  for (const file of manifestFiles) {
@@ -63,7 +55,6 @@ export async function readManifests(rootDir, manifestFiles) {
63
55
  });
64
56
  }
65
57
  catch {
66
- // Skip broken JSON; not our job to validate the customer's manifests.
67
58
  }
68
59
  }
69
60
  return out;
@@ -98,6 +89,26 @@ export function findExternalServices(parsed) {
98
89
  callSites: [{ file: m.filePath, line: 1 }],
99
90
  });
100
91
  }
92
+ for (const [pkg, vendor] of Object.entries(WEB_FRAMEWORK_PACKAGES)) {
93
+ const ver = m.deps[pkg];
94
+ if (!ver)
95
+ continue;
96
+ const key = `web_framework::${pkg}`;
97
+ const existing = seen.get(key);
98
+ if (existing) {
99
+ existing.callSites.push({ file: m.filePath, line: 1 });
100
+ if (!existing.version)
101
+ existing.version = ver;
102
+ continue;
103
+ }
104
+ seen.set(key, {
105
+ name: vendor,
106
+ kind: "web_framework",
107
+ package: pkg,
108
+ version: ver,
109
+ callSites: [{ file: m.filePath, line: 1 }],
110
+ });
111
+ }
101
112
  }
102
113
  return [...seen.values()];
103
114
  }
@@ -125,7 +136,6 @@ export function findDataStores(parsed) {
125
136
  }
126
137
  return [...seen.values()];
127
138
  }
128
- /** Pick the most likely web framework for a manifest's owning package. */
129
139
  export function detectFrameworkFromManifest(m) {
130
140
  for (const [pkg, framework] of Object.entries(WEB_FRAMEWORK_PACKAGES)) {
131
141
  if (m.deps[pkg])
@@ -153,4 +163,3 @@ function relPosix(rootDir, abs) {
153
163
  const rel = path.relative(rootDir, abs);
154
164
  return rel.split(path.sep).join("/");
155
165
  }
156
- //# sourceMappingURL=manifest.js.map
@@ -1,11 +1,3 @@
1
- /**
2
- * tree-sitter-typescript loader. Initializes the WASM grammar lazily and
3
- * caches the parser+language for the rest of the run.
4
- *
5
- * @vscode/tree-sitter-wasm bundles the prebuilt .wasm files so we don't
6
- * need a build step on the user's machine. Loading a .wasm grammar is
7
- * O(50ms); parsing a TS file is O(low ms) per file.
8
- */
9
1
  import { Parser } from "web-tree-sitter";
10
2
  export declare function getParsers(): Promise<{
11
3
  parser: Parser;
@@ -1,11 +1,3 @@
1
- /**
2
- * tree-sitter-typescript loader. Initializes the WASM grammar lazily and
3
- * caches the parser+language for the rest of the run.
4
- *
5
- * @vscode/tree-sitter-wasm bundles the prebuilt .wasm files so we don't
6
- * need a build step on the user's machine. Loading a .wasm grammar is
7
- * O(50ms); parsing a TS file is O(low ms) per file.
8
- */
9
1
  import { createRequire } from "node:module";
10
2
  import * as path from "node:path";
11
3
  import { Parser, Language } from "web-tree-sitter";
@@ -30,4 +22,3 @@ export function getParsers() {
30
22
  export function pickParserForFile(file, parser, tsx) {
31
23
  return file.endsWith(".tsx") || file.endsWith(".jsx") ? tsx : parser;
32
24
  }
33
- //# sourceMappingURL=parser.js.map
@@ -1,24 +1,3 @@
1
- /**
2
- * AST scanner for TypeScript / JavaScript source files.
3
- *
4
- * For each file: parses with tree-sitter-typescript, walks the tree
5
- * looking for:
6
- *
7
- * 1. `import` / `require` statements naming any tracked package
8
- * (AI SDKs, web frameworks, DB clients) → import-evidence + extra
9
- * ExternalService call sites
10
- * 2. Express-style route declarations:
11
- * app.METHOD("/path", ...)
12
- * router.METHOD("/path", ...)
13
- * server.METHOD("/path", ...)
14
- * where METHOD is one of GET/POST/PUT/DELETE/PATCH/ALL/USE
15
- * → HTTP-route-evidence on a component derived from the file
16
- *
17
- * Privacy invariant: we only ever emit identifier names, file paths,
18
- * line numbers, dependency identifiers, and HTTP method/path strings.
19
- * Function bodies and inline values are never read past the first arg
20
- * of a route call.
21
- */
22
1
  import type { EvidenceLocation, Evidence, HttpRouteEvidence, ImportEvidence } from "../../types.js";
23
2
  export interface FileScanResult {
24
3
  file: string;
@@ -27,7 +6,6 @@ export interface FileScanResult {
27
6
  }
28
7
  export declare function scanFile(absPath: string, repoRel: string): Promise<FileScanResult>;
29
8
  export interface ScannedAggregates {
30
- /** Per-component findings (one per source file with relevant evidence). */
31
9
  components: Array<{
32
10
  name: string;
33
11
  sourceFile: string;
@@ -35,9 +13,7 @@ export interface ScannedAggregates {
35
13
  routes: HttpRouteEvidence[];
36
14
  imports: ImportEvidence[];
37
15
  }>;
38
- /** Extra ExternalService call-site evidence beyond what the manifests gave. */
39
16
  aiSdkCallSites: Map<string, EvidenceLocation[]>;
40
- /** Extra data-store evidence (when AST shows usage of e.g. `new Pool()` etc.; v1 just imports). */
41
17
  dataStoreEvidence: Map<string, Evidence[]>;
42
18
  }
43
19
  export declare function aggregate(scans: FileScanResult[]): ScannedAggregates;
@@ -1,24 +1,3 @@
1
- /**
2
- * AST scanner for TypeScript / JavaScript source files.
3
- *
4
- * For each file: parses with tree-sitter-typescript, walks the tree
5
- * looking for:
6
- *
7
- * 1. `import` / `require` statements naming any tracked package
8
- * (AI SDKs, web frameworks, DB clients) → import-evidence + extra
9
- * ExternalService call sites
10
- * 2. Express-style route declarations:
11
- * app.METHOD("/path", ...)
12
- * router.METHOD("/path", ...)
13
- * server.METHOD("/path", ...)
14
- * where METHOD is one of GET/POST/PUT/DELETE/PATCH/ALL/USE
15
- * → HTTP-route-evidence on a component derived from the file
16
- *
17
- * Privacy invariant: we only ever emit identifier names, file paths,
18
- * line numbers, dependency identifiers, and HTTP method/path strings.
19
- * Function bodies and inline values are never read past the first arg
20
- * of a route call.
21
- */
22
1
  import * as fs from "node:fs/promises";
23
2
  import { getParsers, pickParserForFile } from "./parser.js";
24
3
  import { AI_SDK_PACKAGES, DATA_STORE_PACKAGES, WEB_FRAMEWORK_PACKAGES, } from "./manifest.js";
@@ -75,35 +54,22 @@ function walk(node, visit) {
75
54
  walk(child, visit);
76
55
  }
77
56
  }
78
- /** Pull the string literal from `import … from "<here>"`. */
79
57
  function extractImportSource(node) {
80
- // import_statement → ... → (string (string_fragment))
81
58
  const stringNode = node.children.find((c) => c?.type === "string");
82
59
  if (!stringNode)
83
60
  return null;
84
61
  const frag = stringNode.namedChildren.find((c) => c?.type === "string_fragment");
85
62
  return frag?.text ?? null;
86
63
  }
87
- /** Map a package import to a tracked-package identifier, allowing for sub-paths. */
88
64
  function trackedPackageMatch(pkg) {
89
65
  if (TRACKED_PACKAGES.has(pkg))
90
66
  return pkg;
91
- // sub-paths: "@anthropic-ai/sdk/foo" → "@anthropic-ai/sdk"
92
67
  for (const tracked of TRACKED_PACKAGES) {
93
68
  if (pkg.startsWith(tracked + "/"))
94
69
  return tracked;
95
70
  }
96
71
  return null;
97
72
  }
98
- /**
99
- * Identify Express-style route calls. Only inspects the AST shape; never
100
- * reads the handler-function body.
101
- *
102
- * Matches:
103
- * <ROUTER_IDENT>.<METHOD>("/path", ...)
104
- *
105
- * Where the second-and-onward args are deliberately ignored.
106
- */
107
73
  function extractRouteCall(node) {
108
74
  const fn = node.namedChildren.find((c) => c?.type === "member_expression");
109
75
  const args = node.namedChildren.find((c) => c?.type === "arguments");
@@ -119,7 +85,6 @@ function extractRouteCall(node) {
119
85
  return null;
120
86
  if (!HTTP_METHODS.has(methodName))
121
87
  return null;
122
- // First arg should be a string literal — the route path.
123
88
  const firstArg = args.namedChildren[0];
124
89
  if (!firstArg || firstArg.type !== "string")
125
90
  return null;
@@ -135,8 +100,6 @@ export function aggregate(scans) {
135
100
  for (const s of scans) {
136
101
  if (s.routes.length === 0 && s.imports.length === 0)
137
102
  continue;
138
- // Tag SDK call sites. Each tracked import of an AI SDK package
139
- // counts as a call site for that vendor, on top of the manifest hit.
140
103
  for (const imp of s.imports) {
141
104
  if (AI_SDK_PACKAGES[imp.package]) {
142
105
  const arr = aiSdkCallSites.get(imp.package) ?? [];
@@ -152,8 +115,6 @@ export function aggregate(scans) {
152
115
  }
153
116
  if (s.routes.length === 0)
154
117
  continue;
155
- // Derive a friendly component name from the file path (the deepest
156
- // meaningful directory). e.g. src/auth/login.ts → "auth".
157
118
  const name = deriveComponentName(s.file);
158
119
  const framework = detectFrameworkFromImports(s.imports);
159
120
  components.push({
@@ -168,10 +129,7 @@ export function aggregate(scans) {
168
129
  }
169
130
  function deriveComponentName(filePath) {
170
131
  const segs = filePath.split("/");
171
- // Drop "src" / "lib" / "app" prefixes so the name is meaningful.
172
132
  const start = ["src", "lib", "app"].includes(segs[0]) ? 1 : 0;
173
- // Use the parent directory as the component (e.g. "auth"), or the
174
- // filename root if the file lives at the top level.
175
133
  if (segs.length - start >= 2)
176
134
  return segs[start];
177
135
  const last = segs[segs.length - 1];
@@ -185,4 +143,3 @@ function detectFrameworkFromImports(imports) {
185
143
  }
186
144
  return undefined;
187
145
  }
188
- //# sourceMappingURL=scanner.js.map
@@ -0,0 +1,10 @@
1
+ import type { ExtractionResult } from "./types.js";
2
+ import type { RepoGraphArtifactV1, AnalysisMode } from "./graph-types.js";
3
+ export interface BuildOptions {
4
+ applicationId?: string;
5
+ repoId?: string;
6
+ commitSha?: string;
7
+ branch?: string;
8
+ analysisMode?: AnalysisMode;
9
+ }
10
+ export declare function buildRepoGraphArtifact(result: ExtractionResult, opts?: BuildOptions): RepoGraphArtifactV1;
@@ -0,0 +1,245 @@
1
+ const TRUSTED_LANE = "trusted";
2
+ const TRUSTED_SOURCE = "inspect.trusted";
3
+ export function buildRepoGraphArtifact(result, opts = {}) {
4
+ const nodes = [];
5
+ const edges = [];
6
+ const serviceById = new Map();
7
+ for (const component of result.components) {
8
+ if (component.kind === "external_service" || component.kind === "data_store") {
9
+ continue;
10
+ }
11
+ if (component.evidence.length === 0)
12
+ continue;
13
+ const node = buildServiceNode(component);
14
+ serviceById.set(node.id, node);
15
+ nodes.push(node);
16
+ }
17
+ for (const component of result.components) {
18
+ const owningServiceId = serviceIdFor(component);
19
+ if (!serviceById.has(owningServiceId))
20
+ continue;
21
+ const seenEndpoints = new Set();
22
+ for (const ev of component.evidence) {
23
+ if (ev.kind !== "http_route")
24
+ continue;
25
+ const method = canonicalMethod(ev.method);
26
+ if (!method)
27
+ continue;
28
+ const endpointNode = buildEndpointNode(owningServiceId, component.language, ev, method);
29
+ if (seenEndpoints.has(endpointNode.id))
30
+ continue;
31
+ seenEndpoints.add(endpointNode.id);
32
+ nodes.push(endpointNode);
33
+ edges.push(buildExposesEdge(owningServiceId, endpointNode.id, ev));
34
+ }
35
+ }
36
+ const manifestEvidenceByPackage = indexManifestsByPackage(result);
37
+ const depById = new Map();
38
+ for (const ext of result.externalServices) {
39
+ const node = buildExternalDependencyNode(ext, manifestEvidenceByPackage.get(ext.package) ?? []);
40
+ if (depById.has(node.id))
41
+ continue;
42
+ if (node.evidence.length === 0)
43
+ continue;
44
+ depById.set(node.id, node);
45
+ nodes.push(node);
46
+ }
47
+ const fileToServiceId = buildFileToServiceIndex(result.components, serviceById);
48
+ for (const ext of result.externalServices) {
49
+ const depId = externalDepId(ext);
50
+ const seenServiceIds = new Set();
51
+ const evidenceByService = new Map();
52
+ for (const callSite of ext.callSites) {
53
+ const serviceId = fileToServiceId.get(callSite.file);
54
+ if (!serviceId)
55
+ continue;
56
+ seenServiceIds.add(serviceId);
57
+ const list = evidenceByService.get(serviceId) ?? [];
58
+ list.push(toImportEvidence(callSite, ext.package));
59
+ evidenceByService.set(serviceId, list);
60
+ }
61
+ for (const serviceId of seenServiceIds) {
62
+ edges.push(buildDependsOnEdge(serviceId, depId, evidenceByService.get(serviceId) ?? []));
63
+ }
64
+ }
65
+ const provenance = {
66
+ root_dir: result.scope.rootDir,
67
+ analysis_mode: opts.analysisMode ?? "local",
68
+ ...(opts.repoId ? { repo_id: opts.repoId } : {}),
69
+ ...(opts.commitSha ? { commit_sha: opts.commitSha } : {}),
70
+ ...(opts.branch ? { branch: opts.branch } : {}),
71
+ ...(opts.applicationId ? { application_id: opts.applicationId } : {}),
72
+ };
73
+ const languagesFromManifests = new Set(result.manifests.map((m) => normalizeLanguage(m.language)).filter(Boolean));
74
+ return {
75
+ version: "1.0.0",
76
+ kind: "repo_graph_artifact",
77
+ extracted_at: result.extractedAt,
78
+ extractor: result.extractor,
79
+ provenance,
80
+ scope: {
81
+ files_scanned: result.scope.filesScanned,
82
+ files_skipped: result.scope.filesSkipped,
83
+ duration_ms: result.scope.durationMs,
84
+ languages: [...languagesFromManifests],
85
+ },
86
+ nodes,
87
+ edges,
88
+ };
89
+ }
90
+ function buildServiceNode(component) {
91
+ const id = serviceIdFor(component);
92
+ return {
93
+ id,
94
+ type: "service",
95
+ name: component.name,
96
+ lane: TRUSTED_LANE,
97
+ source: TRUSTED_SOURCE,
98
+ confidence: 1,
99
+ language: normalizeLanguage(component.language),
100
+ evidence: component.evidence.map(toEvidenceSpan),
101
+ attributes: {
102
+ ...(component.framework ? { framework: component.framework } : {}),
103
+ source_files: component.sourceFiles,
104
+ },
105
+ };
106
+ }
107
+ function buildEndpointNode(serviceId, language, ev, method) {
108
+ const id = `endpoint::${stripPrefix(serviceId, "service::")}::${method}::${ev.path}`;
109
+ return {
110
+ id,
111
+ type: "endpoint",
112
+ name: `${method} ${ev.path}`,
113
+ lane: TRUSTED_LANE,
114
+ source: TRUSTED_SOURCE,
115
+ confidence: 1,
116
+ language: normalizeLanguage(language),
117
+ evidence: [toEvidenceSpan(ev)],
118
+ attributes: { method, path: ev.path },
119
+ };
120
+ }
121
+ function buildExternalDependencyNode(ext, manifestFallback) {
122
+ const registry = registryFor(ext);
123
+ const evidence = ext.callSites.length > 0
124
+ ? ext.callSites.map((cs) => toImportEvidence(cs, ext.package))
125
+ : manifestFallback;
126
+ return {
127
+ id: externalDepId(ext),
128
+ type: "external_dependency",
129
+ name: ext.name,
130
+ lane: TRUSTED_LANE,
131
+ source: TRUSTED_SOURCE,
132
+ confidence: 1,
133
+ evidence,
134
+ attributes: {
135
+ package: ext.package,
136
+ registry,
137
+ ...(ext.version ? { version: ext.version } : {}),
138
+ ...(ext.kind ? { kind: ext.kind } : {}),
139
+ },
140
+ };
141
+ }
142
+ function indexManifestsByPackage(result) {
143
+ const idx = new Map();
144
+ for (const m of result.manifests) {
145
+ for (const pkg of Object.keys(m.dependencies)) {
146
+ const span = { file: m.file, kind: "manifest", detail: { package: pkg } };
147
+ const list = idx.get(pkg) ?? [];
148
+ list.push(span);
149
+ idx.set(pkg, list);
150
+ }
151
+ }
152
+ return idx;
153
+ }
154
+ function buildExposesEdge(serviceId, endpointId, ev) {
155
+ return {
156
+ id: `exposes::${serviceId}::${endpointId}`,
157
+ type: "exposes",
158
+ source_id: serviceId,
159
+ target_id: endpointId,
160
+ lane: TRUSTED_LANE,
161
+ source: TRUSTED_SOURCE,
162
+ confidence: 1,
163
+ evidence: [toEvidenceSpan(ev)],
164
+ };
165
+ }
166
+ function buildDependsOnEdge(serviceId, depId, evidence) {
167
+ return {
168
+ id: `depends_on::${serviceId}::${depId}`,
169
+ type: "depends_on",
170
+ source_id: serviceId,
171
+ target_id: depId,
172
+ lane: TRUSTED_LANE,
173
+ source: TRUSTED_SOURCE,
174
+ confidence: 1,
175
+ evidence,
176
+ };
177
+ }
178
+ function serviceIdFor(component) {
179
+ return `service::${component.language}::${component.name}`;
180
+ }
181
+ function externalDepId(ext) {
182
+ const registry = registryFor(ext);
183
+ return `external_dependency::${registry}::${ext.package}`;
184
+ }
185
+ function registryFor(ext) {
186
+ if (ext.package.startsWith("github.com/"))
187
+ return "go";
188
+ if (ext.package.startsWith("@") || /^[a-z0-9-]+$/.test(ext.package)) {
189
+ return "npm";
190
+ }
191
+ return "npm";
192
+ }
193
+ const CANONICAL_METHODS = new Set([
194
+ "GET",
195
+ "POST",
196
+ "PUT",
197
+ "PATCH",
198
+ "DELETE",
199
+ "HEAD",
200
+ "OPTIONS",
201
+ "WS",
202
+ "SSE",
203
+ "RPC",
204
+ ]);
205
+ function canonicalMethod(method) {
206
+ const upper = method.toUpperCase();
207
+ return CANONICAL_METHODS.has(upper) ? upper : undefined;
208
+ }
209
+ function normalizeLanguage(lang) {
210
+ return lang;
211
+ }
212
+ function buildFileToServiceIndex(components, servicesById) {
213
+ const idx = new Map();
214
+ for (const component of components) {
215
+ const id = serviceIdFor(component);
216
+ if (!servicesById.has(id))
217
+ continue;
218
+ for (const file of component.sourceFiles) {
219
+ if (!idx.has(file))
220
+ idx.set(file, id);
221
+ }
222
+ }
223
+ return idx;
224
+ }
225
+ function toEvidenceSpan(ev) {
226
+ const base = { file: ev.file, start_line: ev.line, kind: ev.kind };
227
+ if (ev.kind === "http_route") {
228
+ return { ...base, detail: { method: ev.method, path: ev.path } };
229
+ }
230
+ if (ev.kind === "import") {
231
+ return { ...base, detail: { package: ev.package } };
232
+ }
233
+ return base;
234
+ }
235
+ function toImportEvidence(loc, pkg) {
236
+ return {
237
+ file: loc.file,
238
+ start_line: loc.line,
239
+ kind: "import",
240
+ detail: { package: pkg },
241
+ };
242
+ }
243
+ function stripPrefix(value, prefix) {
244
+ return value.startsWith(prefix) ? value.slice(prefix.length) : value;
245
+ }