@neat.is/core 0.6.3 → 0.6.4

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.
@@ -31,7 +31,7 @@ import {
31
31
  touchLastSeen,
32
32
  upsertObservedEdge,
33
33
  writeAtomically
34
- } from "./chunk-L64CATKE.js";
34
+ } from "./chunk-UCDXHLCJ.js";
35
35
  import {
36
36
  assertBindAuthority,
37
37
  buildOtelReceiver,
@@ -2807,4 +2807,4 @@ export {
2807
2807
  resolveHost,
2808
2808
  startDaemon
2809
2809
  };
2810
- //# sourceMappingURL=chunk-PWIT35Z4.js.map
2810
+ //# sourceMappingURL=chunk-22X2YM5H.js.map
@@ -1091,6 +1091,41 @@ async function writeExtractionErrors(errors, errorsPath) {
1091
1091
  const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
1092
1092
  await fs4.appendFile(errorsPath, lines, "utf8");
1093
1093
  }
1094
+ function extractionHealthPathFor(errorsPath) {
1095
+ const dir = path4.dirname(errorsPath);
1096
+ const suffix = path4.basename(errorsPath).replace(/^errors/, "").replace(/\.ndjson$/, "");
1097
+ return path4.join(dir, `extraction-health${suffix}.json`);
1098
+ }
1099
+ var HEALTH_FILE_SAMPLE = 100;
1100
+ async function writeExtractionHealth(errors, healthPath, now = (/* @__PURE__ */ new Date()).toISOString()) {
1101
+ const byProducer = {};
1102
+ for (const e of errors) byProducer[e.producer] = (byProducer[e.producer] ?? 0) + 1;
1103
+ const coverage = {
1104
+ skippedFiles: errors.length,
1105
+ byProducer,
1106
+ files: errors.slice(0, HEALTH_FILE_SAMPLE).map((e) => e.file),
1107
+ updatedAt: now
1108
+ };
1109
+ await fs4.mkdir(path4.dirname(healthPath), { recursive: true });
1110
+ await fs4.writeFile(healthPath, JSON.stringify(coverage), "utf8");
1111
+ }
1112
+ async function readExtractionHealth(healthPath) {
1113
+ let raw;
1114
+ try {
1115
+ raw = await fs4.readFile(healthPath, "utf8");
1116
+ } catch {
1117
+ return void 0;
1118
+ }
1119
+ try {
1120
+ const obj = JSON.parse(raw);
1121
+ if (typeof obj?.skippedFiles !== "number" || !obj.byProducer || !Array.isArray(obj.files)) {
1122
+ return void 0;
1123
+ }
1124
+ return obj;
1125
+ } catch {
1126
+ return void 0;
1127
+ }
1128
+ }
1094
1129
  function isStrictExtractionEnabled() {
1095
1130
  const raw = process.env.NEAT_STRICT_EXTRACTION;
1096
1131
  return raw === "1" || raw === "true";
@@ -2060,14 +2095,16 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
2060
2095
  return best ?? relPath;
2061
2096
  }
2062
2097
  function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
2063
- const relPath = reconcileObservedRelPath(graph, serviceName, callSite.relPath);
2064
- const fileNodeId = fileId2(serviceName, relPath);
2098
+ const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
2099
+ const canonicalService = svcAttrs && svcAttrs.type === NodeType4.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
2100
+ const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
2101
+ const fileNodeId = fileId2(canonicalService, relPath);
2065
2102
  if (!graph.hasNode(fileNodeId)) {
2066
2103
  const language = languageForExt(relPath);
2067
2104
  const node = {
2068
2105
  id: fileNodeId,
2069
2106
  type: NodeType4.FileNode,
2070
- service: serviceName,
2107
+ service: canonicalService,
2071
2108
  path: relPath,
2072
2109
  ...language ? { language } : {},
2073
2110
  ...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
@@ -7708,6 +7745,15 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7708
7745
  );
7709
7746
  }
7710
7747
  }
