@neat.is/core 0.2.8 → 0.2.9

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/dist/server.cjs CHANGED
@@ -99,8 +99,8 @@ function reshapeGrpcRequest(req) {
99
99
  };
100
100
  }
101
101
  function resolveProtoRoot() {
102
- const here = import_node_path30.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
103
- return import_node_path30.default.resolve(here, "..", "proto");
102
+ const here = import_node_path31.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
103
+ return import_node_path31.default.resolve(here, "..", "proto");
104
104
  }
105
105
  function loadTraceService() {
106
106
  const protoRoot = resolveProtoRoot();
@@ -155,13 +155,13 @@ async function startOtelGrpcReceiver(opts) {
155
155
  })
156
156
  };
157
157
  }
158
- var import_node_url, import_node_path30, grpc, protoLoader;
158
+ var import_node_url, import_node_path31, grpc, protoLoader;
159
159
  var init_otel_grpc = __esm({
160
160
  "src/otel-grpc.ts"() {
161
161
  "use strict";
162
162
  init_cjs_shims();
163
163
  import_node_url = require("url");
164
- import_node_path30 = __toESM(require("path"), 1);
164
+ import_node_path31 = __toESM(require("path"), 1);
165
165
  grpc = __toESM(require("@grpc/grpc-js"), 1);
166
166
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
167
167
  init_otel();
@@ -258,10 +258,10 @@ function parseOtlpRequest(body) {
258
258
  }
259
259
  function loadProtobufDecoder() {
260
260
  if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
261
- const here = import_node_path31.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
262
- const protoRoot = import_node_path31.default.resolve(here, "..", "proto");
261
+ const here = import_node_path32.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
262
+ const protoRoot = import_node_path32.default.resolve(here, "..", "proto");
263
263
  const root = new import_protobufjs.default.Root();
264
- root.resolvePath = (_origin, target) => import_node_path31.default.resolve(protoRoot, target);
264
+ root.resolvePath = (_origin, target) => import_node_path32.default.resolve(protoRoot, target);
265
265
  root.loadSync(
266
266
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
267
267
  { keepCase: true }
@@ -353,12 +353,12 @@ async function buildOtelReceiver(opts) {
353
353
  };
354
354
  return decorated;
355
355
  }
356
- var import_node_path31, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
356
+ var import_node_path32, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
357
357
  var init_otel = __esm({
358
358
  "src/otel.ts"() {
359
359
  "use strict";
360
360
  init_cjs_shims();
361
- import_node_path31 = __toESM(require("path"), 1);
361
+ import_node_path32 = __toESM(require("path"), 1);
362
362
  import_node_url2 = require("url");
363
363
  import_fastify2 = __toESM(require("fastify"), 1);
364
364
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -368,7 +368,7 @@ var init_otel = __esm({
368
368
 
369
369
  // src/server.ts
370
370
  init_cjs_shims();
371
- var import_node_path33 = __toESM(require("path"), 1);
371
+ var import_node_path34 = __toESM(require("path"), 1);
372
372
 
373
373
  // src/graph.ts
374
374
  init_cjs_shims();
@@ -812,19 +812,19 @@ function confidenceFromMix(edges, now = Date.now()) {
812
812
  function longestIncomingWalk(graph, start, maxDepth) {
813
813
  let best = { path: [start], edges: [] };
814
814
  const visited = /* @__PURE__ */ new Set([start]);
815
- function step(node, path34, edges) {
816
- if (path34.length > best.path.length) {
817
- best = { path: [...path34], edges: [...edges] };
815
+ function step(node, path35, edges) {
816
+ if (path35.length > best.path.length) {
817
+ best = { path: [...path35], edges: [...edges] };
818
818
  }
819
- if (path34.length - 1 >= maxDepth) return;
819
+ if (path35.length - 1 >= maxDepth) return;
820
820
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
821
821
  for (const [srcId, edge] of incoming) {
822
822
  if (visited.has(srcId)) continue;
823
823
  visited.add(srcId);
824
- path34.push(srcId);
824
+ path35.push(srcId);
825
825
  edges.push(edge);
826
- step(srcId, path34, edges);
827
- path34.pop();
826
+ step(srcId, path35, edges);
827
+ path35.pop();
828
828
  edges.pop();
829
829
  visited.delete(srcId);
830
830
  }
@@ -1847,10 +1847,10 @@ async function readErrorEvents(errorsPath) {
1847
1847
 
1848
1848
  // src/extract/services.ts
1849
1849
  init_cjs_shims();
1850
- var import_node_fs6 = require("fs");
1851
- var import_node_path6 = __toESM(require("path"), 1);
1850
+ var import_node_fs7 = require("fs");
1851
+ var import_node_path7 = __toESM(require("path"), 1);
1852
1852
  var import_ignore = __toESM(require("ignore"), 1);
1853
- var import_minimatch = require("minimatch");
1853
+ var import_minimatch2 = require("minimatch");
1854
1854
  var import_types5 = require("@neat.is/types");
1855
1855
 
1856
1856
  // src/extract/shared.ts
@@ -1963,6 +1963,73 @@ function pythonToPackage(service) {
1963
1963
  };
1964
1964
  }
1965
1965
 
1966
+ // src/extract/owners.ts
1967
+ init_cjs_shims();
1968
+ var import_node_fs6 = require("fs");
1969
+ var import_node_path6 = __toESM(require("path"), 1);
1970
+ var import_minimatch = require("minimatch");
1971
+ async function loadCodeowners(scanPath) {
1972
+ const candidates = [
1973
+ import_node_path6.default.join(scanPath, "CODEOWNERS"),
1974
+ import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
1975
+ ];
1976
+ for (const file of candidates) {
1977
+ if (await exists(file)) {
1978
+ const raw = await import_node_fs6.promises.readFile(file, "utf8");
1979
+ return parseCodeowners(raw);
1980
+ }
1981
+ }
1982
+ return null;
1983
+ }
1984
+ function parseCodeowners(raw) {
1985
+ const rules = [];
1986
+ for (const line of raw.split("\n")) {
1987
+ const trimmed = line.trim();
1988
+ if (!trimmed || trimmed.startsWith("#")) continue;
1989
+ const match = /^(\S+)\s+(.+)$/.exec(trimmed);
1990
+ if (!match) continue;
1991
+ rules.push({ pattern: match[1], owners: match[2].trim() });
1992
+ }
1993
+ return { rules };
1994
+ }
1995
+ function matchOwner(file, repoPath) {
1996
+ const normalized = repoPath.split(import_node_path6.default.sep).join("/");
1997
+ for (const rule of file.rules) {
1998
+ if (matchesPattern(rule.pattern, normalized)) return rule.owners;
1999
+ }
2000
+ return null;
2001
+ }
2002
+ function matchesPattern(rawPattern, repoPath) {
2003
+ let pattern = rawPattern.startsWith("/") ? rawPattern.slice(1) : rawPattern;
2004
+ if (pattern === "*") return !repoPath.includes("/");
2005
+ if (pattern === "**" || pattern === "") return true;
2006
+ if (pattern.endsWith("/")) pattern = pattern + "**";
2007
+ if ((0, import_minimatch.minimatch)(repoPath, pattern, { dot: true })) return true;
2008
+ if (!pattern.includes("*") && (0, import_minimatch.minimatch)(repoPath, pattern + "/**", { dot: true })) return true;
2009
+ return false;
2010
+ }
2011
+ async function readPackageJsonAuthor(serviceDir) {
2012
+ const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
2013
+ if (!await exists(pkgPath)) return null;
2014
+ try {
2015
+ const pkg = await readJson(pkgPath);
2016
+ if (!pkg.author) return null;
2017
+ if (typeof pkg.author === "string") return pkg.author;
2018
+ if (typeof pkg.author === "object" && typeof pkg.author.name === "string") return pkg.author.name;
2019
+ return null;
2020
+ } catch {
2021
+ return null;
2022
+ }
2023
+ }
2024
+ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
2025
+ if (codeowners && repoPath !== void 0) {
2026
+ const owner = matchOwner(codeowners, repoPath);
2027
+ if (owner) return owner;
2028
+ }
2029
+ const author = await readPackageJsonAuthor(serviceDir);
2030
+ return author ?? void 0;
2031
+ }
2032
+
1966
2033
  // src/extract/services.ts
1967
2034
  var DEFAULT_SCAN_DEPTH = 5;
1968
2035
  function parseScanDepth() {
@@ -1979,21 +2046,21 @@ function workspaceGlobs(pkg) {
1979
2046
  return null;
1980
2047
  }
1981
2048
  async function loadGitignore(scanPath) {
1982
- const gitignorePath = import_node_path6.default.join(scanPath, ".gitignore");
2049
+ const gitignorePath = import_node_path7.default.join(scanPath, ".gitignore");
1983
2050
  if (!await exists(gitignorePath)) return null;
1984
- const raw = await import_node_fs6.promises.readFile(gitignorePath, "utf8");
2051
+ const raw = await import_node_fs7.promises.readFile(gitignorePath, "utf8");
1985
2052
  return (0, import_ignore.default)().add(raw);
1986
2053
  }
1987
2054
  async function walkDirs(start, scanPath, options, visit) {
1988
2055
  async function recurse(current, depth) {
1989
2056
  if (depth > options.maxDepth) return;
1990
- const entries = await import_node_fs6.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2057
+ const entries = await import_node_fs7.promises.readdir(current, { withFileTypes: true }).catch(() => []);
1991
2058
  for (const entry of entries) {
1992
2059
  if (!entry.isDirectory()) continue;
1993
2060
  if (IGNORED_DIRS.has(entry.name)) continue;
1994
- const child = import_node_path6.default.join(current, entry.name);
2061
+ const child = import_node_path7.default.join(current, entry.name);
1995
2062
  if (options.ig) {
1996
- const rel = import_node_path6.default.relative(scanPath, child).split(import_node_path6.default.sep).join("/");
2063
+ const rel = import_node_path7.default.relative(scanPath, child).split(import_node_path7.default.sep).join("/");
1997
2064
  if (rel && options.ig.ignores(rel + "/")) continue;
1998
2065
  }
1999
2066
  await visit(child);
@@ -2008,8 +2075,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2008
2075
  for (const raw of globs) {
2009
2076
  const pattern = raw.replace(/^\.\//, "");
2010
2077
  if (!pattern.includes("*")) {
2011
- const candidate = import_node_path6.default.join(scanPath, pattern);
2012
- if (await exists(import_node_path6.default.join(candidate, "package.json"))) found.add(candidate);
2078
+ const candidate = import_node_path7.default.join(scanPath, pattern);
2079
+ if (await exists(import_node_path7.default.join(candidate, "package.json"))) found.add(candidate);
2013
2080
  continue;
2014
2081
  }
2015
2082
  const segments = pattern.split("/");
@@ -2018,13 +2085,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2018
2085
  if (seg.includes("*")) break;
2019
2086
  staticSegments.push(seg);
2020
2087
  }
2021
- const start = import_node_path6.default.join(scanPath, ...staticSegments);
2088
+ const start = import_node_path7.default.join(scanPath, ...staticSegments);
2022
2089
  if (!await exists(start)) continue;
2023
2090
  const hasDoubleStar = pattern.includes("**");
2024
2091
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
2025
2092
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
2026
- const rel = import_node_path6.default.relative(scanPath, dir).split(import_node_path6.default.sep).join("/");
2027
- if ((0, import_minimatch.minimatch)(rel, pattern) && await exists(import_node_path6.default.join(dir, "package.json"))) {
2093
+ const rel = import_node_path7.default.relative(scanPath, dir).split(import_node_path7.default.sep).join("/");
2094
+ if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path7.default.join(dir, "package.json"))) {
2028
2095
  found.add(dir);
2029
2096
  }
2030
2097
  });
@@ -2032,9 +2099,17 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2032
2099
  return [...found];
2033
2100
  }
2034
2101
  async function discoverNodeService(scanPath, dir) {
2035
- const pkgPath = import_node_path6.default.join(dir, "package.json");
2102
+ const pkgPath = import_node_path7.default.join(dir, "package.json");
2036
2103
  if (!await exists(pkgPath)) return null;
2037
- const pkg = await readJson(pkgPath);
2104
+ let pkg;
2105
+ try {
2106
+ pkg = await readJson(pkgPath);
2107
+ } catch (err) {
2108
+ console.warn(
2109
+ `[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
2110
+ );
2111
+ return null;
2112
+ }
2038
2113
  if (!pkg.name) return null;
2039
2114
  const node = {
2040
2115
  id: (0, import_types5.serviceId)(pkg.name),
@@ -2043,7 +2118,7 @@ async function discoverNodeService(scanPath, dir) {
2043
2118
  language: "javascript",
2044
2119
  version: pkg.version,
2045
2120
  dependencies: pkg.dependencies ?? {},
2046
- repoPath: import_node_path6.default.relative(scanPath, dir),
2121
+ repoPath: import_node_path7.default.relative(scanPath, dir),
2047
2122
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
2048
2123
  };
2049
2124
  return { pkg, dir, node };
@@ -2059,13 +2134,22 @@ async function discoverPyService(scanPath, dir) {
2059
2134
  language: "python",
2060
2135
  version: py.version,
2061
2136
  dependencies: py.dependencies,
2062
- repoPath: import_node_path6.default.relative(scanPath, dir)
2137
+ repoPath: import_node_path7.default.relative(scanPath, dir)
2063
2138
  };
2064
2139
  return { pkg, dir, node };
2065
2140
  }
2066
2141
  async function discoverServices(scanPath) {
2067
- const rootPkgPath = import_node_path6.default.join(scanPath, "package.json");
2068
- const rootPkg = await exists(rootPkgPath) ? await readJson(rootPkgPath) : null;
2142
+ const rootPkgPath = import_node_path7.default.join(scanPath, "package.json");
2143
+ let rootPkg = null;
2144
+ if (await exists(rootPkgPath)) {
2145
+ try {
2146
+ rootPkg = await readJson(rootPkgPath);
2147
+ } catch (err) {
2148
+ console.warn(
2149
+ `[neat] services workspaces skipped ${import_node_path7.default.relative(scanPath, rootPkgPath)}: ${err.message}`
2150
+ );
2151
+ }
2152
+ }
2069
2153
  const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null;
2070
2154
  const candidateDirs = [];
2071
2155
  if (wsGlobs) {
@@ -2078,9 +2162,9 @@ async function discoverServices(scanPath) {
2078
2162
  scanPath,
2079
2163
  { maxDepth: parseScanDepth(), ig },
2080
2164
  async (dir) => {
2081
- if (await exists(import_node_path6.default.join(dir, "package.json"))) {
2165
+ if (await exists(import_node_path7.default.join(dir, "package.json"))) {
2082
2166
  candidateDirs.push(dir);
2083
- } else if (await exists(import_node_path6.default.join(dir, "pyproject.toml")) || await exists(import_node_path6.default.join(dir, "requirements.txt")) || await exists(import_node_path6.default.join(dir, "setup.py"))) {
2167
+ } else if (await exists(import_node_path7.default.join(dir, "pyproject.toml")) || await exists(import_node_path7.default.join(dir, "requirements.txt")) || await exists(import_node_path7.default.join(dir, "setup.py"))) {
2084
2168
  candidateDirs.push(dir);
2085
2169
  }
2086
2170
  }
@@ -2094,8 +2178,8 @@ async function discoverServices(scanPath) {
2094
2178
  if (!service) continue;
2095
2179
  const existingDir = seen.get(service.node.name);
2096
2180
  if (existingDir !== void 0) {
2097
- const a = import_node_path6.default.relative(scanPath, existingDir) || ".";
2098
- const b = import_node_path6.default.relative(scanPath, dir) || ".";
2181
+ const a = import_node_path7.default.relative(scanPath, existingDir) || ".";
2182
+ const b = import_node_path7.default.relative(scanPath, dir) || ".";
2099
2183
  console.warn(
2100
2184
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
2101
2185
  );
@@ -2104,6 +2188,11 @@ async function discoverServices(scanPath) {
2104
2188
  seen.set(service.node.name, dir);
2105
2189
  out.push(service);
2106
2190
  }
2191
+ const codeowners = await loadCodeowners(scanPath);
2192
+ for (const service of out) {
2193
+ const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
2194
+ if (owner !== void 0) service.node.owner = owner;
2195
+ }
2107
2196
  return out;
2108
2197
  }
2109
2198
  function addServiceNodes(graph, services) {
@@ -2127,8 +2216,8 @@ function addServiceNodes(graph, services) {
2127
2216
 
2128
2217
  // src/extract/aliases.ts
2129
2218
  init_cjs_shims();
2130
- var import_node_path7 = __toESM(require("path"), 1);
2131
- var import_node_fs7 = require("fs");
2219
+ var import_node_path8 = __toESM(require("path"), 1);
2220
+ var import_node_fs8 = require("fs");
2132
2221
  var import_yaml2 = require("yaml");
2133
2222
  var import_types6 = require("@neat.is/types");
2134
2223
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -2155,21 +2244,29 @@ function indexServicesByName(services) {
2155
2244
  const map = /* @__PURE__ */ new Map();
2156
2245
  for (const s of services) {
2157
2246
  map.set(s.node.name, s.node.id);
2158
- map.set(import_node_path7.default.basename(s.dir), s.node.id);
2247
+ map.set(import_node_path8.default.basename(s.dir), s.node.id);
2159
2248
  }
2160
2249
  return map;
2161
2250
  }
2162
2251
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
2163
2252
  let composePath = null;
2164
2253
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
2165
- const abs = import_node_path7.default.join(scanPath, name);
2254
+ const abs = import_node_path8.default.join(scanPath, name);
2166
2255
  if (await exists(abs)) {
2167
2256
  composePath = abs;
2168
2257
  break;
2169
2258
  }
2170
2259
  }
2171
2260
  if (!composePath) return;
2172
- const compose = await readYaml(composePath);
2261
+ let compose;
2262
+ try {
2263
+ compose = await readYaml(composePath);
2264
+ } catch (err) {
2265
+ console.warn(
2266
+ `[neat] aliases compose skipped ${import_node_path8.default.relative(scanPath, composePath)}: ${err.message}`
2267
+ );
2268
+ return;
2269
+ }
2173
2270
  if (!compose?.services) return;
2174
2271
  for (const [composeName, svc] of Object.entries(compose.services)) {
2175
2272
  const serviceId3 = serviceIndex.get(composeName);
@@ -2208,9 +2305,17 @@ function parseDockerfileLabels(content) {
2208
2305
  }
2209
2306
  async function collectDockerfileAliases(graph, services) {
2210
2307
  for (const service of services) {
2211
- const dockerfilePath = import_node_path7.default.join(service.dir, "Dockerfile");
2308
+ const dockerfilePath = import_node_path8.default.join(service.dir, "Dockerfile");
2212
2309
  if (!await exists(dockerfilePath)) continue;
2213
- const content = await import_node_fs7.promises.readFile(dockerfilePath, "utf8");
2310
+ let content;
2311
+ try {
2312
+ content = await import_node_fs8.promises.readFile(dockerfilePath, "utf8");
2313
+ } catch (err) {
2314
+ console.warn(
2315
+ `[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
2316
+ );
2317
+ continue;
2318
+ }
2214
2319
  const aliases = parseDockerfileLabels(content);
2215
2320
  if (aliases.length > 0) addAliases(graph, service.node.id, aliases);
2216
2321
  }
@@ -2218,13 +2323,13 @@ async function collectDockerfileAliases(graph, services) {
2218
2323
  async function walkYamlFiles(start, depth = 0, max = 5) {
2219
2324
  if (depth > max) return [];
2220
2325
  const out = [];
2221
- const entries = await import_node_fs7.promises.readdir(start, { withFileTypes: true }).catch(() => []);
2326
+ const entries = await import_node_fs8.promises.readdir(start, { withFileTypes: true }).catch(() => []);
2222
2327
  for (const entry of entries) {
2223
2328
  if (entry.isDirectory()) {
2224
2329
  if (IGNORED_DIRS.has(entry.name)) continue;
2225
- out.push(...await walkYamlFiles(import_node_path7.default.join(start, entry.name), depth + 1, max));
2226
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path7.default.extname(entry.name))) {
2227
- out.push(import_node_path7.default.join(start, entry.name));
2330
+ out.push(...await walkYamlFiles(import_node_path8.default.join(start, entry.name), depth + 1, max));
2331
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path8.default.extname(entry.name))) {
2332
+ out.push(import_node_path8.default.join(start, entry.name));
2228
2333
  }
2229
2334
  }
2230
2335
  return out;
@@ -2251,7 +2356,7 @@ function k8sServiceTarget(doc, byName) {
2251
2356
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
2252
2357
  const files = await walkYamlFiles(scanPath);
2253
2358
  for (const file of files) {
2254
- const content = await import_node_fs7.promises.readFile(file, "utf8");
2359
+ const content = await import_node_fs8.promises.readFile(file, "utf8");
2255
2360
  let docs;
2256
2361
  try {
2257
2362
  docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -2276,14 +2381,14 @@ async function addServiceAliases(graph, scanPath, services) {
2276
2381
 
2277
2382
  // src/extract/databases/index.ts
2278
2383
  init_cjs_shims();
2279
- var import_node_path15 = __toESM(require("path"), 1);
2384
+ var import_node_path16 = __toESM(require("path"), 1);
2280
2385
  var import_types7 = require("@neat.is/types");
2281
2386
 
2282
2387
  // src/extract/databases/db-config-yaml.ts
2283
2388
  init_cjs_shims();
2284
- var import_node_path8 = __toESM(require("path"), 1);
2389
+ var import_node_path9 = __toESM(require("path"), 1);
2285
2390
  async function parse(serviceDir) {
2286
- const yamlPath = import_node_path8.default.join(serviceDir, "db-config.yaml");
2391
+ const yamlPath = import_node_path9.default.join(serviceDir, "db-config.yaml");
2287
2392
  if (!await exists(yamlPath)) return [];
2288
2393
  const raw = await readYaml(yamlPath);
2289
2394
  return [
@@ -2301,13 +2406,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
2301
2406
 
2302
2407
  // src/extract/databases/dotenv.ts
2303
2408
  init_cjs_shims();
2304
- var import_node_fs9 = require("fs");
2305
- var import_node_path10 = __toESM(require("path"), 1);
2409
+ var import_node_fs10 = require("fs");
2410
+ var import_node_path11 = __toESM(require("path"), 1);
2306
2411
 
2307
2412
  // src/extract/databases/shared.ts
2308
2413
  init_cjs_shims();
2309
- var import_node_fs8 = require("fs");
2310
- var import_node_path9 = __toESM(require("path"), 1);
2414
+ var import_node_fs9 = require("fs");
2415
+ var import_node_path10 = __toESM(require("path"), 1);
2311
2416
  function schemeToEngine(scheme) {
2312
2417
  const s = scheme.toLowerCase().split("+")[0];
2313
2418
  switch (s) {
@@ -2346,14 +2451,14 @@ function parseConnectionString(url) {
2346
2451
  }
2347
2452
  async function readIfExists(filePath) {
2348
2453
  try {
2349
- return await import_node_fs8.promises.readFile(filePath, "utf8");
2454
+ return await import_node_fs9.promises.readFile(filePath, "utf8");
2350
2455
  } catch {
2351
2456
  return null;
2352
2457
  }
2353
2458
  }
2354
2459
  async function findFirst(serviceDir, candidates) {
2355
2460
  for (const rel of candidates) {
2356
- const abs = import_node_path9.default.join(serviceDir, rel);
2461
+ const abs = import_node_path10.default.join(serviceDir, rel);
2357
2462
  const content = await readIfExists(abs);
2358
2463
  if (content !== null) return abs;
2359
2464
  }
@@ -2404,15 +2509,15 @@ function parseDotenvLine(line) {
2404
2509
  return { key, value };
2405
2510
  }
2406
2511
  async function parse2(serviceDir) {
2407
- const entries = await import_node_fs9.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
2512
+ const entries = await import_node_fs10.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
2408
2513
  const configs = [];
2409
2514
  const seen = /* @__PURE__ */ new Set();
2410
2515
  for (const entry of entries) {
2411
2516
  if (!entry.isFile()) continue;
2412
2517
  const match = isConfigFile(entry.name);
2413
2518
  if (!match.match || match.fileType !== "env") continue;
2414
- const filePath = import_node_path10.default.join(serviceDir, entry.name);
2415
- const content = await import_node_fs9.promises.readFile(filePath, "utf8");
2519
+ const filePath = import_node_path11.default.join(serviceDir, entry.name);
2520
+ const content = await import_node_fs10.promises.readFile(filePath, "utf8");
2416
2521
  for (const line of content.split("\n")) {
2417
2522
  const parsed = parseDotenvLine(line);
2418
2523
  if (!parsed) continue;
@@ -2431,9 +2536,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
2431
2536
 
2432
2537
  // src/extract/databases/prisma.ts
2433
2538
  init_cjs_shims();
2434
- var import_node_path11 = __toESM(require("path"), 1);
2539
+ var import_node_path12 = __toESM(require("path"), 1);
2435
2540
  async function parse3(serviceDir) {
2436
- const schemaPath = import_node_path11.default.join(serviceDir, "prisma", "schema.prisma");
2541
+ const schemaPath = import_node_path12.default.join(serviceDir, "prisma", "schema.prisma");
2437
2542
  const content = await readIfExists(schemaPath);
2438
2543
  if (!content) return [];
2439
2544
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -2565,10 +2670,10 @@ var knexParser = { name: "knex", parse: parse5 };
2565
2670
 
2566
2671
  // src/extract/databases/ormconfig.ts
2567
2672
  init_cjs_shims();
2568
- var import_node_path12 = __toESM(require("path"), 1);
2673
+ var import_node_path13 = __toESM(require("path"), 1);
2569
2674
  async function parse6(serviceDir) {
2570
2675
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
2571
- const abs = import_node_path12.default.join(serviceDir, candidate);
2676
+ const abs = import_node_path13.default.join(serviceDir, candidate);
2572
2677
  if (!await exists(abs)) continue;
2573
2678
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
2574
2679
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -2628,9 +2733,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
2628
2733
 
2629
2734
  // src/extract/databases/sequelize.ts
2630
2735
  init_cjs_shims();
2631
- var import_node_path13 = __toESM(require("path"), 1);
2736
+ var import_node_path14 = __toESM(require("path"), 1);
2632
2737
  async function parse8(serviceDir) {
2633
- const configPath = import_node_path13.default.join(serviceDir, "config", "config.json");
2738
+ const configPath = import_node_path14.default.join(serviceDir, "config", "config.json");
2634
2739
  if (!await exists(configPath)) return [];
2635
2740
  const raw = await readJson(configPath);
2636
2741
  const out = [];
@@ -2657,7 +2762,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
2657
2762
 
2658
2763
  // src/extract/databases/docker-compose.ts
2659
2764
  init_cjs_shims();
2660
- var import_node_path14 = __toESM(require("path"), 1);
2765
+ var import_node_path15 = __toESM(require("path"), 1);
2661
2766
  function portFromService(svc) {
2662
2767
  for (const raw of svc.ports ?? []) {
2663
2768
  const str = String(raw);
@@ -2684,7 +2789,7 @@ function databaseFromEnv(svc) {
2684
2789
  }
2685
2790
  async function parse9(serviceDir) {
2686
2791
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
2687
- const abs = import_node_path14.default.join(serviceDir, name);
2792
+ const abs = import_node_path15.default.join(serviceDir, name);
2688
2793
  if (!await exists(abs)) continue;
2689
2794
  const raw = await readYaml(abs);
2690
2795
  if (!raw?.services) return [];
@@ -2864,7 +2969,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2864
2969
  provenance: import_types7.Provenance.EXTRACTED,
2865
2970
  ...config.sourceFile ? {
2866
2971
  evidence: {
2867
- file: import_node_path15.default.relative(scanPath, config.sourceFile).split(import_node_path15.default.sep).join("/")
2972
+ file: import_node_path16.default.relative(scanPath, config.sourceFile).split(import_node_path16.default.sep).join("/")
2868
2973
  }
2869
2974
  } : {}
2870
2975
  };
@@ -2892,15 +2997,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2892
2997
 
2893
2998
  // src/extract/configs.ts
2894
2999
  init_cjs_shims();
2895
- var import_node_fs10 = require("fs");
2896
- var import_node_path16 = __toESM(require("path"), 1);
3000
+ var import_node_fs11 = require("fs");
3001
+ var import_node_path17 = __toESM(require("path"), 1);
2897
3002
  var import_types8 = require("@neat.is/types");
2898
3003
  async function walkConfigFiles(dir) {
2899
3004
  const out = [];
2900
3005
  async function walk(current) {
2901
- const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true });
3006
+ const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true });
2902
3007
  for (const entry of entries) {
2903
- const full = import_node_path16.default.join(current, entry.name);
3008
+ const full = import_node_path17.default.join(current, entry.name);
2904
3009
  if (entry.isDirectory()) {
2905
3010
  if (!IGNORED_DIRS.has(entry.name)) await walk(full);
2906
3011
  } else if (entry.isFile() && isConfigFile(entry.name).match) {
@@ -2917,13 +3022,13 @@ async function addConfigNodes(graph, services, scanPath) {
2917
3022
  for (const service of services) {
2918
3023
  const configFiles = await walkConfigFiles(service.dir);
2919
3024
  for (const file of configFiles) {
2920
- const relPath = import_node_path16.default.relative(scanPath, file);
3025
+ const relPath = import_node_path17.default.relative(scanPath, file);
2921
3026
  const node = {
2922
3027
  id: (0, import_types8.configId)(relPath),
2923
3028
  type: import_types8.NodeType.ConfigNode,
2924
- name: import_node_path16.default.basename(file),
3029
+ name: import_node_path17.default.basename(file),
2925
3030
  path: relPath,
2926
- fileType: isConfigFile(import_node_path16.default.basename(file)).fileType
3031
+ fileType: isConfigFile(import_node_path17.default.basename(file)).fileType
2927
3032
  };
2928
3033
  if (!graph.hasNode(node.id)) {
2929
3034
  graph.addNode(node.id, node);
@@ -2935,7 +3040,7 @@ async function addConfigNodes(graph, services, scanPath) {
2935
3040
  target: node.id,
2936
3041
  type: import_types8.EdgeType.CONFIGURED_BY,
2937
3042
  provenance: import_types8.Provenance.EXTRACTED,
2938
- evidence: { file: relPath.split(import_node_path16.default.sep).join("/") }
3043
+ evidence: { file: relPath.split(import_node_path17.default.sep).join("/") }
2939
3044
  };
2940
3045
  if (!graph.hasEdge(edge.id)) {
2941
3046
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -2952,7 +3057,7 @@ var import_types14 = require("@neat.is/types");
2952
3057
 
2953
3058
  // src/extract/calls/http.ts
2954
3059
  init_cjs_shims();
2955
- var import_node_path18 = __toESM(require("path"), 1);
3060
+ var import_node_path19 = __toESM(require("path"), 1);
2956
3061
  var import_tree_sitter = __toESM(require("tree-sitter"), 1);
2957
3062
  var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
2958
3063
  var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
@@ -2960,17 +3065,17 @@ var import_types9 = require("@neat.is/types");
2960
3065
 
2961
3066
  // src/extract/calls/shared.ts
2962
3067
  init_cjs_shims();
2963
- var import_node_fs11 = require("fs");
2964
- var import_node_path17 = __toESM(require("path"), 1);
3068
+ var import_node_fs12 = require("fs");
3069
+ var import_node_path18 = __toESM(require("path"), 1);
2965
3070
  async function walkSourceFiles(dir) {
2966
3071
  const out = [];
2967
3072
  async function walk(current) {
2968
- const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3073
+ const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2969
3074
  for (const entry of entries) {
2970
- const full = import_node_path17.default.join(current, entry.name);
3075
+ const full = import_node_path18.default.join(current, entry.name);
2971
3076
  if (entry.isDirectory()) {
2972
3077
  if (!IGNORED_DIRS.has(entry.name)) await walk(full);
2973
- } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path17.default.extname(entry.name))) {
3078
+ } else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path18.default.extname(entry.name))) {
2974
3079
  out.push(full);
2975
3080
  }
2976
3081
  }
@@ -2983,7 +3088,7 @@ async function loadSourceFiles(dir) {
2983
3088
  const out = [];
2984
3089
  for (const p of paths) {
2985
3090
  try {
2986
- const content = await import_node_fs11.promises.readFile(p, "utf8");
3091
+ const content = await import_node_fs12.promises.readFile(p, "utf8");
2987
3092
  out.push({ path: p, content });
2988
3093
  } catch {
2989
3094
  }
@@ -3039,9 +3144,9 @@ async function addHttpCallEdges(graph, services) {
3039
3144
  const knownHosts = /* @__PURE__ */ new Set();
3040
3145
  const hostToNodeId = /* @__PURE__ */ new Map();
3041
3146
  for (const service of services) {
3042
- knownHosts.add(import_node_path18.default.basename(service.dir));
3147
+ knownHosts.add(import_node_path19.default.basename(service.dir));
3043
3148
  knownHosts.add(service.pkg.name);
3044
- hostToNodeId.set(import_node_path18.default.basename(service.dir), service.node.id);
3149
+ hostToNodeId.set(import_node_path19.default.basename(service.dir), service.node.id);
3045
3150
  hostToNodeId.set(service.pkg.name, service.node.id);
3046
3151
  }
3047
3152
  let edgesAdded = 0;
@@ -3049,7 +3154,7 @@ async function addHttpCallEdges(graph, services) {
3049
3154
  const files = await loadSourceFiles(service.dir);
3050
3155
  const seenTargets = /* @__PURE__ */ new Map();
3051
3156
  for (const file of files) {
3052
- const parser = import_node_path18.default.extname(file.path) === ".py" ? pyParser : jsParser;
3157
+ const parser = import_node_path19.default.extname(file.path) === ".py" ? pyParser : jsParser;
3053
3158
  let targets;
3054
3159
  try {
3055
3160
  targets = callsFromSource(file.content, parser, knownHosts);
@@ -3077,7 +3182,7 @@ async function addHttpCallEdges(graph, services) {
3077
3182
  type: import_types9.EdgeType.CALLS,
3078
3183
  provenance: import_types9.Provenance.EXTRACTED,
3079
3184
  evidence: {
3080
- file: import_node_path18.default.relative(service.dir, evidenceFile.file),
3185
+ file: import_node_path19.default.relative(service.dir, evidenceFile.file),
3081
3186
  line,
3082
3187
  snippet: snippet(fileContent, line)
3083
3188
  }
@@ -3093,7 +3198,7 @@ async function addHttpCallEdges(graph, services) {
3093
3198
 
3094
3199
  // src/extract/calls/kafka.ts
3095
3200
  init_cjs_shims();
3096
- var import_node_path19 = __toESM(require("path"), 1);
3201
+ var import_node_path20 = __toESM(require("path"), 1);
3097
3202
  var import_types10 = require("@neat.is/types");
3098
3203
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
3099
3204
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -3120,7 +3225,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3120
3225
  kind: "kafka-topic",
3121
3226
  edgeType,
3122
3227
  evidence: {
3123
- file: import_node_path19.default.relative(serviceDir, file.path),
3228
+ file: import_node_path20.default.relative(serviceDir, file.path),
3124
3229
  line,
3125
3230
  snippet: snippet(file.content, line)
3126
3231
  }
@@ -3133,7 +3238,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3133
3238
 
3134
3239
  // src/extract/calls/redis.ts
3135
3240
  init_cjs_shims();
3136
- var import_node_path20 = __toESM(require("path"), 1);
3241
+ var import_node_path21 = __toESM(require("path"), 1);
3137
3242
  var import_types11 = require("@neat.is/types");
3138
3243
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
3139
3244
  function redisEndpointsFromFile(file, serviceDir) {
@@ -3152,7 +3257,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3152
3257
  kind: "redis",
3153
3258
  edgeType: "CALLS",
3154
3259
  evidence: {
3155
- file: import_node_path20.default.relative(serviceDir, file.path),
3260
+ file: import_node_path21.default.relative(serviceDir, file.path),
3156
3261
  line,
3157
3262
  snippet: snippet(file.content, line)
3158
3263
  }
@@ -3163,7 +3268,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3163
3268
 
3164
3269
  // src/extract/calls/aws.ts
3165
3270
  init_cjs_shims();
3166
- var import_node_path21 = __toESM(require("path"), 1);
3271
+ var import_node_path22 = __toESM(require("path"), 1);
3167
3272
  var import_types12 = require("@neat.is/types");
3168
3273
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
3169
3274
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -3193,7 +3298,7 @@ function awsEndpointsFromFile(file, serviceDir) {
3193
3298
  kind,
3194
3299
  edgeType: "CALLS",
3195
3300
  evidence: {
3196
- file: import_node_path21.default.relative(serviceDir, file.path),
3301
+ file: import_node_path22.default.relative(serviceDir, file.path),
3197
3302
  line,
3198
3303
  snippet: snippet(file.content, line)
3199
3304
  }
@@ -3218,7 +3323,7 @@ function awsEndpointsFromFile(file, serviceDir) {
3218
3323
 
3219
3324
  // src/extract/calls/grpc.ts
3220
3325
  init_cjs_shims();
3221
- var import_node_path22 = __toESM(require("path"), 1);
3326
+ var import_node_path23 = __toESM(require("path"), 1);
3222
3327
  var import_types13 = require("@neat.is/types");
3223
3328
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
3224
3329
  function isLikelyAddress(value) {
@@ -3243,7 +3348,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
3243
3348
  kind: "grpc-service",
3244
3349
  edgeType: "CALLS",
3245
3350
  evidence: {
3246
- file: import_node_path22.default.relative(serviceDir, file.path),
3351
+ file: import_node_path23.default.relative(serviceDir, file.path),
3247
3352
  line,
3248
3353
  snippet: snippet(file.content, line)
3249
3354
  }
@@ -3323,7 +3428,7 @@ init_cjs_shims();
3323
3428
 
3324
3429
  // src/extract/infra/docker-compose.ts
3325
3430
  init_cjs_shims();
3326
- var import_node_path23 = __toESM(require("path"), 1);
3431
+ var import_node_path24 = __toESM(require("path"), 1);
3327
3432
  var import_types16 = require("@neat.is/types");
3328
3433
 
3329
3434
  // src/extract/infra/shared.ts
@@ -3361,7 +3466,7 @@ function dependsOnList(value) {
3361
3466
  }
3362
3467
  function serviceNameToServiceNode(name, services) {
3363
3468
  for (const s of services) {
3364
- if (s.node.name === name || import_node_path23.default.basename(s.dir) === name) return s.node.id;
3469
+ if (s.node.name === name || import_node_path24.default.basename(s.dir) === name) return s.node.id;
3365
3470
  }
3366
3471
  return null;
3367
3472
  }
@@ -3370,16 +3475,24 @@ async function addComposeInfra(graph, scanPath, services) {
3370
3475
  let edgesAdded = 0;
3371
3476
  let composePath = null;
3372
3477
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3373
- const abs = import_node_path23.default.join(scanPath, name);
3478
+ const abs = import_node_path24.default.join(scanPath, name);
3374
3479
  if (await exists(abs)) {
3375
3480
  composePath = abs;
3376
3481
  break;
3377
3482
  }
3378
3483
  }
3379
3484
  if (!composePath) return { nodesAdded, edgesAdded };
3380
- const compose = await readYaml(composePath);
3485
+ let compose;
3486
+ try {
3487
+ compose = await readYaml(composePath);
3488
+ } catch (err) {
3489
+ console.warn(
3490
+ `[neat] infra docker-compose skipped ${import_node_path24.default.relative(scanPath, composePath)}: ${err.message}`
3491
+ );
3492
+ return { nodesAdded, edgesAdded };
3493
+ }
3381
3494
  if (!compose?.services) return { nodesAdded, edgesAdded };
3382
- const evidenceFile = import_node_path23.default.relative(scanPath, composePath).split(import_node_path23.default.sep).join("/");
3495
+ const evidenceFile = import_node_path24.default.relative(scanPath, composePath).split(import_node_path24.default.sep).join("/");
3383
3496
  const composeNameToNodeId = /* @__PURE__ */ new Map();
3384
3497
  for (const [composeName, svc] of Object.entries(compose.services)) {
3385
3498
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -3420,8 +3533,8 @@ async function addComposeInfra(graph, scanPath, services) {
3420
3533
 
3421
3534
  // src/extract/infra/dockerfile.ts
3422
3535
  init_cjs_shims();
3423
- var import_node_path24 = __toESM(require("path"), 1);
3424
- var import_node_fs12 = require("fs");
3536
+ var import_node_path25 = __toESM(require("path"), 1);
3537
+ var import_node_fs13 = require("fs");
3425
3538
  var import_types17 = require("@neat.is/types");
3426
3539
  function runtimeImage(content) {
3427
3540
  const lines = content.split("\n");
@@ -3441,9 +3554,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3441
3554
  let nodesAdded = 0;
3442
3555
  let edgesAdded = 0;
3443
3556
  for (const service of services) {
3444
- const dockerfilePath = import_node_path24.default.join(service.dir, "Dockerfile");
3557
+ const dockerfilePath = import_node_path25.default.join(service.dir, "Dockerfile");
3445
3558
  if (!await exists(dockerfilePath)) continue;
3446
- const content = await import_node_fs12.promises.readFile(dockerfilePath, "utf8");
3559
+ let content;
3560
+ try {
3561
+ content = await import_node_fs13.promises.readFile(dockerfilePath, "utf8");
3562
+ } catch (err) {
3563
+ console.warn(
3564
+ `[neat] infra dockerfile skipped ${import_node_path25.default.relative(scanPath, dockerfilePath)}: ${err.message}`
3565
+ );
3566
+ continue;
3567
+ }
3447
3568
  const image = runtimeImage(content);
3448
3569
  if (!image) continue;
3449
3570
  const node = makeInfraNode("container-image", image);
@@ -3460,7 +3581,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3460
3581
  type: import_types17.EdgeType.RUNS_ON,
3461
3582
  provenance: import_types17.Provenance.EXTRACTED,
3462
3583
  evidence: {
3463
- file: import_node_path24.default.relative(scanPath, dockerfilePath).split(import_node_path24.default.sep).join("/")
3584
+ file: import_node_path25.default.relative(scanPath, dockerfilePath).split(import_node_path25.default.sep).join("/")
3464
3585
  }
3465
3586
  };
3466
3587
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3472,19 +3593,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3472
3593
 
3473
3594
  // src/extract/infra/terraform.ts
3474
3595
  init_cjs_shims();
3475
- var import_node_fs13 = require("fs");
3476
- var import_node_path25 = __toESM(require("path"), 1);
3596
+ var import_node_fs14 = require("fs");
3597
+ var import_node_path26 = __toESM(require("path"), 1);
3477
3598
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
3478
3599
  async function walkTfFiles(start, depth = 0, max = 5) {
3479
3600
  if (depth > max) return [];
3480
3601
  const out = [];
3481
- const entries = await import_node_fs13.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3602
+ const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3482
3603
  for (const entry of entries) {
3483
3604
  if (entry.isDirectory()) {
3484
3605
  if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
3485
- out.push(...await walkTfFiles(import_node_path25.default.join(start, entry.name), depth + 1, max));
3606
+ out.push(...await walkTfFiles(import_node_path26.default.join(start, entry.name), depth + 1, max));
3486
3607
  } else if (entry.isFile() && entry.name.endsWith(".tf")) {
3487
- out.push(import_node_path25.default.join(start, entry.name));
3608
+ out.push(import_node_path26.default.join(start, entry.name));
3488
3609
  }
3489
3610
  }
3490
3611
  return out;
@@ -3493,7 +3614,7 @@ async function addTerraformResources(graph, scanPath) {
3493
3614
  let nodesAdded = 0;
3494
3615
  const files = await walkTfFiles(scanPath);
3495
3616
  for (const file of files) {
3496
- const content = await import_node_fs13.promises.readFile(file, "utf8");
3617
+ const content = await import_node_fs14.promises.readFile(file, "utf8");
3497
3618
  RESOURCE_RE.lastIndex = 0;
3498
3619
  let m;
3499
3620
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -3511,8 +3632,8 @@ async function addTerraformResources(graph, scanPath) {
3511
3632
 
3512
3633
  // src/extract/infra/k8s.ts
3513
3634
  init_cjs_shims();
3514
- var import_node_fs14 = require("fs");
3515
- var import_node_path26 = __toESM(require("path"), 1);
3635
+ var import_node_fs15 = require("fs");
3636
+ var import_node_path27 = __toESM(require("path"), 1);
3516
3637
  var import_yaml3 = require("yaml");
3517
3638
  var K8S_KIND_TO_INFRA_KIND = {
3518
3639
  Service: "k8s-service",
@@ -3526,13 +3647,13 @@ var K8S_KIND_TO_INFRA_KIND = {
3526
3647
  async function walkYamlFiles2(start, depth = 0, max = 5) {
3527
3648
  if (depth > max) return [];
3528
3649
  const out = [];
3529
- const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3650
+ const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3530
3651
  for (const entry of entries) {
3531
3652
  if (entry.isDirectory()) {
3532
3653
  if (IGNORED_DIRS.has(entry.name)) continue;
3533
- out.push(...await walkYamlFiles2(import_node_path26.default.join(start, entry.name), depth + 1, max));
3534
- } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path26.default.extname(entry.name))) {
3535
- out.push(import_node_path26.default.join(start, entry.name));
3654
+ out.push(...await walkYamlFiles2(import_node_path27.default.join(start, entry.name), depth + 1, max));
3655
+ } else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path27.default.extname(entry.name))) {
3656
+ out.push(import_node_path27.default.join(start, entry.name));
3536
3657
  }
3537
3658
  }
3538
3659
  return out;
@@ -3541,7 +3662,7 @@ async function addK8sResources(graph, scanPath) {
3541
3662
  let nodesAdded = 0;
3542
3663
  const files = await walkYamlFiles2(scanPath);
3543
3664
  for (const file of files) {
3544
- const content = await import_node_fs14.promises.readFile(file, "utf8");
3665
+ const content = await import_node_fs15.promises.readFile(file, "utf8");
3545
3666
  let docs;
3546
3667
  try {
3547
3668
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -3607,7 +3728,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3607
3728
 
3608
3729
  // src/diff.ts
3609
3730
  init_cjs_shims();
3610
- var import_node_fs15 = require("fs");
3731
+ var import_node_fs16 = require("fs");
3611
3732
  async function loadSnapshotForDiff(target) {
3612
3733
  if (/^https?:\/\//i.test(target)) {
3613
3734
  const res = await fetch(target);
@@ -3616,7 +3737,7 @@ async function loadSnapshotForDiff(target) {
3616
3737
  }
3617
3738
  return await res.json();
3618
3739
  }
3619
- const raw = await import_node_fs15.promises.readFile(target, "utf8");
3740
+ const raw = await import_node_fs16.promises.readFile(target, "utf8");
3620
3741
  return JSON.parse(raw);
3621
3742
  }
3622
3743
  function indexEntries(entries) {
@@ -3684,23 +3805,23 @@ function canonicalJson(value) {
3684
3805
 
3685
3806
  // src/projects.ts
3686
3807
  init_cjs_shims();
3687
- var import_node_path27 = __toESM(require("path"), 1);
3808
+ var import_node_path28 = __toESM(require("path"), 1);
3688
3809
  function pathsForProject(project, baseDir) {
3689
3810
  if (project === DEFAULT_PROJECT) {
3690
3811
  return {
3691
- snapshotPath: import_node_path27.default.join(baseDir, "graph.json"),
3692
- errorsPath: import_node_path27.default.join(baseDir, "errors.ndjson"),
3693
- staleEventsPath: import_node_path27.default.join(baseDir, "stale-events.ndjson"),
3694
- embeddingsCachePath: import_node_path27.default.join(baseDir, "embeddings.json"),
3695
- policyViolationsPath: import_node_path27.default.join(baseDir, "policy-violations.ndjson")
3812
+ snapshotPath: import_node_path28.default.join(baseDir, "graph.json"),
3813
+ errorsPath: import_node_path28.default.join(baseDir, "errors.ndjson"),
3814
+ staleEventsPath: import_node_path28.default.join(baseDir, "stale-events.ndjson"),
3815
+ embeddingsCachePath: import_node_path28.default.join(baseDir, "embeddings.json"),
3816
+ policyViolationsPath: import_node_path28.default.join(baseDir, "policy-violations.ndjson")
3696
3817
  };
3697
3818
  }
3698
3819
  return {
3699
- snapshotPath: import_node_path27.default.join(baseDir, `${project}.json`),
3700
- errorsPath: import_node_path27.default.join(baseDir, `errors.${project}.ndjson`),
3701
- staleEventsPath: import_node_path27.default.join(baseDir, `stale-events.${project}.ndjson`),
3702
- embeddingsCachePath: import_node_path27.default.join(baseDir, `embeddings.${project}.json`),
3703
- policyViolationsPath: import_node_path27.default.join(baseDir, `policy-violations.${project}.ndjson`)
3820
+ snapshotPath: import_node_path28.default.join(baseDir, `${project}.json`),
3821
+ errorsPath: import_node_path28.default.join(baseDir, `errors.${project}.ndjson`),
3822
+ staleEventsPath: import_node_path28.default.join(baseDir, `stale-events.${project}.ndjson`),
3823
+ embeddingsCachePath: import_node_path28.default.join(baseDir, `embeddings.${project}.json`),
3824
+ policyViolationsPath: import_node_path28.default.join(baseDir, `policy-violations.${project}.ndjson`)
3704
3825
  };
3705
3826
  }
3706
3827
  var Projects = class {
@@ -3740,23 +3861,23 @@ function parseExtraProjects(raw) {
3740
3861
 
3741
3862
  // src/registry.ts
3742
3863
  init_cjs_shims();
3743
- var import_node_fs16 = require("fs");
3864
+ var import_node_fs17 = require("fs");
3744
3865
  var import_node_os2 = __toESM(require("os"), 1);
3745
- var import_node_path28 = __toESM(require("path"), 1);
3866
+ var import_node_path29 = __toESM(require("path"), 1);
3746
3867
  var import_types18 = require("@neat.is/types");
3747
3868
  function neatHome() {
3748
3869
  const override = process.env.NEAT_HOME;
3749
- if (override && override.length > 0) return import_node_path28.default.resolve(override);
3750
- return import_node_path28.default.join(import_node_os2.default.homedir(), ".neat");
3870
+ if (override && override.length > 0) return import_node_path29.default.resolve(override);
3871
+ return import_node_path29.default.join(import_node_os2.default.homedir(), ".neat");
3751
3872
  }
3752
3873
  function registryPath() {
3753
- return import_node_path28.default.join(neatHome(), "projects.json");
3874
+ return import_node_path29.default.join(neatHome(), "projects.json");
3754
3875
  }
3755
3876
  async function readRegistry() {
3756
3877
  const file = registryPath();
3757
3878
  let raw;
3758
3879
  try {
3759
- raw = await import_node_fs16.promises.readFile(file, "utf8");
3880
+ raw = await import_node_fs17.promises.readFile(file, "utf8");
3760
3881
  } catch (err) {
3761
3882
  if (err.code === "ENOENT") {
3762
3883
  return { version: 1, projects: [] };
@@ -4192,8 +4313,8 @@ async function buildApi(opts) {
4192
4313
 
4193
4314
  // src/persist.ts
4194
4315
  init_cjs_shims();
4195
- var import_node_fs17 = require("fs");
4196
- var import_node_path29 = __toESM(require("path"), 1);
4316
+ var import_node_fs18 = require("fs");
4317
+ var import_node_path30 = __toESM(require("path"), 1);
4197
4318
  var SCHEMA_VERSION = 2;
4198
4319
  function migrateV1ToV2(payload) {
4199
4320
  const nodes = payload.graph.nodes;
@@ -4207,7 +4328,7 @@ function migrateV1ToV2(payload) {
4207
4328
  return { ...payload, schemaVersion: 2 };
4208
4329
  }
4209
4330
  async function ensureDir(filePath) {
4210
- await import_node_fs17.promises.mkdir(import_node_path29.default.dirname(filePath), { recursive: true });
4331
+ await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(filePath), { recursive: true });
4211
4332
  }
4212
4333
  async function saveGraphToDisk(graph, outPath) {
4213
4334
  await ensureDir(outPath);
@@ -4217,13 +4338,13 @@ async function saveGraphToDisk(graph, outPath) {
4217
4338
  graph: graph.export()
4218
4339
  };
4219
4340
  const tmp = `${outPath}.tmp`;
4220
- await import_node_fs17.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
4221
- await import_node_fs17.promises.rename(tmp, outPath);
4341
+ await import_node_fs18.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
4342
+ await import_node_fs18.promises.rename(tmp, outPath);
4222
4343
  }
4223
4344
  async function loadGraphFromDisk(graph, outPath) {
4224
4345
  let raw;
4225
4346
  try {
4226
- raw = await import_node_fs17.promises.readFile(outPath, "utf8");
4347
+ raw = await import_node_fs18.promises.readFile(outPath, "utf8");
4227
4348
  } catch (err) {
4228
4349
  if (err.code === "ENOENT") return;
4229
4350
  throw err;
@@ -4280,8 +4401,8 @@ init_otel_grpc();
4280
4401
 
4281
4402
  // src/search.ts
4282
4403
  init_cjs_shims();
4283
- var import_node_fs18 = require("fs");
4284
- var import_node_path32 = __toESM(require("path"), 1);
4404
+ var import_node_fs19 = require("fs");
4405
+ var import_node_path33 = __toESM(require("path"), 1);
4285
4406
  var import_node_crypto = require("crypto");
4286
4407
  var DEFAULT_LIMIT = 10;
4287
4408
  var NOMIC_DIM = 768;
@@ -4411,7 +4532,7 @@ async function pickEmbedder() {
4411
4532
  }
4412
4533
  async function readCache(cachePath) {
4413
4534
  try {
4414
- const raw = await import_node_fs18.promises.readFile(cachePath, "utf8");
4535
+ const raw = await import_node_fs19.promises.readFile(cachePath, "utf8");
4415
4536
  const parsed = JSON.parse(raw);
4416
4537
  if (parsed.version !== 1) return null;
4417
4538
  return parsed;
@@ -4420,8 +4541,8 @@ async function readCache(cachePath) {
4420
4541
  }
4421
4542
  }
4422
4543
  async function writeCache(cachePath, cache) {
4423
- await import_node_fs18.promises.mkdir(import_node_path32.default.dirname(cachePath), { recursive: true });
4424
- await import_node_fs18.promises.writeFile(cachePath, JSON.stringify(cache));
4544
+ await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(cachePath), { recursive: true });
4545
+ await import_node_fs19.promises.writeFile(cachePath, JSON.stringify(cache));
4425
4546
  }
4426
4547
  var VectorIndex = class {
4427
4548
  constructor(embedder, cachePath) {
@@ -4600,14 +4721,14 @@ async function bootProject(registry, name, scanPath, baseDir) {
4600
4721
  async function main() {
4601
4722
  const baseDirEnv = process.env.NEAT_OUT_DIR;
4602
4723
  const legacyOutPath = process.env.NEAT_OUT_PATH;
4603
- const baseDir = baseDirEnv ? import_node_path33.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path33.default.resolve(import_node_path33.default.dirname(legacyOutPath)) : import_node_path33.default.resolve("./neat-out");
4604
- const defaultScanPath = import_node_path33.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
4724
+ const baseDir = baseDirEnv ? import_node_path34.default.resolve(baseDirEnv) : legacyOutPath ? import_node_path34.default.resolve(import_node_path34.default.dirname(legacyOutPath)) : import_node_path34.default.resolve("./neat-out");
4725
+ const defaultScanPath = import_node_path34.default.resolve(process.env.NEAT_SCAN_PATH ?? "./demo");
4605
4726
  const registry = new Projects();
4606
4727
  await bootProject(registry, DEFAULT_PROJECT, defaultScanPath, baseDir);
4607
4728
  for (const name of parseExtraProjects(process.env.NEAT_PROJECTS)) {
4608
4729
  const envKey = `NEAT_PROJECT_SCAN_PATH_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
4609
4730
  const projectScan = process.env[envKey];
4610
- await bootProject(registry, name, projectScan ? import_node_path33.default.resolve(projectScan) : void 0, baseDir);
4731
+ await bootProject(registry, name, projectScan ? import_node_path34.default.resolve(projectScan) : void 0, baseDir);
4611
4732
  }
4612
4733
  const host = process.env.HOST ?? "0.0.0.0";
4613
4734
  const port = Number(process.env.PORT ?? 8080);