@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/cli.cjs CHANGED
@@ -101,8 +101,8 @@ function reshapeGrpcRequest(req) {
101
101
  };
102
102
  }
103
103
  function resolveProtoRoot() {
104
- const here = import_node_path30.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
105
- return import_node_path30.default.resolve(here, "..", "proto");
104
+ const here = import_node_path31.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
105
+ return import_node_path31.default.resolve(here, "..", "proto");
106
106
  }
107
107
  function loadTraceService() {
108
108
  const protoRoot = resolveProtoRoot();
@@ -157,13 +157,13 @@ async function startOtelGrpcReceiver(opts) {
157
157
  })
158
158
  };
159
159
  }
160
- var import_node_url, import_node_path30, grpc, protoLoader;
160
+ var import_node_url, import_node_path31, grpc, protoLoader;
161
161
  var init_otel_grpc = __esm({
162
162
  "src/otel-grpc.ts"() {
163
163
  "use strict";
164
164
  init_cjs_shims();
165
165
  import_node_url = require("url");
166
- import_node_path30 = __toESM(require("path"), 1);
166
+ import_node_path31 = __toESM(require("path"), 1);
167
167
  grpc = __toESM(require("@grpc/grpc-js"), 1);
168
168
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
169
169
  init_otel();
@@ -260,10 +260,10 @@ function parseOtlpRequest(body) {
260
260
  }
261
261
  function loadProtobufDecoder() {
262
262
  if (exportTraceServiceRequestType) return exportTraceServiceRequestType;
263
- const here = import_node_path31.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
264
- const protoRoot = import_node_path31.default.resolve(here, "..", "proto");
263
+ const here = import_node_path32.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
264
+ const protoRoot = import_node_path32.default.resolve(here, "..", "proto");
265
265
  const root = new import_protobufjs.default.Root();
266
- root.resolvePath = (_origin, target) => import_node_path31.default.resolve(protoRoot, target);
266
+ root.resolvePath = (_origin, target) => import_node_path32.default.resolve(protoRoot, target);
267
267
  root.loadSync(
268
268
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
269
269
  { keepCase: true }
@@ -355,12 +355,12 @@ async function buildOtelReceiver(opts) {
355
355
  };
356
356
  return decorated;
357
357
  }
358
- var import_node_path31, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
358
+ var import_node_path32, import_node_url2, import_fastify2, import_protobufjs, exportTraceServiceRequestType;
359
359
  var init_otel = __esm({
360
360
  "src/otel.ts"() {
361
361
  "use strict";
362
362
  init_cjs_shims();
363
- import_node_path31 = __toESM(require("path"), 1);
363
+ import_node_path32 = __toESM(require("path"), 1);
364
364
  import_node_url2 = require("url");
365
365
  import_fastify2 = __toESM(require("fastify"), 1);
366
366
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -380,8 +380,8 @@ __export(cli_exports, {
380
380
  });
381
381
  module.exports = __toCommonJS(cli_exports);
382
382
  init_cjs_shims();
383
- var import_node_path36 = __toESM(require("path"), 1);
384
- var import_node_fs21 = require("fs");
383
+ var import_node_path37 = __toESM(require("path"), 1);
384
+ var import_node_fs22 = require("fs");
385
385
 
386
386
  // src/graph.ts
387
387
  init_cjs_shims();
@@ -887,19 +887,19 @@ function confidenceFromMix(edges, now = Date.now()) {
887
887
  function longestIncomingWalk(graph, start, maxDepth) {
888
888
  let best = { path: [start], edges: [] };
889
889
  const visited = /* @__PURE__ */ new Set([start]);
890
- function step(node, path37, edges) {
891
- if (path37.length > best.path.length) {
892
- best = { path: [...path37], edges: [...edges] };
890
+ function step(node, path38, edges) {
891
+ if (path38.length > best.path.length) {
892
+ best = { path: [...path38], edges: [...edges] };
893
893
  }
894
- if (path37.length - 1 >= maxDepth) return;
894
+ if (path38.length - 1 >= maxDepth) return;
895
895
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
896
896
  for (const [srcId, edge] of incoming) {
897
897
  if (visited.has(srcId)) continue;
898
898
  visited.add(srcId);
899
- path37.push(srcId);
899
+ path38.push(srcId);
900
900
  edges.push(edge);
901
- step(srcId, path37, edges);
902
- path37.pop();
901
+ step(srcId, path38, edges);
902
+ path38.pop();
903
903
  edges.pop();
904
904
  visited.delete(srcId);
905
905
  }
@@ -1936,10 +1936,10 @@ async function readErrorEvents(errorsPath) {
1936
1936
 
1937
1937
  // src/extract/services.ts
1938
1938
  init_cjs_shims();
1939
- var import_node_fs6 = require("fs");
1940
- var import_node_path6 = __toESM(require("path"), 1);
1939
+ var import_node_fs7 = require("fs");
1940
+ var import_node_path7 = __toESM(require("path"), 1);
1941
1941
  var import_ignore = __toESM(require("ignore"), 1);
1942
- var import_minimatch = require("minimatch");
1942
+ var import_minimatch2 = require("minimatch");
1943
1943
  var import_types5 = require("@neat.is/types");
1944
1944
 
1945
1945
  // src/extract/shared.ts
@@ -2052,6 +2052,73 @@ function pythonToPackage(service) {
2052
2052
  };
2053
2053
  }
2054
2054
 
2055
+ // src/extract/owners.ts
2056
+ init_cjs_shims();
2057
+ var import_node_fs6 = require("fs");
2058
+ var import_node_path6 = __toESM(require("path"), 1);
2059
+ var import_minimatch = require("minimatch");
2060
+ async function loadCodeowners(scanPath) {
2061
+ const candidates = [
2062
+ import_node_path6.default.join(scanPath, "CODEOWNERS"),
2063
+ import_node_path6.default.join(scanPath, ".github", "CODEOWNERS")
2064
+ ];
2065
+ for (const file of candidates) {
2066
+ if (await exists(file)) {
2067
+ const raw = await import_node_fs6.promises.readFile(file, "utf8");
2068
+ return parseCodeowners(raw);
2069
+ }
2070
+ }
2071
+ return null;
2072
+ }
2073
+ function parseCodeowners(raw) {
2074
+ const rules = [];
2075
+ for (const line of raw.split("\n")) {
2076
+ const trimmed = line.trim();
2077
+ if (!trimmed || trimmed.startsWith("#")) continue;
2078
+ const match = /^(\S+)\s+(.+)$/.exec(trimmed);
2079
+ if (!match) continue;
2080
+ rules.push({ pattern: match[1], owners: match[2].trim() });
2081
+ }
2082
+ return { rules };
2083
+ }
2084
+ function matchOwner(file, repoPath) {
2085
+ const normalized = repoPath.split(import_node_path6.default.sep).join("/");
2086
+ for (const rule of file.rules) {
2087
+ if (matchesPattern(rule.pattern, normalized)) return rule.owners;
2088
+ }
2089
+ return null;
2090
+ }
2091
+ function matchesPattern(rawPattern, repoPath) {
2092
+ let pattern = rawPattern.startsWith("/") ? rawPattern.slice(1) : rawPattern;
2093
+ if (pattern === "*") return !repoPath.includes("/");
2094
+ if (pattern === "**" || pattern === "") return true;
2095
+ if (pattern.endsWith("/")) pattern = pattern + "**";
2096
+ if ((0, import_minimatch.minimatch)(repoPath, pattern, { dot: true })) return true;
2097
+ if (!pattern.includes("*") && (0, import_minimatch.minimatch)(repoPath, pattern + "/**", { dot: true })) return true;
2098
+ return false;
2099
+ }
2100
+ async function readPackageJsonAuthor(serviceDir) {
2101
+ const pkgPath = import_node_path6.default.join(serviceDir, "package.json");
2102
+ if (!await exists(pkgPath)) return null;
2103
+ try {
2104
+ const pkg = await readJson(pkgPath);
2105
+ if (!pkg.author) return null;
2106
+ if (typeof pkg.author === "string") return pkg.author;
2107
+ if (typeof pkg.author === "object" && typeof pkg.author.name === "string") return pkg.author.name;
2108
+ return null;
2109
+ } catch {
2110
+ return null;
2111
+ }
2112
+ }
2113
+ async function computeServiceOwner(codeowners, repoPath, serviceDir) {
2114
+ if (codeowners && repoPath !== void 0) {
2115
+ const owner = matchOwner(codeowners, repoPath);
2116
+ if (owner) return owner;
2117
+ }
2118
+ const author = await readPackageJsonAuthor(serviceDir);
2119
+ return author ?? void 0;
2120
+ }
2121
+
2055
2122
  // src/extract/services.ts
2056
2123
  var DEFAULT_SCAN_DEPTH = 5;
2057
2124
  function parseScanDepth() {
@@ -2068,21 +2135,21 @@ function workspaceGlobs(pkg) {
2068
2135
  return null;
2069
2136
  }
2070
2137
  async function loadGitignore(scanPath) {
2071
- const gitignorePath = import_node_path6.default.join(scanPath, ".gitignore");
2138
+ const gitignorePath = import_node_path7.default.join(scanPath, ".gitignore");
2072
2139
  if (!await exists(gitignorePath)) return null;
2073
- const raw = await import_node_fs6.promises.readFile(gitignorePath, "utf8");
2140
+ const raw = await import_node_fs7.promises.readFile(gitignorePath, "utf8");
2074
2141
  return (0, import_ignore.default)().add(raw);
2075
2142
  }
2076
2143
  async function walkDirs(start, scanPath, options, visit) {
2077
2144
  async function recurse(current, depth) {
2078
2145
  if (depth > options.maxDepth) return;
2079
- const entries = await import_node_fs6.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2146
+ const entries = await import_node_fs7.promises.readdir(current, { withFileTypes: true }).catch(() => []);
2080
2147
  for (const entry2 of entries) {
2081
2148
  if (!entry2.isDirectory()) continue;
2082
2149
  if (IGNORED_DIRS.has(entry2.name)) continue;
2083
- const child = import_node_path6.default.join(current, entry2.name);
2150
+ const child = import_node_path7.default.join(current, entry2.name);
2084
2151
  if (options.ig) {
2085
- const rel = import_node_path6.default.relative(scanPath, child).split(import_node_path6.default.sep).join("/");
2152
+ const rel = import_node_path7.default.relative(scanPath, child).split(import_node_path7.default.sep).join("/");
2086
2153
  if (rel && options.ig.ignores(rel + "/")) continue;
2087
2154
  }
2088
2155
  await visit(child);
@@ -2097,8 +2164,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2097
2164
  for (const raw of globs) {
2098
2165
  const pattern = raw.replace(/^\.\//, "");
2099
2166
  if (!pattern.includes("*")) {
2100
- const candidate = import_node_path6.default.join(scanPath, pattern);
2101
- if (await exists(import_node_path6.default.join(candidate, "package.json"))) found.add(candidate);
2167
+ const candidate = import_node_path7.default.join(scanPath, pattern);
2168
+ if (await exists(import_node_path7.default.join(candidate, "package.json"))) found.add(candidate);
2102
2169
  continue;
2103
2170
  }
2104
2171
  const segments = pattern.split("/");
@@ -2107,13 +2174,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2107
2174
  if (seg.includes("*")) break;
2108
2175
  staticSegments.push(seg);
2109
2176
  }
2110
- const start = import_node_path6.default.join(scanPath, ...staticSegments);
2177
+ const start = import_node_path7.default.join(scanPath, ...staticSegments);
2111
2178
  if (!await exists(start)) continue;
2112
2179
  const hasDoubleStar = pattern.includes("**");
2113
2180
  const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
2114
2181
  await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
2115
- const rel = import_node_path6.default.relative(scanPath, dir).split(import_node_path6.default.sep).join("/");
2116
- if ((0, import_minimatch.minimatch)(rel, pattern) && await exists(import_node_path6.default.join(dir, "package.json"))) {
2182
+ const rel = import_node_path7.default.relative(scanPath, dir).split(import_node_path7.default.sep).join("/");
2183
+ if ((0, import_minimatch2.minimatch)(rel, pattern) && await exists(import_node_path7.default.join(dir, "package.json"))) {
2117
2184
  found.add(dir);
2118
2185
  }
2119
2186
  });
@@ -2121,9 +2188,17 @@ async function expandWorkspaceGlobs(scanPath, globs) {
2121
2188
  return [...found];
2122
2189
  }
2123
2190
  async function discoverNodeService(scanPath, dir) {
2124
- const pkgPath = import_node_path6.default.join(dir, "package.json");
2191
+ const pkgPath = import_node_path7.default.join(dir, "package.json");
2125
2192
  if (!await exists(pkgPath)) return null;
2126
- const pkg = await readJson(pkgPath);
2193
+ let pkg;
2194
+ try {
2195
+ pkg = await readJson(pkgPath);
2196
+ } catch (err) {
2197
+ console.warn(
2198
+ `[neat] services skipped ${import_node_path7.default.relative(scanPath, pkgPath)}: ${err.message}`
2199
+ );
2200
+ return null;
2201
+ }
2127
2202
  if (!pkg.name) return null;
2128
2203
  const node = {
2129
2204
  id: (0, import_types5.serviceId)(pkg.name),
@@ -2132,7 +2207,7 @@ async function discoverNodeService(scanPath, dir) {
2132
2207
  language: "javascript",
2133
2208
  version: pkg.version,
2134
2209
  dependencies: pkg.dependencies ?? {},
2135
- repoPath: import_node_path6.default.relative(scanPath, dir),
2210
+ repoPath: import_node_path7.default.relative(scanPath, dir),
2136
2211
  ...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {}
2137
2212
  };
2138
2213
  return { pkg, dir, node };
@@ -2148,13 +2223,22 @@ async function discoverPyService(scanPath, dir) {
2148
2223
  language: "python",
2149
2224
  version: py.version,
2150
2225
  dependencies: py.dependencies,
2151
- repoPath: import_node_path6.default.relative(scanPath, dir)
2226
+ repoPath: import_node_path7.default.relative(scanPath, dir)
2152
2227
  };
2153
2228
  return { pkg, dir, node };
2154
2229
  }
2155
2230
  async function discoverServices(scanPath) {
2156
- const rootPkgPath = import_node_path6.default.join(scanPath, "package.json");
2157
- const rootPkg = await exists(rootPkgPath) ? await readJson(rootPkgPath) : null;
2231
+ const rootPkgPath = import_node_path7.default.join(scanPath, "package.json");
2232
+ let rootPkg = null;
2233
+ if (await exists(rootPkgPath)) {
2234
+ try {
2235
+ rootPkg = await readJson(rootPkgPath);
2236
+ } catch (err) {
2237
+ console.warn(
2238
+ `[neat] services workspaces skipped ${import_node_path7.default.relative(scanPath, rootPkgPath)}: ${err.message}`
2239
+ );
2240
+ }
2241
+ }
2158
2242
  const wsGlobs = rootPkg ? workspaceGlobs(rootPkg) : null;
2159
2243
  const candidateDirs = [];
2160
2244
  if (wsGlobs) {
@@ -2167,9 +2251,9 @@ async function discoverServices(scanPath) {
2167
2251
  scanPath,
2168
2252
  { maxDepth: parseScanDepth(), ig },
2169
2253
  async (dir) => {
2170
- if (await exists(import_node_path6.default.join(dir, "package.json"))) {
2254
+ if (await exists(import_node_path7.default.join(dir, "package.json"))) {
2171
2255
  candidateDirs.push(dir);
2172
- } 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"))) {
2256
+ } 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"))) {
2173
2257
  candidateDirs.push(dir);
2174
2258
  }
2175
2259
  }
@@ -2183,8 +2267,8 @@ async function discoverServices(scanPath) {
2183
2267
  if (!service) continue;
2184
2268
  const existingDir = seen.get(service.node.name);
2185
2269
  if (existingDir !== void 0) {
2186
- const a = import_node_path6.default.relative(scanPath, existingDir) || ".";
2187
- const b = import_node_path6.default.relative(scanPath, dir) || ".";
2270
+ const a = import_node_path7.default.relative(scanPath, existingDir) || ".";
2271
+ const b = import_node_path7.default.relative(scanPath, dir) || ".";
2188
2272
  console.warn(
2189
2273
  `[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
2190
2274
  );
@@ -2193,6 +2277,11 @@ async function discoverServices(scanPath) {
2193
2277
  seen.set(service.node.name, dir);
2194
2278
  out.push(service);
2195
2279
  }
2280
+ const codeowners = await loadCodeowners(scanPath);
2281
+ for (const service of out) {
2282
+ const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
2283
+ if (owner !== void 0) service.node.owner = owner;
2284
+ }
2196
2285
  return out;
2197
2286
  }
2198
2287
  function addServiceNodes(graph, services) {
@@ -2216,8 +2305,8 @@ function addServiceNodes(graph, services) {
2216
2305
 
2217
2306
  // src/extract/aliases.ts
2218
2307
  init_cjs_shims();
2219
- var import_node_path7 = __toESM(require("path"), 1);
2220
- var import_node_fs7 = require("fs");
2308
+ var import_node_path8 = __toESM(require("path"), 1);
2309
+ var import_node_fs8 = require("fs");
2221
2310
  var import_yaml2 = require("yaml");
2222
2311
  var import_types6 = require("@neat.is/types");
2223
2312
  var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
@@ -2244,21 +2333,29 @@ function indexServicesByName(services) {
2244
2333
  const map = /* @__PURE__ */ new Map();
2245
2334
  for (const s of services) {
2246
2335
  map.set(s.node.name, s.node.id);
2247
- map.set(import_node_path7.default.basename(s.dir), s.node.id);
2336
+ map.set(import_node_path8.default.basename(s.dir), s.node.id);
2248
2337
  }
2249
2338
  return map;
2250
2339
  }
2251
2340
  async function collectComposeAliases(graph, scanPath, serviceIndex) {
2252
2341
  let composePath = null;
2253
2342
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
2254
- const abs = import_node_path7.default.join(scanPath, name);
2343
+ const abs = import_node_path8.default.join(scanPath, name);
2255
2344
  if (await exists(abs)) {
2256
2345
  composePath = abs;
2257
2346
  break;
2258
2347
  }
2259
2348
  }
2260
2349
  if (!composePath) return;
2261
- const compose = await readYaml(composePath);
2350
+ let compose;
2351
+ try {
2352
+ compose = await readYaml(composePath);
2353
+ } catch (err) {
2354
+ console.warn(
2355
+ `[neat] aliases compose skipped ${import_node_path8.default.relative(scanPath, composePath)}: ${err.message}`
2356
+ );
2357
+ return;
2358
+ }
2262
2359
  if (!compose?.services) return;
2263
2360
  for (const [composeName, svc] of Object.entries(compose.services)) {
2264
2361
  const serviceId3 = serviceIndex.get(composeName);
@@ -2297,9 +2394,17 @@ function parseDockerfileLabels(content) {
2297
2394
  }
2298
2395
  async function collectDockerfileAliases(graph, services) {
2299
2396
  for (const service of services) {
2300
- const dockerfilePath = import_node_path7.default.join(service.dir, "Dockerfile");
2397
+ const dockerfilePath = import_node_path8.default.join(service.dir, "Dockerfile");
2301
2398
  if (!await exists(dockerfilePath)) continue;
2302
- const content = await import_node_fs7.promises.readFile(dockerfilePath, "utf8");
2399
+ let content;
2400
+ try {
2401
+ content = await import_node_fs8.promises.readFile(dockerfilePath, "utf8");
2402
+ } catch (err) {
2403
+ console.warn(
2404
+ `[neat] aliases dockerfile skipped ${dockerfilePath}: ${err.message}`
2405
+ );
2406
+ continue;
2407
+ }
2303
2408
  const aliases = parseDockerfileLabels(content);
2304
2409
  if (aliases.length > 0) addAliases(graph, service.node.id, aliases);
2305
2410
  }
@@ -2307,13 +2412,13 @@ async function collectDockerfileAliases(graph, services) {
2307
2412
  async function walkYamlFiles(start, depth = 0, max = 5) {
2308
2413
  if (depth > max) return [];
2309
2414
  const out = [];
2310
- const entries = await import_node_fs7.promises.readdir(start, { withFileTypes: true }).catch(() => []);
2415
+ const entries = await import_node_fs8.promises.readdir(start, { withFileTypes: true }).catch(() => []);
2311
2416
  for (const entry2 of entries) {
2312
2417
  if (entry2.isDirectory()) {
2313
2418
  if (IGNORED_DIRS.has(entry2.name)) continue;
2314
- out.push(...await walkYamlFiles(import_node_path7.default.join(start, entry2.name), depth + 1, max));
2315
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path7.default.extname(entry2.name))) {
2316
- out.push(import_node_path7.default.join(start, entry2.name));
2419
+ out.push(...await walkYamlFiles(import_node_path8.default.join(start, entry2.name), depth + 1, max));
2420
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path8.default.extname(entry2.name))) {
2421
+ out.push(import_node_path8.default.join(start, entry2.name));
2317
2422
  }
2318
2423
  }
2319
2424
  return out;
@@ -2340,7 +2445,7 @@ function k8sServiceTarget(doc, byName) {
2340
2445
  async function collectK8sAliases(graph, scanPath, serviceIndex) {
2341
2446
  const files = await walkYamlFiles(scanPath);
2342
2447
  for (const file of files) {
2343
- const content = await import_node_fs7.promises.readFile(file, "utf8");
2448
+ const content = await import_node_fs8.promises.readFile(file, "utf8");
2344
2449
  let docs;
2345
2450
  try {
2346
2451
  docs = (0, import_yaml2.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -2365,14 +2470,14 @@ async function addServiceAliases(graph, scanPath, services) {
2365
2470
 
2366
2471
  // src/extract/databases/index.ts
2367
2472
  init_cjs_shims();
2368
- var import_node_path15 = __toESM(require("path"), 1);
2473
+ var import_node_path16 = __toESM(require("path"), 1);
2369
2474
  var import_types7 = require("@neat.is/types");
2370
2475
 
2371
2476
  // src/extract/databases/db-config-yaml.ts
2372
2477
  init_cjs_shims();
2373
- var import_node_path8 = __toESM(require("path"), 1);
2478
+ var import_node_path9 = __toESM(require("path"), 1);
2374
2479
  async function parse(serviceDir) {
2375
- const yamlPath = import_node_path8.default.join(serviceDir, "db-config.yaml");
2480
+ const yamlPath = import_node_path9.default.join(serviceDir, "db-config.yaml");
2376
2481
  if (!await exists(yamlPath)) return [];
2377
2482
  const raw = await readYaml(yamlPath);
2378
2483
  return [
@@ -2390,13 +2495,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
2390
2495
 
2391
2496
  // src/extract/databases/dotenv.ts
2392
2497
  init_cjs_shims();
2393
- var import_node_fs9 = require("fs");
2394
- var import_node_path10 = __toESM(require("path"), 1);
2498
+ var import_node_fs10 = require("fs");
2499
+ var import_node_path11 = __toESM(require("path"), 1);
2395
2500
 
2396
2501
  // src/extract/databases/shared.ts
2397
2502
  init_cjs_shims();
2398
- var import_node_fs8 = require("fs");
2399
- var import_node_path9 = __toESM(require("path"), 1);
2503
+ var import_node_fs9 = require("fs");
2504
+ var import_node_path10 = __toESM(require("path"), 1);
2400
2505
  function schemeToEngine(scheme) {
2401
2506
  const s = scheme.toLowerCase().split("+")[0];
2402
2507
  switch (s) {
@@ -2435,14 +2540,14 @@ function parseConnectionString(url) {
2435
2540
  }
2436
2541
  async function readIfExists(filePath) {
2437
2542
  try {
2438
- return await import_node_fs8.promises.readFile(filePath, "utf8");
2543
+ return await import_node_fs9.promises.readFile(filePath, "utf8");
2439
2544
  } catch {
2440
2545
  return null;
2441
2546
  }
2442
2547
  }
2443
2548
  async function findFirst(serviceDir, candidates) {
2444
2549
  for (const rel of candidates) {
2445
- const abs = import_node_path9.default.join(serviceDir, rel);
2550
+ const abs = import_node_path10.default.join(serviceDir, rel);
2446
2551
  const content = await readIfExists(abs);
2447
2552
  if (content !== null) return abs;
2448
2553
  }
@@ -2493,15 +2598,15 @@ function parseDotenvLine(line) {
2493
2598
  return { key, value };
2494
2599
  }
2495
2600
  async function parse2(serviceDir) {
2496
- const entries = await import_node_fs9.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
2601
+ const entries = await import_node_fs10.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
2497
2602
  const configs = [];
2498
2603
  const seen = /* @__PURE__ */ new Set();
2499
2604
  for (const entry2 of entries) {
2500
2605
  if (!entry2.isFile()) continue;
2501
2606
  const match = isConfigFile(entry2.name);
2502
2607
  if (!match.match || match.fileType !== "env") continue;
2503
- const filePath = import_node_path10.default.join(serviceDir, entry2.name);
2504
- const content = await import_node_fs9.promises.readFile(filePath, "utf8");
2608
+ const filePath = import_node_path11.default.join(serviceDir, entry2.name);
2609
+ const content = await import_node_fs10.promises.readFile(filePath, "utf8");
2505
2610
  for (const line of content.split("\n")) {
2506
2611
  const parsed = parseDotenvLine(line);
2507
2612
  if (!parsed) continue;
@@ -2520,9 +2625,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
2520
2625
 
2521
2626
  // src/extract/databases/prisma.ts
2522
2627
  init_cjs_shims();
2523
- var import_node_path11 = __toESM(require("path"), 1);
2628
+ var import_node_path12 = __toESM(require("path"), 1);
2524
2629
  async function parse3(serviceDir) {
2525
- const schemaPath = import_node_path11.default.join(serviceDir, "prisma", "schema.prisma");
2630
+ const schemaPath = import_node_path12.default.join(serviceDir, "prisma", "schema.prisma");
2526
2631
  const content = await readIfExists(schemaPath);
2527
2632
  if (!content) return [];
2528
2633
  const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
@@ -2654,10 +2759,10 @@ var knexParser = { name: "knex", parse: parse5 };
2654
2759
 
2655
2760
  // src/extract/databases/ormconfig.ts
2656
2761
  init_cjs_shims();
2657
- var import_node_path12 = __toESM(require("path"), 1);
2762
+ var import_node_path13 = __toESM(require("path"), 1);
2658
2763
  async function parse6(serviceDir) {
2659
2764
  for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
2660
- const abs = import_node_path12.default.join(serviceDir, candidate);
2765
+ const abs = import_node_path13.default.join(serviceDir, candidate);
2661
2766
  if (!await exists(abs)) continue;
2662
2767
  const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
2663
2768
  const entries = Array.isArray(raw) ? raw : [raw];
@@ -2717,9 +2822,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
2717
2822
 
2718
2823
  // src/extract/databases/sequelize.ts
2719
2824
  init_cjs_shims();
2720
- var import_node_path13 = __toESM(require("path"), 1);
2825
+ var import_node_path14 = __toESM(require("path"), 1);
2721
2826
  async function parse8(serviceDir) {
2722
- const configPath = import_node_path13.default.join(serviceDir, "config", "config.json");
2827
+ const configPath = import_node_path14.default.join(serviceDir, "config", "config.json");
2723
2828
  if (!await exists(configPath)) return [];
2724
2829
  const raw = await readJson(configPath);
2725
2830
  const out = [];
@@ -2746,7 +2851,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
2746
2851
 
2747
2852
  // src/extract/databases/docker-compose.ts
2748
2853
  init_cjs_shims();
2749
- var import_node_path14 = __toESM(require("path"), 1);
2854
+ var import_node_path15 = __toESM(require("path"), 1);
2750
2855
  function portFromService(svc) {
2751
2856
  for (const raw of svc.ports ?? []) {
2752
2857
  const str = String(raw);
@@ -2773,7 +2878,7 @@ function databaseFromEnv(svc) {
2773
2878
  }
2774
2879
  async function parse9(serviceDir) {
2775
2880
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
2776
- const abs = import_node_path14.default.join(serviceDir, name);
2881
+ const abs = import_node_path15.default.join(serviceDir, name);
2777
2882
  if (!await exists(abs)) continue;
2778
2883
  const raw = await readYaml(abs);
2779
2884
  if (!raw?.services) return [];
@@ -2953,7 +3058,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2953
3058
  provenance: import_types7.Provenance.EXTRACTED,
2954
3059
  ...config.sourceFile ? {
2955
3060
  evidence: {
2956
- file: import_node_path15.default.relative(scanPath, config.sourceFile).split(import_node_path15.default.sep).join("/")
3061
+ file: import_node_path16.default.relative(scanPath, config.sourceFile).split(import_node_path16.default.sep).join("/")
2957
3062
  }
2958
3063
  } : {}
2959
3064
  };
@@ -2981,15 +3086,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
2981
3086
 
2982
3087
  // src/extract/configs.ts
2983
3088
  init_cjs_shims();
2984
- var import_node_fs10 = require("fs");
2985
- var import_node_path16 = __toESM(require("path"), 1);
3089
+ var import_node_fs11 = require("fs");
3090
+ var import_node_path17 = __toESM(require("path"), 1);
2986
3091
  var import_types8 = require("@neat.is/types");
2987
3092
  async function walkConfigFiles(dir) {
2988
3093
  const out = [];
2989
3094
  async function walk(current) {
2990
- const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true });
3095
+ const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true });
2991
3096
  for (const entry2 of entries) {
2992
- const full = import_node_path16.default.join(current, entry2.name);
3097
+ const full = import_node_path17.default.join(current, entry2.name);
2993
3098
  if (entry2.isDirectory()) {
2994
3099
  if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
2995
3100
  } else if (entry2.isFile() && isConfigFile(entry2.name).match) {
@@ -3006,13 +3111,13 @@ async function addConfigNodes(graph, services, scanPath) {
3006
3111
  for (const service of services) {
3007
3112
  const configFiles = await walkConfigFiles(service.dir);
3008
3113
  for (const file of configFiles) {
3009
- const relPath = import_node_path16.default.relative(scanPath, file);
3114
+ const relPath = import_node_path17.default.relative(scanPath, file);
3010
3115
  const node = {
3011
3116
  id: (0, import_types8.configId)(relPath),
3012
3117
  type: import_types8.NodeType.ConfigNode,
3013
- name: import_node_path16.default.basename(file),
3118
+ name: import_node_path17.default.basename(file),
3014
3119
  path: relPath,
3015
- fileType: isConfigFile(import_node_path16.default.basename(file)).fileType
3120
+ fileType: isConfigFile(import_node_path17.default.basename(file)).fileType
3016
3121
  };
3017
3122
  if (!graph.hasNode(node.id)) {
3018
3123
  graph.addNode(node.id, node);
@@ -3024,7 +3129,7 @@ async function addConfigNodes(graph, services, scanPath) {
3024
3129
  target: node.id,
3025
3130
  type: import_types8.EdgeType.CONFIGURED_BY,
3026
3131
  provenance: import_types8.Provenance.EXTRACTED,
3027
- evidence: { file: relPath.split(import_node_path16.default.sep).join("/") }
3132
+ evidence: { file: relPath.split(import_node_path17.default.sep).join("/") }
3028
3133
  };
3029
3134
  if (!graph.hasEdge(edge.id)) {
3030
3135
  graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
@@ -3041,7 +3146,7 @@ var import_types14 = require("@neat.is/types");
3041
3146
 
3042
3147
  // src/extract/calls/http.ts
3043
3148
  init_cjs_shims();
3044
- var import_node_path18 = __toESM(require("path"), 1);
3149
+ var import_node_path19 = __toESM(require("path"), 1);
3045
3150
  var import_tree_sitter = __toESM(require("tree-sitter"), 1);
3046
3151
  var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
3047
3152
  var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
@@ -3049,17 +3154,17 @@ var import_types9 = require("@neat.is/types");
3049
3154
 
3050
3155
  // src/extract/calls/shared.ts
3051
3156
  init_cjs_shims();
3052
- var import_node_fs11 = require("fs");
3053
- var import_node_path17 = __toESM(require("path"), 1);
3157
+ var import_node_fs12 = require("fs");
3158
+ var import_node_path18 = __toESM(require("path"), 1);
3054
3159
  async function walkSourceFiles(dir) {
3055
3160
  const out = [];
3056
3161
  async function walk(current) {
3057
- const entries = await import_node_fs11.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3162
+ const entries = await import_node_fs12.promises.readdir(current, { withFileTypes: true }).catch(() => []);
3058
3163
  for (const entry2 of entries) {
3059
- const full = import_node_path17.default.join(current, entry2.name);
3164
+ const full = import_node_path18.default.join(current, entry2.name);
3060
3165
  if (entry2.isDirectory()) {
3061
3166
  if (!IGNORED_DIRS.has(entry2.name)) await walk(full);
3062
- } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path17.default.extname(entry2.name))) {
3167
+ } else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path18.default.extname(entry2.name))) {
3063
3168
  out.push(full);
3064
3169
  }
3065
3170
  }
@@ -3072,7 +3177,7 @@ async function loadSourceFiles(dir) {
3072
3177
  const out = [];
3073
3178
  for (const p of paths) {
3074
3179
  try {
3075
- const content = await import_node_fs11.promises.readFile(p, "utf8");
3180
+ const content = await import_node_fs12.promises.readFile(p, "utf8");
3076
3181
  out.push({ path: p, content });
3077
3182
  } catch {
3078
3183
  }
@@ -3128,9 +3233,9 @@ async function addHttpCallEdges(graph, services) {
3128
3233
  const knownHosts = /* @__PURE__ */ new Set();
3129
3234
  const hostToNodeId = /* @__PURE__ */ new Map();
3130
3235
  for (const service of services) {
3131
- knownHosts.add(import_node_path18.default.basename(service.dir));
3236
+ knownHosts.add(import_node_path19.default.basename(service.dir));
3132
3237
  knownHosts.add(service.pkg.name);
3133
- hostToNodeId.set(import_node_path18.default.basename(service.dir), service.node.id);
3238
+ hostToNodeId.set(import_node_path19.default.basename(service.dir), service.node.id);
3134
3239
  hostToNodeId.set(service.pkg.name, service.node.id);
3135
3240
  }
3136
3241
  let edgesAdded = 0;
@@ -3138,7 +3243,7 @@ async function addHttpCallEdges(graph, services) {
3138
3243
  const files = await loadSourceFiles(service.dir);
3139
3244
  const seenTargets = /* @__PURE__ */ new Map();
3140
3245
  for (const file of files) {
3141
- const parser = import_node_path18.default.extname(file.path) === ".py" ? pyParser : jsParser;
3246
+ const parser = import_node_path19.default.extname(file.path) === ".py" ? pyParser : jsParser;
3142
3247
  let targets;
3143
3248
  try {
3144
3249
  targets = callsFromSource(file.content, parser, knownHosts);
@@ -3166,7 +3271,7 @@ async function addHttpCallEdges(graph, services) {
3166
3271
  type: import_types9.EdgeType.CALLS,
3167
3272
  provenance: import_types9.Provenance.EXTRACTED,
3168
3273
  evidence: {
3169
- file: import_node_path18.default.relative(service.dir, evidenceFile.file),
3274
+ file: import_node_path19.default.relative(service.dir, evidenceFile.file),
3170
3275
  line,
3171
3276
  snippet: snippet(fileContent, line)
3172
3277
  }
@@ -3182,7 +3287,7 @@ async function addHttpCallEdges(graph, services) {
3182
3287
 
3183
3288
  // src/extract/calls/kafka.ts
3184
3289
  init_cjs_shims();
3185
- var import_node_path19 = __toESM(require("path"), 1);
3290
+ var import_node_path20 = __toESM(require("path"), 1);
3186
3291
  var import_types10 = require("@neat.is/types");
3187
3292
  var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
3188
3293
  var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
@@ -3209,7 +3314,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3209
3314
  kind: "kafka-topic",
3210
3315
  edgeType,
3211
3316
  evidence: {
3212
- file: import_node_path19.default.relative(serviceDir, file.path),
3317
+ file: import_node_path20.default.relative(serviceDir, file.path),
3213
3318
  line,
3214
3319
  snippet: snippet(file.content, line)
3215
3320
  }
@@ -3222,7 +3327,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
3222
3327
 
3223
3328
  // src/extract/calls/redis.ts
3224
3329
  init_cjs_shims();
3225
- var import_node_path20 = __toESM(require("path"), 1);
3330
+ var import_node_path21 = __toESM(require("path"), 1);
3226
3331
  var import_types11 = require("@neat.is/types");
3227
3332
  var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
3228
3333
  function redisEndpointsFromFile(file, serviceDir) {
@@ -3241,7 +3346,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3241
3346
  kind: "redis",
3242
3347
  edgeType: "CALLS",
3243
3348
  evidence: {
3244
- file: import_node_path20.default.relative(serviceDir, file.path),
3349
+ file: import_node_path21.default.relative(serviceDir, file.path),
3245
3350
  line,
3246
3351
  snippet: snippet(file.content, line)
3247
3352
  }
@@ -3252,7 +3357,7 @@ function redisEndpointsFromFile(file, serviceDir) {
3252
3357
 
3253
3358
  // src/extract/calls/aws.ts
3254
3359
  init_cjs_shims();
3255
- var import_node_path21 = __toESM(require("path"), 1);
3360
+ var import_node_path22 = __toESM(require("path"), 1);
3256
3361
  var import_types12 = require("@neat.is/types");
3257
3362
  var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
3258
3363
  var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
@@ -3282,7 +3387,7 @@ function awsEndpointsFromFile(file, serviceDir) {
3282
3387
  kind,
3283
3388
  edgeType: "CALLS",
3284
3389
  evidence: {
3285
- file: import_node_path21.default.relative(serviceDir, file.path),
3390
+ file: import_node_path22.default.relative(serviceDir, file.path),
3286
3391
  line,
3287
3392
  snippet: snippet(file.content, line)
3288
3393
  }
@@ -3307,7 +3412,7 @@ function awsEndpointsFromFile(file, serviceDir) {
3307
3412
 
3308
3413
  // src/extract/calls/grpc.ts
3309
3414
  init_cjs_shims();
3310
- var import_node_path22 = __toESM(require("path"), 1);
3415
+ var import_node_path23 = __toESM(require("path"), 1);
3311
3416
  var import_types13 = require("@neat.is/types");
3312
3417
  var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
3313
3418
  function isLikelyAddress(value) {
@@ -3332,7 +3437,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
3332
3437
  kind: "grpc-service",
3333
3438
  edgeType: "CALLS",
3334
3439
  evidence: {
3335
- file: import_node_path22.default.relative(serviceDir, file.path),
3440
+ file: import_node_path23.default.relative(serviceDir, file.path),
3336
3441
  line,
3337
3442
  snippet: snippet(file.content, line)
3338
3443
  }
@@ -3412,7 +3517,7 @@ init_cjs_shims();
3412
3517
 
3413
3518
  // src/extract/infra/docker-compose.ts
3414
3519
  init_cjs_shims();
3415
- var import_node_path23 = __toESM(require("path"), 1);
3520
+ var import_node_path24 = __toESM(require("path"), 1);
3416
3521
  var import_types16 = require("@neat.is/types");
3417
3522
 
3418
3523
  // src/extract/infra/shared.ts
@@ -3450,7 +3555,7 @@ function dependsOnList(value) {
3450
3555
  }
3451
3556
  function serviceNameToServiceNode(name, services) {
3452
3557
  for (const s of services) {
3453
- if (s.node.name === name || import_node_path23.default.basename(s.dir) === name) return s.node.id;
3558
+ if (s.node.name === name || import_node_path24.default.basename(s.dir) === name) return s.node.id;
3454
3559
  }
3455
3560
  return null;
3456
3561
  }
@@ -3459,16 +3564,24 @@ async function addComposeInfra(graph, scanPath, services) {
3459
3564
  let edgesAdded = 0;
3460
3565
  let composePath = null;
3461
3566
  for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
3462
- const abs = import_node_path23.default.join(scanPath, name);
3567
+ const abs = import_node_path24.default.join(scanPath, name);
3463
3568
  if (await exists(abs)) {
3464
3569
  composePath = abs;
3465
3570
  break;
3466
3571
  }
3467
3572
  }
3468
3573
  if (!composePath) return { nodesAdded, edgesAdded };
3469
- const compose = await readYaml(composePath);
3574
+ let compose;
3575
+ try {
3576
+ compose = await readYaml(composePath);
3577
+ } catch (err) {
3578
+ console.warn(
3579
+ `[neat] infra docker-compose skipped ${import_node_path24.default.relative(scanPath, composePath)}: ${err.message}`
3580
+ );
3581
+ return { nodesAdded, edgesAdded };
3582
+ }
3470
3583
  if (!compose?.services) return { nodesAdded, edgesAdded };
3471
- const evidenceFile = import_node_path23.default.relative(scanPath, composePath).split(import_node_path23.default.sep).join("/");
3584
+ const evidenceFile = import_node_path24.default.relative(scanPath, composePath).split(import_node_path24.default.sep).join("/");
3472
3585
  const composeNameToNodeId = /* @__PURE__ */ new Map();
3473
3586
  for (const [composeName, svc] of Object.entries(compose.services)) {
3474
3587
  const matchedServiceId = serviceNameToServiceNode(composeName, services);
@@ -3509,8 +3622,8 @@ async function addComposeInfra(graph, scanPath, services) {
3509
3622
 
3510
3623
  // src/extract/infra/dockerfile.ts
3511
3624
  init_cjs_shims();
3512
- var import_node_path24 = __toESM(require("path"), 1);
3513
- var import_node_fs12 = require("fs");
3625
+ var import_node_path25 = __toESM(require("path"), 1);
3626
+ var import_node_fs13 = require("fs");
3514
3627
  var import_types17 = require("@neat.is/types");
3515
3628
  function runtimeImage(content) {
3516
3629
  const lines = content.split("\n");
@@ -3530,9 +3643,17 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3530
3643
  let nodesAdded = 0;
3531
3644
  let edgesAdded = 0;
3532
3645
  for (const service of services) {
3533
- const dockerfilePath = import_node_path24.default.join(service.dir, "Dockerfile");
3646
+ const dockerfilePath = import_node_path25.default.join(service.dir, "Dockerfile");
3534
3647
  if (!await exists(dockerfilePath)) continue;
3535
- const content = await import_node_fs12.promises.readFile(dockerfilePath, "utf8");
3648
+ let content;
3649
+ try {
3650
+ content = await import_node_fs13.promises.readFile(dockerfilePath, "utf8");
3651
+ } catch (err) {
3652
+ console.warn(
3653
+ `[neat] infra dockerfile skipped ${import_node_path25.default.relative(scanPath, dockerfilePath)}: ${err.message}`
3654
+ );
3655
+ continue;
3656
+ }
3536
3657
  const image = runtimeImage(content);
3537
3658
  if (!image) continue;
3538
3659
  const node = makeInfraNode("container-image", image);
@@ -3549,7 +3670,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3549
3670
  type: import_types17.EdgeType.RUNS_ON,
3550
3671
  provenance: import_types17.Provenance.EXTRACTED,
3551
3672
  evidence: {
3552
- file: import_node_path24.default.relative(scanPath, dockerfilePath).split(import_node_path24.default.sep).join("/")
3673
+ file: import_node_path25.default.relative(scanPath, dockerfilePath).split(import_node_path25.default.sep).join("/")
3553
3674
  }
3554
3675
  };
3555
3676
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
@@ -3561,19 +3682,19 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
3561
3682
 
3562
3683
  // src/extract/infra/terraform.ts
3563
3684
  init_cjs_shims();
3564
- var import_node_fs13 = require("fs");
3565
- var import_node_path25 = __toESM(require("path"), 1);
3685
+ var import_node_fs14 = require("fs");
3686
+ var import_node_path26 = __toESM(require("path"), 1);
3566
3687
  var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
3567
3688
  async function walkTfFiles(start, depth = 0, max = 5) {
3568
3689
  if (depth > max) return [];
3569
3690
  const out = [];
3570
- const entries = await import_node_fs13.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3691
+ const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3571
3692
  for (const entry2 of entries) {
3572
3693
  if (entry2.isDirectory()) {
3573
3694
  if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
3574
- out.push(...await walkTfFiles(import_node_path25.default.join(start, entry2.name), depth + 1, max));
3695
+ out.push(...await walkTfFiles(import_node_path26.default.join(start, entry2.name), depth + 1, max));
3575
3696
  } else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
3576
- out.push(import_node_path25.default.join(start, entry2.name));
3697
+ out.push(import_node_path26.default.join(start, entry2.name));
3577
3698
  }
3578
3699
  }
3579
3700
  return out;
@@ -3582,7 +3703,7 @@ async function addTerraformResources(graph, scanPath) {
3582
3703
  let nodesAdded = 0;
3583
3704
  const files = await walkTfFiles(scanPath);
3584
3705
  for (const file of files) {
3585
- const content = await import_node_fs13.promises.readFile(file, "utf8");
3706
+ const content = await import_node_fs14.promises.readFile(file, "utf8");
3586
3707
  RESOURCE_RE.lastIndex = 0;
3587
3708
  let m;
3588
3709
  while ((m = RESOURCE_RE.exec(content)) !== null) {
@@ -3600,8 +3721,8 @@ async function addTerraformResources(graph, scanPath) {
3600
3721
 
3601
3722
  // src/extract/infra/k8s.ts
3602
3723
  init_cjs_shims();
3603
- var import_node_fs14 = require("fs");
3604
- var import_node_path26 = __toESM(require("path"), 1);
3724
+ var import_node_fs15 = require("fs");
3725
+ var import_node_path27 = __toESM(require("path"), 1);
3605
3726
  var import_yaml3 = require("yaml");
3606
3727
  var K8S_KIND_TO_INFRA_KIND = {
3607
3728
  Service: "k8s-service",
@@ -3615,13 +3736,13 @@ var K8S_KIND_TO_INFRA_KIND = {
3615
3736
  async function walkYamlFiles2(start, depth = 0, max = 5) {
3616
3737
  if (depth > max) return [];
3617
3738
  const out = [];
3618
- const entries = await import_node_fs14.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3739
+ const entries = await import_node_fs15.promises.readdir(start, { withFileTypes: true }).catch(() => []);
3619
3740
  for (const entry2 of entries) {
3620
3741
  if (entry2.isDirectory()) {
3621
3742
  if (IGNORED_DIRS.has(entry2.name)) continue;
3622
- out.push(...await walkYamlFiles2(import_node_path26.default.join(start, entry2.name), depth + 1, max));
3623
- } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path26.default.extname(entry2.name))) {
3624
- out.push(import_node_path26.default.join(start, entry2.name));
3743
+ out.push(...await walkYamlFiles2(import_node_path27.default.join(start, entry2.name), depth + 1, max));
3744
+ } else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path27.default.extname(entry2.name))) {
3745
+ out.push(import_node_path27.default.join(start, entry2.name));
3625
3746
  }
3626
3747
  }
3627
3748
  return out;
@@ -3630,7 +3751,7 @@ async function addK8sResources(graph, scanPath) {
3630
3751
  let nodesAdded = 0;
3631
3752
  const files = await walkYamlFiles2(scanPath);
3632
3753
  for (const file of files) {
3633
- const content = await import_node_fs14.promises.readFile(file, "utf8");
3754
+ const content = await import_node_fs15.promises.readFile(file, "utf8");
3634
3755
  let docs;
3635
3756
  try {
3636
3757
  docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
@@ -3696,8 +3817,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
3696
3817
 
3697
3818
  // src/persist.ts
3698
3819
  init_cjs_shims();
3699
- var import_node_fs15 = require("fs");
3700
- var import_node_path27 = __toESM(require("path"), 1);
3820
+ var import_node_fs16 = require("fs");
3821
+ var import_node_path28 = __toESM(require("path"), 1);
3701
3822
  var SCHEMA_VERSION = 2;
3702
3823
  function migrateV1ToV2(payload) {
3703
3824
  const nodes = payload.graph.nodes;
@@ -3711,7 +3832,7 @@ function migrateV1ToV2(payload) {
3711
3832
  return { ...payload, schemaVersion: 2 };
3712
3833
  }
3713
3834
  async function ensureDir(filePath) {
3714
- await import_node_fs15.promises.mkdir(import_node_path27.default.dirname(filePath), { recursive: true });
3835
+ await import_node_fs16.promises.mkdir(import_node_path28.default.dirname(filePath), { recursive: true });
3715
3836
  }
3716
3837
  async function saveGraphToDisk(graph, outPath) {
3717
3838
  await ensureDir(outPath);
@@ -3721,13 +3842,13 @@ async function saveGraphToDisk(graph, outPath) {
3721
3842
  graph: graph.export()
3722
3843
  };
3723
3844
  const tmp = `${outPath}.tmp`;
3724
- await import_node_fs15.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
3725
- await import_node_fs15.promises.rename(tmp, outPath);
3845
+ await import_node_fs16.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
3846
+ await import_node_fs16.promises.rename(tmp, outPath);
3726
3847
  }
3727
3848
  async function loadGraphFromDisk(graph, outPath) {
3728
3849
  let raw;
3729
3850
  try {
3730
- raw = await import_node_fs15.promises.readFile(outPath, "utf8");
3851
+ raw = await import_node_fs16.promises.readFile(outPath, "utf8");
3731
3852
  } catch (err) {
3732
3853
  if (err.code === "ENOENT") return;
3733
3854
  throw err;
@@ -3780,7 +3901,7 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
3780
3901
 
3781
3902
  // src/watch.ts
3782
3903
  init_cjs_shims();
3783
- var import_node_path33 = __toESM(require("path"), 1);
3904
+ var import_node_path34 = __toESM(require("path"), 1);
3784
3905
  var import_chokidar = __toESM(require("chokidar"), 1);
3785
3906
 
3786
3907
  // src/api.ts
@@ -3791,7 +3912,7 @@ var import_types19 = require("@neat.is/types");
3791
3912
 
3792
3913
  // src/diff.ts
3793
3914
  init_cjs_shims();
3794
- var import_node_fs16 = require("fs");
3915
+ var import_node_fs17 = require("fs");
3795
3916
  async function loadSnapshotForDiff(target) {
3796
3917
  if (/^https?:\/\//i.test(target)) {
3797
3918
  const res = await fetch(target);
@@ -3800,7 +3921,7 @@ async function loadSnapshotForDiff(target) {
3800
3921
  }
3801
3922
  return await res.json();
3802
3923
  }
3803
- const raw = await import_node_fs16.promises.readFile(target, "utf8");
3924
+ const raw = await import_node_fs17.promises.readFile(target, "utf8");
3804
3925
  return JSON.parse(raw);
3805
3926
  }
3806
3927
  function indexEntries(entries) {
@@ -3868,23 +3989,23 @@ function canonicalJson(value) {
3868
3989
 
3869
3990
  // src/projects.ts
3870
3991
  init_cjs_shims();
3871
- var import_node_path28 = __toESM(require("path"), 1);
3992
+ var import_node_path29 = __toESM(require("path"), 1);
3872
3993
  function pathsForProject(project, baseDir) {
3873
3994
  if (project === DEFAULT_PROJECT) {
3874
3995
  return {
3875
- snapshotPath: import_node_path28.default.join(baseDir, "graph.json"),
3876
- errorsPath: import_node_path28.default.join(baseDir, "errors.ndjson"),
3877
- staleEventsPath: import_node_path28.default.join(baseDir, "stale-events.ndjson"),
3878
- embeddingsCachePath: import_node_path28.default.join(baseDir, "embeddings.json"),
3879
- policyViolationsPath: import_node_path28.default.join(baseDir, "policy-violations.ndjson")
3996
+ snapshotPath: import_node_path29.default.join(baseDir, "graph.json"),
3997
+ errorsPath: import_node_path29.default.join(baseDir, "errors.ndjson"),
3998
+ staleEventsPath: import_node_path29.default.join(baseDir, "stale-events.ndjson"),
3999
+ embeddingsCachePath: import_node_path29.default.join(baseDir, "embeddings.json"),
4000
+ policyViolationsPath: import_node_path29.default.join(baseDir, "policy-violations.ndjson")
3880
4001
  };
3881
4002
  }
3882
4003
  return {
3883
- snapshotPath: import_node_path28.default.join(baseDir, `${project}.json`),
3884
- errorsPath: import_node_path28.default.join(baseDir, `errors.${project}.ndjson`),
3885
- staleEventsPath: import_node_path28.default.join(baseDir, `stale-events.${project}.ndjson`),
3886
- embeddingsCachePath: import_node_path28.default.join(baseDir, `embeddings.${project}.json`),
3887
- policyViolationsPath: import_node_path28.default.join(baseDir, `policy-violations.${project}.ndjson`)
4004
+ snapshotPath: import_node_path29.default.join(baseDir, `${project}.json`),
4005
+ errorsPath: import_node_path29.default.join(baseDir, `errors.${project}.ndjson`),
4006
+ staleEventsPath: import_node_path29.default.join(baseDir, `stale-events.${project}.ndjson`),
4007
+ embeddingsCachePath: import_node_path29.default.join(baseDir, `embeddings.${project}.json`),
4008
+ policyViolationsPath: import_node_path29.default.join(baseDir, `policy-violations.${project}.ndjson`)
3888
4009
  };
3889
4010
  }
3890
4011
  var Projects = class {
@@ -3920,49 +4041,49 @@ var Projects = class {
3920
4041
 
3921
4042
  // src/registry.ts
3922
4043
  init_cjs_shims();
3923
- var import_node_fs17 = require("fs");
4044
+ var import_node_fs18 = require("fs");
3924
4045
  var import_node_os2 = __toESM(require("os"), 1);
3925
- var import_node_path29 = __toESM(require("path"), 1);
4046
+ var import_node_path30 = __toESM(require("path"), 1);
3926
4047
  var import_types18 = require("@neat.is/types");
3927
4048
  var LOCK_TIMEOUT_MS = 5e3;
3928
4049
  var LOCK_RETRY_MS = 50;
3929
4050
  function neatHome() {
3930
4051
  const override = process.env.NEAT_HOME;
3931
- if (override && override.length > 0) return import_node_path29.default.resolve(override);
3932
- return import_node_path29.default.join(import_node_os2.default.homedir(), ".neat");
4052
+ if (override && override.length > 0) return import_node_path30.default.resolve(override);
4053
+ return import_node_path30.default.join(import_node_os2.default.homedir(), ".neat");
3933
4054
  }
3934
4055
  function registryPath() {
3935
- return import_node_path29.default.join(neatHome(), "projects.json");
4056
+ return import_node_path30.default.join(neatHome(), "projects.json");
3936
4057
  }
3937
4058
  function registryLockPath() {
3938
- return import_node_path29.default.join(neatHome(), "projects.json.lock");
4059
+ return import_node_path30.default.join(neatHome(), "projects.json.lock");
3939
4060
  }
3940
4061
  async function normalizeProjectPath(input) {
3941
- const resolved = import_node_path29.default.resolve(input);
4062
+ const resolved = import_node_path30.default.resolve(input);
3942
4063
  try {
3943
- return await import_node_fs17.promises.realpath(resolved);
4064
+ return await import_node_fs18.promises.realpath(resolved);
3944
4065
  } catch {
3945
4066
  return resolved;
3946
4067
  }
3947
4068
  }
3948
4069
  async function writeAtomically(target, contents) {
3949
- await import_node_fs17.promises.mkdir(import_node_path29.default.dirname(target), { recursive: true });
4070
+ await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(target), { recursive: true });
3950
4071
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
3951
- const fd = await import_node_fs17.promises.open(tmp, "w");
4072
+ const fd = await import_node_fs18.promises.open(tmp, "w");
3952
4073
  try {
3953
4074
  await fd.writeFile(contents, "utf8");
3954
4075
  await fd.sync();
3955
4076
  } finally {
3956
4077
  await fd.close();
3957
4078
  }
3958
- await import_node_fs17.promises.rename(tmp, target);
4079
+ await import_node_fs18.promises.rename(tmp, target);
3959
4080
  }
3960
4081
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
3961
4082
  const deadline = Date.now() + timeoutMs;
3962
- await import_node_fs17.promises.mkdir(import_node_path29.default.dirname(lockPath), { recursive: true });
4083
+ await import_node_fs18.promises.mkdir(import_node_path30.default.dirname(lockPath), { recursive: true });
3963
4084
  while (true) {
3964
4085
  try {
3965
- const fd = await import_node_fs17.promises.open(lockPath, "wx");
4086
+ const fd = await import_node_fs18.promises.open(lockPath, "wx");
3966
4087
  await fd.close();
3967
4088
  return;
3968
4089
  } catch (err) {
@@ -3978,7 +4099,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
3978
4099
  }
3979
4100
  }
3980
4101
  async function releaseLock(lockPath) {
3981
- await import_node_fs17.promises.unlink(lockPath).catch(() => {
4102
+ await import_node_fs18.promises.unlink(lockPath).catch(() => {
3982
4103
  });
3983
4104
  }
3984
4105
  async function withLock(fn) {
@@ -3994,7 +4115,7 @@ async function readRegistry() {
3994
4115
  const file = registryPath();
3995
4116
  let raw;
3996
4117
  try {
3997
- raw = await import_node_fs17.promises.readFile(file, "utf8");
4118
+ raw = await import_node_fs18.promises.readFile(file, "utf8");
3998
4119
  } catch (err) {
3999
4120
  if (err.code === "ENOENT") {
4000
4121
  return { version: 1, projects: [] };
@@ -4514,8 +4635,8 @@ init_otel_grpc();
4514
4635
 
4515
4636
  // src/search.ts
4516
4637
  init_cjs_shims();
4517
- var import_node_fs18 = require("fs");
4518
- var import_node_path32 = __toESM(require("path"), 1);
4638
+ var import_node_fs19 = require("fs");
4639
+ var import_node_path33 = __toESM(require("path"), 1);
4519
4640
  var import_node_crypto = require("crypto");
4520
4641
  var DEFAULT_LIMIT = 10;
4521
4642
  var NOMIC_DIM = 768;
@@ -4645,7 +4766,7 @@ async function pickEmbedder() {
4645
4766
  }
4646
4767
  async function readCache(cachePath) {
4647
4768
  try {
4648
- const raw = await import_node_fs18.promises.readFile(cachePath, "utf8");
4769
+ const raw = await import_node_fs19.promises.readFile(cachePath, "utf8");
4649
4770
  const parsed = JSON.parse(raw);
4650
4771
  if (parsed.version !== 1) return null;
4651
4772
  return parsed;
@@ -4654,8 +4775,8 @@ async function readCache(cachePath) {
4654
4775
  }
4655
4776
  }
4656
4777
  async function writeCache(cachePath, cache) {
4657
- await import_node_fs18.promises.mkdir(import_node_path32.default.dirname(cachePath), { recursive: true });
4658
- await import_node_fs18.promises.writeFile(cachePath, JSON.stringify(cache));
4778
+ await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(cachePath), { recursive: true });
4779
+ await import_node_fs19.promises.writeFile(cachePath, JSON.stringify(cache));
4659
4780
  }
4660
4781
  var VectorIndex = class {
4661
4782
  constructor(embedder, cachePath) {
@@ -4810,8 +4931,8 @@ var ALL_PHASES = [
4810
4931
  ];
4811
4932
  function classifyChange(relPath) {
4812
4933
  const phases = /* @__PURE__ */ new Set();
4813
- const base = import_node_path33.default.basename(relPath).toLowerCase();
4814
- const segments = relPath.split(import_node_path33.default.sep).map((s) => s.toLowerCase());
4934
+ const base = import_node_path34.default.basename(relPath).toLowerCase();
4935
+ const segments = relPath.split(import_node_path34.default.sep).map((s) => s.toLowerCase());
4815
4936
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
4816
4937
  phases.add("services");
4817
4938
  phases.add("aliases");
@@ -4894,8 +5015,8 @@ async function startWatch(graph, opts) {
4894
5015
  const projectName = opts.project ?? DEFAULT_PROJECT;
4895
5016
  await loadGraphFromDisk(graph, opts.outPath);
4896
5017
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
4897
- const policyFilePath = import_node_path33.default.join(opts.scanPath, "policy.json");
4898
- const policyViolationsPath = import_node_path33.default.join(import_node_path33.default.dirname(opts.outPath), "policy-violations.ndjson");
5018
+ const policyFilePath = import_node_path34.default.join(opts.scanPath, "policy.json");
5019
+ const policyViolationsPath = import_node_path34.default.join(import_node_path34.default.dirname(opts.outPath), "policy-violations.ndjson");
4899
5020
  let policies = [];
4900
5021
  try {
4901
5022
  policies = await loadPolicyFile(policyFilePath);
@@ -4944,7 +5065,7 @@ async function startWatch(graph, opts) {
4944
5065
  const host = opts.host ?? "0.0.0.0";
4945
5066
  const port = opts.port ?? 8080;
4946
5067
  const otelPort = opts.otelPort ?? 4318;
4947
- const cachePath = opts.embeddingsCachePath ?? import_node_path33.default.join(import_node_path33.default.dirname(opts.outPath), "embeddings.json");
5068
+ const cachePath = opts.embeddingsCachePath ?? import_node_path34.default.join(import_node_path34.default.dirname(opts.outPath), "embeddings.json");
4948
5069
  let searchIndex;
4949
5070
  try {
4950
5071
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -4962,7 +5083,7 @@ async function startWatch(graph, opts) {
4962
5083
  // Paths are derived from the explicit options the watch caller passes
4963
5084
  // — pathsForProject is only used to fill in the embeddings/snapshot
4964
5085
  // fields so the registry shape is complete.
4965
- ...pathsForProject(projectName, import_node_path33.default.dirname(opts.outPath)),
5086
+ ...pathsForProject(projectName, import_node_path34.default.dirname(opts.outPath)),
4966
5087
  snapshotPath: opts.outPath,
4967
5088
  errorsPath: opts.errorsPath,
4968
5089
  staleEventsPath: opts.staleEventsPath
@@ -5047,9 +5168,9 @@ async function startWatch(graph, opts) {
5047
5168
  };
5048
5169
  const onPath = (absPath) => {
5049
5170
  if (shouldIgnore(absPath)) return;
5050
- const rel = import_node_path33.default.relative(opts.scanPath, absPath);
5171
+ const rel = import_node_path34.default.relative(opts.scanPath, absPath);
5051
5172
  if (!rel || rel.startsWith("..")) return;
5052
- pendingPaths.add(rel.split(import_node_path33.default.sep).join("/"));
5173
+ pendingPaths.add(rel.split(import_node_path34.default.sep).join("/"));
5053
5174
  const phases = classifyChange(rel);
5054
5175
  if (phases.size === 0) {
5055
5176
  for (const p of ALL_PHASES) pending.add(p);
@@ -5097,8 +5218,8 @@ init_cjs_shims();
5097
5218
 
5098
5219
  // src/installers/javascript.ts
5099
5220
  init_cjs_shims();
5100
- var import_node_fs19 = require("fs");
5101
- var import_node_path34 = __toESM(require("path"), 1);
5221
+ var import_node_fs20 = require("fs");
5222
+ var import_node_path35 = __toESM(require("path"), 1);
5102
5223
  var SDK_PACKAGES = [
5103
5224
  { name: "@opentelemetry/api", version: "^1.9.0" },
5104
5225
  { name: "@opentelemetry/sdk-node", version: "^0.57.0" },
@@ -5114,7 +5235,7 @@ var OTEL_ENV = {
5114
5235
  };
5115
5236
  async function readPackageJson(serviceDir) {
5116
5237
  try {
5117
- const raw = await import_node_fs19.promises.readFile(import_node_path34.default.join(serviceDir, "package.json"), "utf8");
5238
+ const raw = await import_node_fs20.promises.readFile(import_node_path35.default.join(serviceDir, "package.json"), "utf8");
5118
5239
  return JSON.parse(raw);
5119
5240
  } catch {
5120
5241
  return null;
@@ -5133,7 +5254,7 @@ function rewriteStartScript(start) {
5133
5254
  }
5134
5255
  async function plan(serviceDir) {
5135
5256
  const pkg = await readPackageJson(serviceDir);
5136
- const manifestPath = import_node_path34.default.join(serviceDir, "package.json");
5257
+ const manifestPath = import_node_path35.default.join(serviceDir, "package.json");
5137
5258
  const empty = {
5138
5259
  language: "javascript",
5139
5260
  serviceDir,
@@ -5180,7 +5301,7 @@ async function apply(installPlan) {
5180
5301
  const originals = /* @__PURE__ */ new Map();
5181
5302
  for (const file of touched) {
5182
5303
  try {
5183
- originals.set(file, await import_node_fs19.promises.readFile(file, "utf8"));
5304
+ originals.set(file, await import_node_fs20.promises.readFile(file, "utf8"));
5184
5305
  } catch {
5185
5306
  }
5186
5307
  }
@@ -5206,8 +5327,8 @@ async function apply(installPlan) {
5206
5327
  }
5207
5328
  const newRaw = JSON.stringify(pkg, null, 2) + "\n";
5208
5329
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
5209
- await import_node_fs19.promises.writeFile(tmp, newRaw, "utf8");
5210
- await import_node_fs19.promises.rename(tmp, file);
5330
+ await import_node_fs20.promises.writeFile(tmp, newRaw, "utf8");
5331
+ await import_node_fs20.promises.rename(tmp, file);
5211
5332
  }
5212
5333
  } catch (err) {
5213
5334
  await rollback(installPlan, originals);
@@ -5218,7 +5339,7 @@ async function rollback(installPlan, originals) {
5218
5339
  const restored = [];
5219
5340
  for (const [file, raw] of originals.entries()) {
5220
5341
  try {
5221
- await import_node_fs19.promises.writeFile(file, raw, "utf8");
5342
+ await import_node_fs20.promises.writeFile(file, raw, "utf8");
5222
5343
  restored.push(file);
5223
5344
  } catch {
5224
5345
  }
@@ -5232,8 +5353,8 @@ async function rollback(installPlan, originals) {
5232
5353
  ...restored.map((f) => `restored: ${f}`),
5233
5354
  ""
5234
5355
  ];
5235
- const rollbackPath = import_node_path34.default.join(installPlan.serviceDir, "neat-rollback.patch");
5236
- await import_node_fs19.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
5356
+ const rollbackPath = import_node_path35.default.join(installPlan.serviceDir, "neat-rollback.patch");
5357
+ await import_node_fs20.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
5237
5358
  }
5238
5359
  var javascriptInstaller = {
5239
5360
  name: "javascript",
@@ -5244,8 +5365,8 @@ var javascriptInstaller = {
5244
5365
 
5245
5366
  // src/installers/python.ts
5246
5367
  init_cjs_shims();
5247
- var import_node_fs20 = require("fs");
5248
- var import_node_path35 = __toESM(require("path"), 1);
5368
+ var import_node_fs21 = require("fs");
5369
+ var import_node_path36 = __toESM(require("path"), 1);
5249
5370
  var SDK_PACKAGES2 = [
5250
5371
  { name: "opentelemetry-distro", version: ">=0.49b0" },
5251
5372
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -5257,7 +5378,7 @@ var OTEL_ENV2 = {
5257
5378
  };
5258
5379
  async function exists2(p) {
5259
5380
  try {
5260
- await import_node_fs20.promises.stat(p);
5381
+ await import_node_fs21.promises.stat(p);
5261
5382
  return true;
5262
5383
  } catch {
5263
5384
  return false;
@@ -5266,7 +5387,7 @@ async function exists2(p) {
5266
5387
  async function detect2(serviceDir) {
5267
5388
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
5268
5389
  for (const m of markers) {
5269
- if (await exists2(import_node_path35.default.join(serviceDir, m))) return true;
5390
+ if (await exists2(import_node_path36.default.join(serviceDir, m))) return true;
5270
5391
  }
5271
5392
  return false;
5272
5393
  }
@@ -5276,9 +5397,9 @@ function reqPackageName(line) {
5276
5397
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
5277
5398
  }
5278
5399
  async function planRequirementsTxtEdits(serviceDir) {
5279
- const file = import_node_path35.default.join(serviceDir, "requirements.txt");
5400
+ const file = import_node_path36.default.join(serviceDir, "requirements.txt");
5280
5401
  if (!await exists2(file)) return null;
5281
- const raw = await import_node_fs20.promises.readFile(file, "utf8");
5402
+ const raw = await import_node_fs21.promises.readFile(file, "utf8");
5282
5403
  const presentNames = new Set(
5283
5404
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
5284
5405
  );
@@ -5286,9 +5407,9 @@ async function planRequirementsTxtEdits(serviceDir) {
5286
5407
  return { manifest: file, missing: [...missing] };
5287
5408
  }
5288
5409
  async function planProcfileEdits(serviceDir) {
5289
- const procfile = import_node_path35.default.join(serviceDir, "Procfile");
5410
+ const procfile = import_node_path36.default.join(serviceDir, "Procfile");
5290
5411
  if (!await exists2(procfile)) return [];
5291
- const raw = await import_node_fs20.promises.readFile(procfile, "utf8");
5412
+ const raw = await import_node_fs21.promises.readFile(procfile, "utf8");
5292
5413
  const edits = [];
5293
5414
  for (const line of raw.split(/\r?\n/)) {
5294
5415
  if (line.length === 0) continue;
@@ -5340,8 +5461,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
5340
5461
  const next = `${original}${trailing}${newlines.join("\n")}
5341
5462
  `;
5342
5463
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
5343
- await import_node_fs20.promises.writeFile(tmp, next, "utf8");
5344
- await import_node_fs20.promises.rename(tmp, manifest);
5464
+ await import_node_fs21.promises.writeFile(tmp, next, "utf8");
5465
+ await import_node_fs21.promises.rename(tmp, manifest);
5345
5466
  }
5346
5467
  async function applyProcfile(procfile, edits, original) {
5347
5468
  let next = original;
@@ -5350,8 +5471,8 @@ async function applyProcfile(procfile, edits, original) {
5350
5471
  next = next.replace(e.before, e.after);
5351
5472
  }
5352
5473
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
5353
- await import_node_fs20.promises.writeFile(tmp, next, "utf8");
5354
- await import_node_fs20.promises.rename(tmp, procfile);
5474
+ await import_node_fs21.promises.writeFile(tmp, next, "utf8");
5475
+ await import_node_fs21.promises.rename(tmp, procfile);
5355
5476
  }
5356
5477
  async function apply2(installPlan) {
5357
5478
  const touched = /* @__PURE__ */ new Set();
@@ -5361,7 +5482,7 @@ async function apply2(installPlan) {
5361
5482
  const originals = /* @__PURE__ */ new Map();
5362
5483
  for (const file of touched) {
5363
5484
  try {
5364
- originals.set(file, await import_node_fs20.promises.readFile(file, "utf8"));
5485
+ originals.set(file, await import_node_fs21.promises.readFile(file, "utf8"));
5365
5486
  } catch {
5366
5487
  }
5367
5488
  }
@@ -5371,7 +5492,7 @@ async function apply2(installPlan) {
5371
5492
  if (raw === void 0) {
5372
5493
  throw new Error(`python installer: cannot read ${file} during apply`);
5373
5494
  }
5374
- const base = import_node_path35.default.basename(file);
5495
+ const base = import_node_path36.default.basename(file);
5375
5496
  if (base === "requirements.txt") {
5376
5497
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
5377
5498
  if (edits.length > 0) await applyRequirementsTxt(file, edits, raw);
@@ -5389,7 +5510,7 @@ async function rollback2(installPlan, originals) {
5389
5510
  const restored = [];
5390
5511
  for (const [file, raw] of originals.entries()) {
5391
5512
  try {
5392
- await import_node_fs20.promises.writeFile(file, raw, "utf8");
5513
+ await import_node_fs21.promises.writeFile(file, raw, "utf8");
5393
5514
  restored.push(file);
5394
5515
  } catch {
5395
5516
  }
@@ -5403,8 +5524,8 @@ async function rollback2(installPlan, originals) {
5403
5524
  ...restored.map((f) => `restored: ${f}`),
5404
5525
  ""
5405
5526
  ];
5406
- const rollbackPath = import_node_path35.default.join(installPlan.serviceDir, "neat-rollback.patch");
5407
- await import_node_fs20.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
5527
+ const rollbackPath = import_node_path36.default.join(installPlan.serviceDir, "neat-rollback.patch");
5528
+ await import_node_fs21.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
5408
5529
  }
5409
5530
  var pythonInstaller = {
5410
5531
  name: "python",
@@ -5514,10 +5635,10 @@ var TransportError = class extends Error {
5514
5635
  function createHttpClient(baseUrl) {
5515
5636
  const root = baseUrl.replace(/\/$/, "");
5516
5637
  return {
5517
- async get(path37) {
5638
+ async get(path38) {
5518
5639
  let res;
5519
5640
  try {
5520
- res = await fetch(`${root}${path37}`);
5641
+ res = await fetch(`${root}${path38}`);
5521
5642
  } catch (err) {
5522
5643
  throw new TransportError(
5523
5644
  `cannot reach neat-core at ${root}: ${err.message}`
@@ -5527,16 +5648,16 @@ function createHttpClient(baseUrl) {
5527
5648
  const body = await res.text().catch(() => "");
5528
5649
  throw new HttpError(
5529
5650
  res.status,
5530
- `${res.status} ${res.statusText} on GET ${path37}: ${body}`,
5651
+ `${res.status} ${res.statusText} on GET ${path38}: ${body}`,
5531
5652
  body
5532
5653
  );
5533
5654
  }
5534
5655
  return await res.json();
5535
5656
  },
5536
- async post(path37, body) {
5657
+ async post(path38, body) {
5537
5658
  let res;
5538
5659
  try {
5539
- res = await fetch(`${root}${path37}`, {
5660
+ res = await fetch(`${root}${path38}`, {
5540
5661
  method: "POST",
5541
5662
  headers: { "content-type": "application/json" },
5542
5663
  body: JSON.stringify(body)
@@ -5550,7 +5671,7 @@ function createHttpClient(baseUrl) {
5550
5671
  const text = await res.text().catch(() => "");
5551
5672
  throw new HttpError(
5552
5673
  res.status,
5553
- `${res.status} ${res.statusText} on POST ${path37}: ${text}`,
5674
+ `${res.status} ${res.statusText} on POST ${path38}: ${text}`,
5554
5675
  text
5555
5676
  );
5556
5677
  }
@@ -5564,12 +5685,12 @@ function projectPath(project, suffix) {
5564
5685
  }
5565
5686
  async function runRootCause(client, input) {
5566
5687
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
5567
- const path37 = projectPath(
5688
+ const path38 = projectPath(
5568
5689
  input.project,
5569
5690
  `/traverse/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
5570
5691
  );
5571
5692
  try {
5572
- const result = await client.get(path37);
5693
+ const result = await client.get(path38);
5573
5694
  const arrowPath = result.traversalPath.join(" \u2190 ");
5574
5695
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
5575
5696
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -5595,12 +5716,12 @@ async function runRootCause(client, input) {
5595
5716
  }
5596
5717
  async function runBlastRadius(client, input) {
5597
5718
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
5598
- const path37 = projectPath(
5719
+ const path38 = projectPath(
5599
5720
  input.project,
5600
5721
  `/traverse/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
5601
5722
  );
5602
5723
  try {
5603
- const result = await client.get(path37);
5724
+ const result = await client.get(path38);
5604
5725
  if (result.totalAffected === 0) {
5605
5726
  return {
5606
5727
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -5634,12 +5755,12 @@ function formatBlastEntry(n) {
5634
5755
  }
5635
5756
  async function runDependencies(client, input) {
5636
5757
  const depth = input.depth ?? 3;
5637
- const path37 = projectPath(
5758
+ const path38 = projectPath(
5638
5759
  input.project,
5639
5760
  `/graph/node/${encodeURIComponent(input.nodeId)}/dependencies?depth=${depth}`
5640
5761
  );
5641
5762
  try {
5642
- const result = await client.get(path37);
5763
+ const result = await client.get(path38);
5643
5764
  if (result.total === 0) {
5644
5765
  return {
5645
5766
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -5720,9 +5841,9 @@ function formatDuration(ms) {
5720
5841
  return `${Math.round(h / 24)}d`;
5721
5842
  }
5722
5843
  async function runIncidents(client, input) {
5723
- const path37 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
5844
+ const path38 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
5724
5845
  try {
5725
- const events = await client.get(path37);
5846
+ const events = await client.get(path38);
5726
5847
  if (events.length === 0) {
5727
5848
  return {
5728
5849
  summary: input.nodeId ? `No incidents recorded against ${input.nodeId}.` : "No incidents recorded."
@@ -6170,7 +6291,7 @@ async function buildPatchSections(services) {
6170
6291
  }
6171
6292
  async function runInit(opts) {
6172
6293
  const written = [];
6173
- const stat = await import_node_fs21.promises.stat(opts.scanPath).catch(() => null);
6294
+ const stat = await import_node_fs22.promises.stat(opts.scanPath).catch(() => null);
6174
6295
  if (!stat || !stat.isDirectory()) {
6175
6296
  console.error(`neat init: ${opts.scanPath} is not a directory`);
6176
6297
  return { exitCode: 2, writtenFiles: written };
@@ -6179,9 +6300,9 @@ async function runInit(opts) {
6179
6300
  printDiscoveryReport(opts, services);
6180
6301
  const sections = opts.noInstall ? [] : await buildPatchSections(services);
6181
6302
  const patch = renderPatch(sections);
6182
- const patchPath = import_node_path36.default.join(opts.scanPath, "neat.patch");
6303
+ const patchPath = import_node_path37.default.join(opts.scanPath, "neat.patch");
6183
6304
  if (opts.dryRun) {
6184
- await import_node_fs21.promises.writeFile(patchPath, patch, "utf8");
6305
+ await import_node_fs22.promises.writeFile(patchPath, patch, "utf8");
6185
6306
  written.push(patchPath);
6186
6307
  console.log(`dry-run: patch written to ${patchPath}`);
6187
6308
  console.log("rerun without --dry-run to register and snapshot.");
@@ -6221,7 +6342,7 @@ async function runInit(opts) {
6221
6342
  console.log("patch applied. Run `npm install` (or your language equivalent) to refresh lockfiles.");
6222
6343
  }
6223
6344
  } else {
6224
- await import_node_fs21.promises.writeFile(patchPath, patch, "utf8");
6345
+ await import_node_fs22.promises.writeFile(patchPath, patch, "utf8");
6225
6346
  written.push(patchPath);
6226
6347
  }
6227
6348
  }
@@ -6261,9 +6382,9 @@ var CLAUDE_SKILL_CONFIG = {
6261
6382
  };
6262
6383
  function claudeConfigPath() {
6263
6384
  const override = process.env.NEAT_CLAUDE_CONFIG;
6264
- if (override && override.length > 0) return import_node_path36.default.resolve(override);
6385
+ if (override && override.length > 0) return import_node_path37.default.resolve(override);
6265
6386
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
6266
- return import_node_path36.default.join(home, ".claude.json");
6387
+ return import_node_path37.default.join(home, ".claude.json");
6267
6388
  }
6268
6389
  async function runSkill(opts) {
6269
6390
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -6275,7 +6396,7 @@ async function runSkill(opts) {
6275
6396
  const target = claudeConfigPath();
6276
6397
  let existing = {};
6277
6398
  try {
6278
- existing = JSON.parse(await import_node_fs21.promises.readFile(target, "utf8"));
6399
+ existing = JSON.parse(await import_node_fs22.promises.readFile(target, "utf8"));
6279
6400
  } catch (err) {
6280
6401
  if (err.code !== "ENOENT") {
6281
6402
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -6287,8 +6408,8 @@ async function runSkill(opts) {
6287
6408
  ...existing,
6288
6409
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
6289
6410
  };
6290
- await import_node_fs21.promises.mkdir(import_node_path36.default.dirname(target), { recursive: true });
6291
- await import_node_fs21.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
6411
+ await import_node_fs22.promises.mkdir(import_node_path37.default.dirname(target), { recursive: true });
6412
+ await import_node_fs22.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
6292
6413
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
6293
6414
  console.log("restart Claude Code to pick up the new MCP server.");
6294
6415
  return { exitCode: 0 };
@@ -6322,12 +6443,12 @@ async function main() {
6322
6443
  console.error("neat init: --apply and --dry-run are mutually exclusive");
6323
6444
  process.exit(2);
6324
6445
  }
6325
- const scanPath = import_node_path36.default.resolve(target);
6446
+ const scanPath = import_node_path37.default.resolve(target);
6326
6447
  const projectExplicit = parsed.project !== null;
6327
- const projectName = projectExplicit ? project : import_node_path36.default.basename(scanPath);
6448
+ const projectName = projectExplicit ? project : import_node_path37.default.basename(scanPath);
6328
6449
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
6329
- const fallback = pathsForProject(projectKey, import_node_path36.default.join(scanPath, "neat-out")).snapshotPath;
6330
- const outPath = import_node_path36.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
6450
+ const fallback = pathsForProject(projectKey, import_node_path37.default.join(scanPath, "neat-out")).snapshotPath;
6451
+ const outPath = import_node_path37.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
6331
6452
  const result = await runInit({
6332
6453
  scanPath,
6333
6454
  outPath,
@@ -6347,21 +6468,21 @@ async function main() {
6347
6468
  usage();
6348
6469
  process.exit(2);
6349
6470
  }
6350
- const scanPath = import_node_path36.default.resolve(target);
6351
- const stat = await import_node_fs21.promises.stat(scanPath).catch(() => null);
6471
+ const scanPath = import_node_path37.default.resolve(target);
6472
+ const stat = await import_node_fs22.promises.stat(scanPath).catch(() => null);
6352
6473
  if (!stat || !stat.isDirectory()) {
6353
6474
  console.error(`neat watch: ${scanPath} is not a directory`);
6354
6475
  process.exit(2);
6355
6476
  }
6356
- const projectPaths = pathsForProject(project, import_node_path36.default.join(scanPath, "neat-out"));
6357
- const outPath = import_node_path36.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
6358
- const errorsPath = import_node_path36.default.resolve(
6359
- process.env.NEAT_ERRORS_PATH ?? import_node_path36.default.join(import_node_path36.default.dirname(outPath), import_node_path36.default.basename(projectPaths.errorsPath))
6477
+ const projectPaths = pathsForProject(project, import_node_path37.default.join(scanPath, "neat-out"));
6478
+ const outPath = import_node_path37.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
6479
+ const errorsPath = import_node_path37.default.resolve(
6480
+ process.env.NEAT_ERRORS_PATH ?? import_node_path37.default.join(import_node_path37.default.dirname(outPath), import_node_path37.default.basename(projectPaths.errorsPath))
6360
6481
  );
6361
- const staleEventsPath = import_node_path36.default.resolve(
6362
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path36.default.join(import_node_path36.default.dirname(outPath), import_node_path36.default.basename(projectPaths.staleEventsPath))
6482
+ const staleEventsPath = import_node_path37.default.resolve(
6483
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path37.default.join(import_node_path37.default.dirname(outPath), import_node_path37.default.basename(projectPaths.staleEventsPath))
6363
6484
  );
6364
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path36.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
6485
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path37.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
6365
6486
  const handle = await startWatch(getGraph(project), {
6366
6487
  scanPath,
6367
6488
  outPath,