7748
+ if (opts.errorsPath) {
7749
+ try {
7750
+ await writeExtractionHealth(errorEntries, extractionHealthPathFor(opts.errorsPath));
7751
+ } catch (err) {
7752
+ console.warn(
7753
+ `[neat] failed to write extraction health sidecar: ${err.message}`
7754
+ );
7755
+ }
7756
+ }
7711
7757
  const droppedEntries = drainDroppedExtracted();
7712
7758
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7713
7759
  const rejectedPath = path43.join(path43.dirname(opts.errorsPath), "rejected.ndjson");
@@ -9475,7 +9521,11 @@ function resolveProject(registry, req, reply, bootstrap, singleProject) {
9475
9521
  void reply.code(503).send({ ready: false, project: name, status: "broken" });
9476
9522
  return null;
9477
9523
  }
9478
- void reply.code(404).send({ error: "project not found", project: name });
9524
+ void reply.code(404).send({
9525
+ error: "project not found",
9526
+ project: name,
9527
+ hint: "This daemon does not host that project. GET /projects lists what it serves (see hostedHere)."
9528
+ });
9479
9529
  return null;
9480
9530
  }
9481
9531
  return ctx;
@@ -9513,6 +9563,9 @@ function registerRoutes(scope, ctx) {
9513
9563
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9514
9564
  if (!proj) return;
9515
9565
  const uptimeMs = Date.now() - startedAt;
9566
+ const coverage = await readExtractionHealth(
9567
+ extractionHealthPathFor(proj.paths.errorsPath)
9568
+ );
9516
9569
  return {
9517
9570
  ok: true,
9518
9571
  project: proj.name,
@@ -9523,7 +9576,8 @@ function registerRoutes(scope, ctx) {
9523
9576
  uptime: Math.floor(uptimeMs / 1e3),
9524
9577
  nodeCount: proj.graph.order,
9525
9578
  edgeCount: proj.graph.size,
9526
- lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
9579
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
9580
+ ...coverage ? { coverage } : {}
9527
9581
  };
9528
9582
  });
9529
9583
  }
@@ -10165,12 +10219,17 @@ async function buildApi(opts) {
10165
10219
  // The daemon is serving this project, so it's active unless its
10166
10220
  // bootstrap broke. ('bootstrapping' still reads as active — the project
10167
10221
  // is the one to land on; its graph route answers 503 until ready.)
10168
- status: phase === "broken" ? "broken" : "active"
10222
+ status: phase === "broken" ? "broken" : "active",
10223
+ hostedHere: true
10169
10224
  };
10170
10225
  return [entry];
10171
10226
  }
10172
10227
  try {
10173
- return await listProjects();
10228
+ const entries = await listProjects();
10229
+ return entries.map((entry) => ({
10230
+ ...entry,
10231
+ hostedHere: registry.has(entry.name)
10232
+ }));
10174
10233
  } catch (err) {
10175
10234
  return reply.code(500).send({
10176
10235
  error: "failed to read project registry",
@@ -10184,7 +10243,21 @@ async function buildApi(opts) {
10184
10243
  if (!entry) {
10185
10244
  return reply.code(404).send({ error: "project not found", project: req.params.project });
10186
10245
  }
10187
- return { project: entry };
10246
+ const hostedHere = registry.has(entry.name);
10247
+ if (!hostedHere) {
10248
+ const owner = await findDaemonByProject(entry.name).catch(() => void 0);
10249
+ if (owner) {
10250
+ return {
10251
+ project: { ...entry, hostedHere },
10252
+ servedBy: {
10253
+ path: owner.record.projectPath,
10254
+ restPort: owner.record.ports.rest,
10255
+ live: owner.live
10256
+ }
10257
+ };
10258
+ }
10259
+ }
10260
+ return { project: { ...entry, hostedHere } };
10188
10261
  } catch (err) {
10189
10262
  return reply.code(500).send({
10190
10263
  error: "failed to read project registry",
@@ -10289,4 +10362,4 @@ export {
10289
10362
  recordConnectorPoll,
10290
10363
  buildApi
10291
10364
  };
10292
- //# sourceMappingURL=chunk-L64CATKE.js.map
10365
+ //# sourceMappingURL=chunk-UCDXHLCJ.js.map