@neat.is/core 0.6.4 → 0.7.1

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.js CHANGED
@@ -1,19 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  clearDaemonRecord,
4
- deprovisionConnector,
5
- getProviderFieldSchema,
6
- isPushProvider,
7
- knownProviderNames,
8
4
  portFromListenAddress,
9
- provisionConnector,
10
5
  readDaemonRecord,
11
6
  resolveHost,
12
7
  resolveNeatVersion,
13
- startConnectorPolling,
14
- validateConnectorEntry,
15
8
  writeDaemonRecord
16
- } from "./chunk-22X2YM5H.js";
9
+ } from "./chunk-MDBE23Y3.js";
17
10
  import {
18
11
  buildSearchIndex
19
12
  } from "./chunk-BIY46Q6U.js";
@@ -36,6 +29,7 @@ import {
36
29
  autoSlugConnectorId,
37
30
  buildApi,
38
31
  computeDivergences,
32
+ deprovisionConnector,
39
33
  describeCredential,
40
34
  detectPackageManager,
41
35
  discoverServices,
@@ -47,8 +41,11 @@ import {
47
41
  formatExtractionBanner,
48
42
  formatPrecisionFloorBanner,
49
43
  getGraph,
44
+ getProviderFieldSchema,
50
45
  isEnvRef,
46
+ isPushProvider,
51
47
  isStrictExtractionEnabled,
48
+ knownProviderNames,
52
49
  listMachineProjects,
53
50
  listProjects,
54
51
  loadGraphFromDisk,
@@ -58,6 +55,7 @@ import {
58
55
  normalizeProjectPath,
59
56
  pathsForProject,
60
57
  promoteFrontierNodes,
58
+ provisionConnector,
61
59
  pruneRegistry,
62
60
  readConnectorsConfig,
63
61
  removeConnectorEntry,
@@ -69,13 +67,15 @@ import {
69
67
  saveGraphToDisk,
70
68
  setStatus,
71
69
  signalDaemonStop,
70
+ startConnectorPolling,
72
71
  startPersistLoop,
73
72
  startStalenessLoop,
74
- upsertConnectorEntry
75
- } from "./chunk-UCDXHLCJ.js";
73
+ upsertConnectorEntry,
74
+ validateConnectorEntry
75
+ } from "./chunk-RR4LWQQB.js";
76
76
  import {
77
77
  startOtelGrpcReceiver
78
- } from "./chunk-7QXN726V.js";
78
+ } from "./chunk-6H757ZNM.js";
79
79
  import {
80
80
  __dirname,
81
81
  __require,
@@ -83,12 +83,12 @@ import {
83
83
  buildOtelReceiver,
84
84
  listenSteppingOtlp,
85
85
  readAuthEnv
86
- } from "./chunk-ZLAZ7PLC.js";
86
+ } from "./chunk-P2ZEKJ35.js";
87
87
 
88
88
  // src/cli.ts
89
- import path10 from "path";
89
+ import path11 from "path";
90
90
  import os2 from "os";
91
- import { promises as fs8 } from "fs";
91
+ import { promises as fs9 } from "fs";
92
92
 
93
93
  // src/banner.ts
94
94
  import path from "path";
@@ -825,7 +825,7 @@ import semver from "semver";
825
825
 
826
826
  // src/installers/templates.ts
827
827
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
828
- var OTEL_INIT_STAMP = "// neat-template-version: 7 \u2014 OTel init degrades to no-OBSERVED instead of crashing the host app when @opentelemetry deps are absent (#820); daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
828
+ var OTEL_INIT_STAMP = "// neat-template-version: 8 \u2014 CommonJS TypeScript uses a synchronous bootstrap (#910); OTel init degrades to no-OBSERVED when dependencies are absent (#820); daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
829
829
  var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
830
830
  var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
831
831
  var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
@@ -1189,6 +1189,13 @@ ${neatWireCaptureSource(false)}
1189
1189
  }
1190
1190
  }
1191
1191
  `;
1192
+ var OTEL_INIT_TS_CJS = OTEL_INIT_CJS.replace(
1193
+ `${OTEL_INIT_HEADER}
1194
+ ${OTEL_INIT_STAMP}`,
1195
+ `${OTEL_INIT_HEADER}
1196
+ ${OTEL_INIT_STAMP}
1197
+ // @ts-nocheck \u2014 generated CommonJS runtime shim.`
1198
+ );
1192
1199
  var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
1193
1200
  ${OTEL_INIT_STAMP}
1194
1201
  ${OTEL_ESM_NODE_IMPORTS}
@@ -1460,6 +1467,13 @@ function detectNonBundledInstrumentations(pkg) {
1460
1467
  registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
1461
1468
  });
1462
1469
  }
1470
+ if (getMajor(deps["@nestjs/core"]) >= 11) {
1471
+ out.push({
1472
+ pkg: "@opentelemetry/instrumentation-nestjs-core",
1473
+ version: "^0.67.0",
1474
+ registration: "instrumentations.push(new (require('@opentelemetry/instrumentation-nestjs-core').NestInstrumentation)())"
1475
+ });
1476
+ }
1463
1477
  return out;
1464
1478
  }
1465
1479
  var OTEL_ENV = {
@@ -1772,18 +1786,19 @@ async function resolveEntry(serviceDir, pkg) {
1772
1786
  }
1773
1787
  function dispatchEntry(entryFile, pkg) {
1774
1788
  const ext = path5.extname(entryFile).toLowerCase();
1775
- if (ext === ".ts" || ext === ".tsx") return "ts";
1789
+ if (ext === ".ts" || ext === ".tsx") return pkg.type === "module" ? "ts" : "ts-cjs";
1776
1790
  if (ext === ".mjs") return "esm";
1777
1791
  if (ext === ".cjs") return "cjs";
1778
1792
  return pkg.type === "module" ? "esm" : "cjs";
1779
1793
  }
1780
1794
  function otelInitFilename(flavor) {
1781
- if (flavor === "ts") return "otel-init.ts";
1795
+ if (flavor === "ts" || flavor === "ts-cjs") return "otel-init.ts";
1782
1796
  if (flavor === "esm") return "otel-init.mjs";
1783
1797
  return "otel-init.cjs";
1784
1798
  }
1785
1799
  function otelInitContents(flavor) {
1786
1800
  if (flavor === "ts") return OTEL_INIT_TS;
1801
+ if (flavor === "ts-cjs") return OTEL_INIT_TS_CJS;
1787
1802
  if (flavor === "esm") return OTEL_INIT_ESM;
1788
1803
  return OTEL_INIT_CJS;
1789
1804
  }
@@ -2949,9 +2964,137 @@ var pythonInstaller = {
2949
2964
  apply: apply2
2950
2965
  };
2951
2966
 
2967
+ // src/installers/go.ts
2968
+ import { promises as fs6 } from "fs";
2969
+ import path7 from "path";
2970
+ var GO_DEPS = [
2971
+ { name: "go.opentelemetry.io/otel", version: "v1.38.0" },
2972
+ { name: "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp", version: "v1.38.0" },
2973
+ { name: "go.opentelemetry.io/otel/sdk", version: "v1.38.0" }
2974
+ ];
2975
+ async function exists3(file) {
2976
+ return fs6.stat(file).then(() => true, () => false);
2977
+ }
2978
+ async function findMain(serviceDir) {
2979
+ const root = path7.join(serviceDir, "main.go");
2980
+ if (await exists3(root)) return root;
2981
+ const cmd = path7.join(serviceDir, "cmd");
2982
+ const entries = await fs6.readdir(cmd, { withFileTypes: true }).catch(() => []);
2983
+ for (const entry2 of entries.sort((a, b) => a.name.localeCompare(b.name))) {
2984
+ if (!entry2.isDirectory()) continue;
2985
+ const candidate = path7.join(cmd, entry2.name, "main.go");
2986
+ if (await exists3(candidate)) return candidate;
2987
+ }
2988
+ return null;
2989
+ }
2990
+ function neatOtelGo(packageName = "main") {
2991
+ return `// neat-otel-init v1 \u2014 generated by NEAT. Safe to re-generate; do not edit.
2992
+ package ${packageName}
2993
+
2994
+ import (
2995
+ "context"
2996
+ "os"
2997
+ "path/filepath"
2998
+ "runtime"
2999
+ "strings"
3000
+
3001
+ "go.opentelemetry.io/otel"
3002
+ "go.opentelemetry.io/otel/attribute"
3003
+ "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
3004
+ sdktrace "go.opentelemetry.io/otel/sdk/trace"
3005
+ "go.opentelemetry.io/otel/trace"
3006
+ )
3007
+
3008
+ type neatCallSiteSpanProcessor struct { root string }
3009
+
3010
+ func (p neatCallSiteSpanProcessor) OnStart(_ context.Context, span sdktrace.ReadWriteSpan) {
3011
+ if span.SpanKind() != trace.SpanKindClient && span.SpanKind() != trace.SpanKindProducer { return }
3012
+ pcs := make([]uintptr, 32)
3013
+ n := runtime.Callers(2, pcs)
3014
+ frames := runtime.CallersFrames(pcs[:n])
3015
+ for {
3016
+ frame, more := frames.Next()
3017
+ clean := filepath.Clean(frame.File)
3018
+ slash := filepath.ToSlash(clean)
3019
+ if clean != "" && strings.HasPrefix(clean, p.root+string(os.PathSeparator)) && !strings.Contains(slash, "/vendor/") && !strings.HasSuffix(slash, "/neat_otel.go") {
3020
+ span.SetAttributes(
3021
+ attribute.String("code.file.path", clean),
3022
+ attribute.Int("code.line.number", frame.Line),
3023
+ attribute.String("code.function.name", frame.Function),
3024
+ )
3025
+ return
3026
+ }
3027
+ if !more { return }
3028
+ }
3029
+ }
3030
+ func (neatCallSiteSpanProcessor) OnEnd(sdktrace.ReadOnlySpan) {}
3031
+ func (neatCallSiteSpanProcessor) Shutdown(context.Context) error { return nil }
3032
+ func (neatCallSiteSpanProcessor) ForceFlush(context.Context) error { return nil }
3033
+
3034
+ func init() {
3035
+ root := os.Getenv("NEAT_SERVICE_ROOT")
3036
+ if root == "" {
3037
+ _, self, _, ok := runtime.Caller(0)
3038
+ if !ok { return }
3039
+ root = filepath.Dir(self)
3040
+ for {
3041
+ if _, err := os.Stat(filepath.Join(root, "go.mod")); err == nil { break }
3042
+ parent := filepath.Dir(root)
3043
+ if parent == root { return }
3044
+ root = parent
3045
+ }
3046
+ }
3047
+ root, _ = filepath.Abs(root)
3048
+ if canonical, err := filepath.EvalSymlinks(root); err == nil { root = canonical }
3049
+ exporter, err := otlptracehttp.New(context.Background())
3050
+ if err != nil { return }
3051
+ provider := sdktrace.NewTracerProvider(
3052
+ sdktrace.WithBatcher(exporter),
3053
+ sdktrace.WithSpanProcessor(neatCallSiteSpanProcessor{root: filepath.Clean(root)}),
3054
+ )
3055
+ otel.SetTracerProvider(provider)
3056
+ }
3057
+ `;
3058
+ }
3059
+ async function detect3(serviceDir) {
3060
+ return exists3(path7.join(serviceDir, "go.mod"));
3061
+ }
3062
+ async function plan3(serviceDir) {
3063
+ const manifest = path7.join(serviceDir, "go.mod");
3064
+ const raw = await fs6.readFile(manifest, "utf8");
3065
+ const main2 = await findMain(serviceDir);
3066
+ const dependencyEdits = GO_DEPS.filter((dep) => !raw.includes(dep.name)).map((dep) => ({ file: manifest, kind: "add", ...dep }));
3067
+ if (!main2) return { language: "go", serviceDir, dependencyEdits: [], entrypointEdits: [], envEdits: [], libOnly: true };
3068
+ const source = await fs6.readFile(main2, "utf8");
3069
+ const packageName = source.match(/^\s*package\s+(\w+)\s*$/m)?.[1] ?? "main";
3070
+ const generated = path7.join(path7.dirname(main2), "neat_otel.go");
3071
+ const generatedFiles = await exists3(generated) ? [] : [{ file: generated, contents: neatOtelGo(packageName) }];
3072
+ return { language: "go", serviceDir, dependencyEdits, entrypointEdits: [], envEdits: [], generatedFiles, entryFile: main2 };
3073
+ }
3074
+ async function apply3(installPlan) {
3075
+ if (installPlan.libOnly) return { serviceDir: installPlan.serviceDir, outcome: "lib-only", writtenFiles: [] };
3076
+ const writtenFiles = [];
3077
+ const byManifest = /* @__PURE__ */ new Map();
3078
+ for (const edit of installPlan.dependencyEdits) byManifest.set(edit.file, [...byManifest.get(edit.file) ?? [], edit]);
3079
+ for (const [manifest, edits] of byManifest) {
3080
+ const raw = await fs6.readFile(manifest, "utf8");
3081
+ const suffix = raw.endsWith("\n") ? "" : "\n";
3082
+ await fs6.writeFile(manifest, `${raw}${suffix}${edits.map((e) => `require ${e.name} ${e.version}`).join("\n")}
3083
+ `, "utf8");
3084
+ writtenFiles.push(manifest);
3085
+ }
3086
+ for (const generated of installPlan.generatedFiles ?? []) {
3087
+ if (await exists3(generated.file)) continue;
3088
+ await fs6.writeFile(generated.file, generated.contents, "utf8");
3089
+ writtenFiles.push(generated.file);
3090
+ }
3091
+ return { serviceDir: installPlan.serviceDir, outcome: writtenFiles.length ? "instrumented" : "already-instrumented", writtenFiles };
3092
+ }
3093
+ var goInstaller = { name: "go", detect: detect3, plan: plan3, apply: apply3 };
3094
+
2952
3095
  // src/installers/shared.ts
2953
- function isEmptyPlan(plan3) {
2954
- return plan3.dependencyEdits.length === 0 && plan3.entrypointEdits.length === 0 && plan3.envEdits.length === 0 && (plan3.generatedFiles?.length ?? 0) === 0 && plan3.nextConfigEdit === void 0;
3096
+ function isEmptyPlan(plan4) {
3097
+ return plan4.dependencyEdits.length === 0 && plan4.entrypointEdits.length === 0 && plan4.envEdits.length === 0 && (plan4.generatedFiles?.length ?? 0) === 0 && plan4.nextConfigEdit === void 0;
2955
3098
  }
2956
3099
 
2957
3100
  // src/installers/index.ts
@@ -2965,7 +3108,7 @@ var FORBIDDEN_LOCKFILES = /* @__PURE__ */ new Set([
2965
3108
  "Cargo.lock",
2966
3109
  "go.sum"
2967
3110
  ]);
2968
- var INSTALLERS = [javascriptInstaller, pythonInstaller];
3111
+ var INSTALLERS = [javascriptInstaller, pythonInstaller, goInstaller];
2969
3112
  async function pickInstaller(serviceDir) {
2970
3113
  for (const inst of INSTALLERS) {
2971
3114
  if (await inst.detect(serviceDir)) return inst;
@@ -2980,7 +3123,7 @@ function renderPatch(sections) {
2980
3123
  "No SDK installers matched the discovered services. Two reasons this",
2981
3124
  "normally happens:",
2982
3125
  " - the project uses a language NEAT does not yet instrument",
2983
- " (Java / Ruby / .NET / Go / Rust are out of MVP scope per ADR-047);",
3126
+ " (Java / Ruby / .NET / Rust are out of MVP scope per ADR-047);",
2984
3127
  " - the SDK is already installed, so the installer returned an empty",
2985
3128
  " plan.",
2986
3129
  "",
@@ -2990,22 +3133,22 @@ function renderPatch(sections) {
2990
3133
  }
2991
3134
  const lines = ["# neat install plan", ""];
2992
3135
  for (const section of sections) {
2993
- const { installer, plan: plan3 } = section;
2994
- lines.push(`## ${installer} (${plan3.language}) \u2014 ${plan3.serviceDir}`);
3136
+ const { installer, plan: plan4 } = section;
3137
+ lines.push(`## ${installer} (${plan4.language}) \u2014 ${plan4.serviceDir}`);
2995
3138
  lines.push("");
2996
- if (plan3.libOnly) {
3139
+ if (plan4.libOnly) {
2997
3140
  lines.push("### skipped \u2014 no resolvable entry point (lib-only)");
2998
3141
  lines.push("");
2999
3142
  continue;
3000
3143
  }
3001
- if (plan3.entryFile) {
3002
- lines.push(`entry: ${plan3.entryFile}`);
3144
+ if (plan4.entryFile) {
3145
+ lines.push(`entry: ${plan4.entryFile}`);
3003
3146
  lines.push("");
3004
3147
  }
3005
- if (plan3.dependencyEdits.length > 0) {
3148
+ if (plan4.dependencyEdits.length > 0) {
3006
3149
  lines.push("### dependencies");
3007
3150
  const byFile = /* @__PURE__ */ new Map();
3008
- for (const dep of plan3.dependencyEdits) {
3151
+ for (const dep of plan4.dependencyEdits) {
3009
3152
  const base = dep.file.split(/[\\/]/).pop() ?? dep.file;
3010
3153
  if (FORBIDDEN_LOCKFILES.has(base)) {
3011
3154
  throw new Error(
@@ -3024,9 +3167,9 @@ function renderPatch(sections) {
3024
3167
  }
3025
3168
  lines.push("");
3026
3169
  }
3027
- if (plan3.generatedFiles && plan3.generatedFiles.length > 0) {
3170
+ if (plan4.generatedFiles && plan4.generatedFiles.length > 0) {
3028
3171
  lines.push("### generated files");
3029
- for (const gen of plan3.generatedFiles) {
3172
+ for (const gen of plan4.generatedFiles) {
3030
3173
  lines.push(`--- (new file) ${gen.file}`);
3031
3174
  for (const ln of gen.contents.split(/\r?\n/)) {
3032
3175
  lines.push(`+ ${ln}`);
@@ -3034,26 +3177,26 @@ function renderPatch(sections) {
3034
3177
  }
3035
3178
  lines.push("");
3036
3179
  }
3037
- if (plan3.entrypointEdits.length > 0) {
3180
+ if (plan4.entrypointEdits.length > 0) {
3038
3181
  lines.push("### entry-point injection");
3039
- for (const e of plan3.entrypointEdits) {
3182
+ for (const e of plan4.entrypointEdits) {
3040
3183
  lines.push(`--- ${e.file}`);
3041
3184
  lines.push(`+ ${e.after}`);
3042
3185
  lines.push(` ${e.before}`);
3043
3186
  }
3044
3187
  lines.push("");
3045
3188
  }
3046
- if (plan3.envEdits.length > 0) {
3189
+ if (plan4.envEdits.length > 0) {
3047
3190
  lines.push("### env (written to <package-dir>/.env.neat)");
3048
- for (const env of plan3.envEdits) {
3191
+ for (const env of plan4.envEdits) {
3049
3192
  lines.push(`- ${env.key}=${env.value}`);
3050
3193
  }
3051
3194
  lines.push("");
3052
3195
  }
3053
- if (plan3.nextConfigEdit) {
3196
+ if (plan4.nextConfigEdit) {
3054
3197
  lines.push("### next.config (framework flag)");
3055
- lines.push(`--- ${plan3.nextConfigEdit.file}`);
3056
- lines.push(`+ experimental: { instrumentationHook: true }, // ${plan3.nextConfigEdit.reason}`);
3198
+ lines.push(`--- ${plan4.nextConfigEdit.file}`);
3199
+ lines.push(`+ experimental: { instrumentationHook: true }, // ${plan4.nextConfigEdit.reason}`);
3057
3200
  lines.push("");
3058
3201
  }
3059
3202
  }
@@ -3061,10 +3204,10 @@ function renderPatch(sections) {
3061
3204
  }
3062
3205
 
3063
3206
  // src/orchestrator.ts
3064
- import { promises as fs6 } from "fs";
3207
+ import { promises as fs7 } from "fs";
3065
3208
  import http from "http";
3066
3209
  import net from "net";
3067
- import path7 from "path";
3210
+ import path8 from "path";
3068
3211
  import { fileURLToPath as fileURLToPath2 } from "url";
3069
3212
  import { spawn as spawn2 } from "child_process";
3070
3213
  import readline from "readline";
@@ -3074,7 +3217,7 @@ async function extractAndPersist(opts) {
3074
3217
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
3075
3218
  resetGraph(graphKey);
3076
3219
  const graph = getGraph(graphKey);
3077
- const projectPaths = pathsForProject(graphKey, path7.join(opts.scanPath, "neat-out"));
3220
+ const projectPaths = pathsForProject(graphKey, path8.join(opts.scanPath, "neat-out"));
3078
3221
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
3079
3222
  errorsPath: projectPaths.errorsPath
3080
3223
  });
@@ -3108,15 +3251,15 @@ async function applyInstallersOver(services, project, options = {}) {
3108
3251
  for (const svc of services) {
3109
3252
  const installer = await pickInstaller(svc.dir);
3110
3253
  if (!installer) continue;
3111
- const plan3 = await installer.plan(svc.dir, { project });
3112
- if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) {
3254
+ const plan4 = await installer.plan(svc.dir, { project });
3255
+ if (isEmptyPlan(plan4) && !plan4.libOnly && plan4.runtimeKind === void 0) {
3113
3256
  already++;
3114
3257
  continue;
3115
3258
  }
3116
- const outcome = await installer.apply(plan3);
3259
+ const outcome = await installer.apply(plan4);
3117
3260
  if (outcome.outcome === "instrumented") {
3118
3261
  instrumented++;
3119
- if (plan3.dependencyEdits.length > 0) {
3262
+ if (plan4.dependencyEdits.length > 0) {
3120
3263
  const cmd = await resolveManager(svc.dir);
3121
3264
  const key = `${cmd.pm}:${cmd.cwd}`;
3122
3265
  if (!installPlans.has(key)) installPlans.set(key, cmd);
@@ -3126,7 +3269,7 @@ async function applyInstallersOver(services, project, options = {}) {
3126
3269
  libOnly++;
3127
3270
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
3128
3271
  if (appDeps.length > 0) {
3129
- const svcName = path7.basename(svc.dir);
3272
+ const svcName = path8.basename(svc.dir);
3130
3273
  const list = appDeps.join(", ");
3131
3274
  console.warn(
3132
3275
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -3139,7 +3282,7 @@ async function applyInstallersOver(services, project, options = {}) {
3139
3282
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
3140
3283
  } else if (outcome.outcome === "react-native") {
3141
3284
  reactNative++;
3142
- const svcName = path7.basename(svc.dir);
3285
+ const svcName = path8.basename(svc.dir);
3143
3286
  console.log(
3144
3287
  `neat: ${svc.dir} detected as React Native / Expo
3145
3288
  The installer doesn't cover this runtime deterministically.
@@ -3150,7 +3293,7 @@ async function applyInstallersOver(services, project, options = {}) {
3150
3293
  );
3151
3294
  } else if (outcome.outcome === "bun") {
3152
3295
  bun++;
3153
- const svcName = path7.basename(svc.dir);
3296
+ const svcName = path8.basename(svc.dir);
3154
3297
  console.log(
3155
3298
  `neat: ${svc.dir} detected as Bun
3156
3299
  The installer doesn't cover this runtime deterministically.
@@ -3161,7 +3304,7 @@ async function applyInstallersOver(services, project, options = {}) {
3161
3304
  );
3162
3305
  } else if (outcome.outcome === "deno") {
3163
3306
  deno++;
3164
- const svcName = path7.basename(svc.dir);
3307
+ const svcName = path8.basename(svc.dir);
3165
3308
  console.log(
3166
3309
  `neat: ${svc.dir} detected as Deno
3167
3310
  The installer doesn't cover this runtime deterministically.
@@ -3172,7 +3315,7 @@ async function applyInstallersOver(services, project, options = {}) {
3172
3315
  );
3173
3316
  } else if (outcome.outcome === "cloudflare-workers") {
3174
3317
  cloudflareWorkers++;
3175
- const svcName = path7.basename(svc.dir);
3318
+ const svcName = path8.basename(svc.dir);
3176
3319
  console.log(
3177
3320
  `neat: ${svc.dir} detected as Cloudflare Workers
3178
3321
  The installer doesn't cover this runtime deterministically.
@@ -3183,7 +3326,7 @@ async function applyInstallersOver(services, project, options = {}) {
3183
3326
  );
3184
3327
  } else if (outcome.outcome === "electron") {
3185
3328
  electron++;
3186
- const svcName = path7.basename(svc.dir);
3329
+ const svcName = path8.basename(svc.dir);
3187
3330
  console.log(
3188
3331
  `neat: ${svc.dir} detected as Electron
3189
3332
  The installer doesn't cover this runtime deterministically.
@@ -3196,7 +3339,7 @@ async function applyInstallersOver(services, project, options = {}) {
3196
3339
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
3197
3340
  const gaps = uninstrumentedLibraries(svc.pkg);
3198
3341
  if (gaps.length > 0) {
3199
- const svcName = path7.basename(svc.dir);
3342
+ const svcName = path8.basename(svc.dir);
3200
3343
  const list = gaps.join(", ");
3201
3344
  const subject = gaps.length === 1 ? "this library" : "these libraries";
3202
3345
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -3402,24 +3545,24 @@ async function persistedPortsFor(scanPath) {
3402
3545
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
3403
3546
  }
3404
3547
  async function acquireSpawnLock(scanPath) {
3405
- const lockPath = path7.join(scanPath, "neat-out", "daemon.spawn.lock");
3406
- await fs6.mkdir(path7.dirname(lockPath), { recursive: true });
3548
+ const lockPath = path8.join(scanPath, "neat-out", "daemon.spawn.lock");
3549
+ await fs7.mkdir(path8.dirname(lockPath), { recursive: true });
3407
3550
  const STALE_LOCK_MS = 6e4;
3408
3551
  try {
3409
- const fd = await fs6.open(lockPath, "wx");
3552
+ const fd = await fs7.open(lockPath, "wx");
3410
3553
  await fd.writeFile(`${process.pid}
3411
3554
  `, "utf8");
3412
3555
  await fd.close();
3413
3556
  return async () => {
3414
- await fs6.unlink(lockPath).catch(() => {
3557
+ await fs7.unlink(lockPath).catch(() => {
3415
3558
  });
3416
3559
  };
3417
3560
  } catch (err) {
3418
3561
  if (err.code !== "EEXIST") return null;
3419
3562
  try {
3420
- const stat = await fs6.stat(lockPath);
3563
+ const stat = await fs7.stat(lockPath);
3421
3564
  if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
3422
- await fs6.unlink(lockPath).catch(() => {
3565
+ await fs7.unlink(lockPath).catch(() => {
3423
3566
  });
3424
3567
  return acquireSpawnLock(scanPath);
3425
3568
  }
@@ -3448,13 +3591,13 @@ async function healthIsForProject(restPort, project) {
3448
3591
  return false;
3449
3592
  }
3450
3593
  function daemonLogPath(projectPath2) {
3451
- return path7.join(projectPath2, "neat-out", "daemon.log");
3594
+ return path8.join(projectPath2, "neat-out", "daemon.log");
3452
3595
  }
3453
3596
  function spawnDaemonDetached(spec) {
3454
- const here = path7.dirname(fileURLToPath2(import.meta.url));
3597
+ const here = path8.dirname(fileURLToPath2(import.meta.url));
3455
3598
  const candidates = [
3456
- path7.join(here, "neatd.cjs"),
3457
- path7.join(here, "neatd.js")
3599
+ path8.join(here, "neatd.cjs"),
3600
+ path8.join(here, "neatd.js")
3458
3601
  ];
3459
3602
  let entry2 = null;
3460
3603
  const fsSync = __require("fs");
@@ -3484,7 +3627,7 @@ function spawnDaemonDetached(spec) {
3484
3627
  let logFd = null;
3485
3628
  if (spec) {
3486
3629
  const logPath = daemonLogPath(spec.projectPath);
3487
- fsSync.mkdirSync(path7.dirname(logPath), { recursive: true });
3630
+ fsSync.mkdirSync(path8.dirname(logPath), { recursive: true });
3488
3631
  logFd = fsSync.openSync(logPath, "a");
3489
3632
  }
3490
3633
  const child = spawn2(process.execPath, [entry2, "start"], {
@@ -3523,7 +3666,7 @@ async function runOrchestrator(opts) {
3523
3666
  browser: "skipped"
3524
3667
  }
3525
3668
  };
3526
- const stat = await fs6.stat(opts.scanPath).catch(() => null);
3669
+ const stat = await fs7.stat(opts.scanPath).catch(() => null);
3527
3670
  if (!stat || !stat.isDirectory()) {
3528
3671
  console.error(`neat: ${opts.scanPath} is not a directory`);
3529
3672
  result.exitCode = 2;
@@ -3683,7 +3826,7 @@ async function runOrchestrator(opts) {
3683
3826
  result.steps.browser = openBrowser(dashboardUrl);
3684
3827
  }
3685
3828
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
3686
- const daemonLog = daemonRunning ? path7.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
3829
+ const daemonLog = daemonRunning ? path8.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
3687
3830
  printSummary(result, graph, dashboardUrl, daemonLog);
3688
3831
  return result;
3689
3832
  }
@@ -4091,7 +4234,7 @@ async function connectorTest(deps, id) {
4091
4234
  function printConnectorUsage(write) {
4092
4235
  write("usage: neat connector <add|list|remove|test> [args]");
4093
4236
  write(` providers: ${knownProviderNames().join(", ")}`);
4094
- write(" pull (polled): supabase, railway, firebase, cloudflare");
4237
+ write(" pull (polled): supabase, railway, firebase, cloudflare, neon");
4095
4238
  write(" push (drains): vercel \u2014 provisions a Vercel trace Drain that forwards");
4096
4239
  write(" traces to the daemon's OTLP receiver; no app instrumentation");
4097
4240
  write(" add <provider> add a connector; validates the credential first (--skip-validate to skip)");
@@ -4100,6 +4243,8 @@ function printConnectorUsage(write) {
4100
4243
  write(" vercel: neat connector add vercel --token $VERCEL_TOKEN \\");
4101
4244
  write(" --otel-token $NEAT_OTEL_TOKEN --team-id <teamId> \\");
4102
4245
  write(" --endpoint https://<public-host>/v1/traces [--project-ids <id,id>]");
4246
+ write(" neon: neat connector add neon --credential $NEON_OBSERVER_URL \\");
4247
+ write(" --project-id <projectId> --service-name <serviceName>");
4103
4248
  write(" list list configured connectors (credentials shown redacted)");
4104
4249
  write(" flags: --project <name>");
4105
4250
  write(" remove <id> remove a connector by id (a push provider also has its drain deleted)");
@@ -4138,27 +4283,27 @@ async function runConnectorCommand(rawArgs, deps = {}) {
4138
4283
  }
4139
4284
 
4140
4285
  // src/hooks-cli.ts
4141
- import path8 from "path";
4286
+ import path9 from "path";
4142
4287
  import os from "os";
4143
- import { promises as fs7 } from "fs";
4288
+ import { promises as fs8 } from "fs";
4144
4289
  import { fileURLToPath as fileURLToPath3 } from "url";
4145
4290
  var HOOK_FILENAME = "neat-search-nudge.mjs";
4146
4291
  var GUIDE_FILENAME = "GRAPH_FIRST.md";
4147
4292
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
4148
4293
  var HOOK_MATCHER = "Grep|Glob|Bash";
4149
4294
  function moduleDir() {
4150
- return typeof __dirname !== "undefined" ? __dirname : path8.dirname(fileURLToPath3(import.meta.url));
4295
+ return typeof __dirname !== "undefined" ? __dirname : path9.dirname(fileURLToPath3(import.meta.url));
4151
4296
  }
4152
4297
  async function readSkillAsset(rel) {
4153
4298
  const here = moduleDir();
4154
4299
  const candidates = [
4155
- path8.resolve(here, "../../claude-skill", rel),
4156
- path8.resolve(here, "../../../claude-skill", rel),
4157
- path8.resolve(here, "../claude-skill", rel)
4300
+ path9.resolve(here, "../../claude-skill", rel),
4301
+ path9.resolve(here, "../../../claude-skill", rel),
4302
+ path9.resolve(here, "../claude-skill", rel)
4158
4303
  ];
4159
4304
  for (const candidate of candidates) {
4160
4305
  try {
4161
- return await fs7.readFile(candidate, "utf8");
4306
+ return await fs8.readFile(candidate, "utf8");
4162
4307
  } catch {
4163
4308
  }
4164
4309
  }
@@ -4168,17 +4313,17 @@ async function readSkillAsset(rel) {
4168
4313
  }
4169
4314
  function neatHome() {
4170
4315
  const override = process.env.NEAT_HOME;
4171
- if (override && override.length > 0) return path8.resolve(override);
4172
- return path8.join(os.homedir(), ".neat");
4316
+ if (override && override.length > 0) return path9.resolve(override);
4317
+ return path9.join(os.homedir(), ".neat");
4173
4318
  }
4174
4319
  function claudeSettingsPath() {
4175
4320
  const override = process.env.NEAT_CLAUDE_SETTINGS;
4176
- if (override && override.length > 0) return path8.resolve(override);
4321
+ if (override && override.length > 0) return path9.resolve(override);
4177
4322
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
4178
- return path8.join(home, ".claude", "settings.json");
4323
+ return path9.join(home, ".claude", "settings.json");
4179
4324
  }
4180
4325
  function installedHookPath() {
4181
- return path8.join(neatHome(), "hooks", HOOK_FILENAME);
4326
+ return path9.join(neatHome(), "hooks", HOOK_FILENAME);
4182
4327
  }
4183
4328
  function isNeatSearchEntry(entry2) {
4184
4329
  return (entry2.hooks ?? []).some(
@@ -4211,14 +4356,14 @@ async function runHooks(opts) {
4211
4356
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
4212
4357
  const guide = await readSkillAsset(GUIDE_FILENAME);
4213
4358
  const scriptPath = installedHookPath();
4214
- await fs7.mkdir(path8.dirname(scriptPath), { recursive: true });
4215
- await fs7.writeFile(scriptPath, hookScript, { mode: 493 });
4216
- const guidePath = path8.join(neatHome(), GUIDE_INSTALL_NAME);
4217
- await fs7.writeFile(guidePath, guide, "utf8");
4359
+ await fs8.mkdir(path9.dirname(scriptPath), { recursive: true });
4360
+ await fs8.writeFile(scriptPath, hookScript, { mode: 493 });
4361
+ const guidePath = path9.join(neatHome(), GUIDE_INSTALL_NAME);
4362
+ await fs8.writeFile(guidePath, guide, "utf8");
4218
4363
  const settingsFile = claudeSettingsPath();
4219
4364
  let settings = {};
4220
4365
  try {
4221
- settings = JSON.parse(await fs7.readFile(settingsFile, "utf8"));
4366
+ settings = JSON.parse(await fs8.readFile(settingsFile, "utf8"));
4222
4367
  } catch (err) {
4223
4368
  if (err.code !== "ENOENT") {
4224
4369
  console.error(
@@ -4240,8 +4385,8 @@ async function runHooks(opts) {
4240
4385
  ...settings,
4241
4386
  hooks: { ...hooks, PreToolUse: preToolUse }
4242
4387
  };
4243
- await fs7.mkdir(path8.dirname(settingsFile), { recursive: true });
4244
- await fs7.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
4388
+ await fs8.mkdir(path9.dirname(settingsFile), { recursive: true });
4389
+ await fs8.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
4245
4390
  console.log(`neat hooks: installed the search-nudge hook`);
4246
4391
  console.log(` script: ${scriptPath}`);
4247
4392
  console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
@@ -4311,7 +4456,7 @@ async function runHooksCommand(args) {
4311
4456
  }
4312
4457
 
4313
4458
  // src/cli-verbs.ts
4314
- import path9 from "path";
4459
+ import path10 from "path";
4315
4460
 
4316
4461
  // src/cli-client.ts
4317
4462
  import { Provenance as Provenance2 } from "@neat.is/types";
@@ -4339,10 +4484,10 @@ function createHttpClient(baseUrl, bearerToken) {
4339
4484
  const root = baseUrl.replace(/\/$/, "");
4340
4485
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
4341
4486
  return {
4342
- async get(path11) {
4487
+ async get(path12) {
4343
4488
  let res;
4344
4489
  try {
4345
- res = await fetch(`${root}${path11}`, {
4490
+ res = await fetch(`${root}${path12}`, {
4346
4491
  headers: { ...authHeader }
4347
4492
  });
4348
4493
  } catch (err) {
@@ -4354,16 +4499,16 @@ function createHttpClient(baseUrl, bearerToken) {
4354
4499
  const body = await res.text().catch(() => "");
4355
4500
  throw new HttpError(
4356
4501
  res.status,
4357
- `${res.status} ${res.statusText} on GET ${path11}: ${body}`,
4502
+ `${res.status} ${res.statusText} on GET ${path12}: ${body}`,
4358
4503
  body
4359
4504
  );
4360
4505
  }
4361
4506
  return await res.json();
4362
4507
  },
4363
- async post(path11, body) {
4508
+ async post(path12, body) {
4364
4509
  let res;
4365
4510
  try {
4366
- res = await fetch(`${root}${path11}`, {
4511
+ res = await fetch(`${root}${path12}`, {
4367
4512
  method: "POST",
4368
4513
  headers: { "content-type": "application/json", ...authHeader },
4369
4514
  body: JSON.stringify(body)
@@ -4377,7 +4522,7 @@ function createHttpClient(baseUrl, bearerToken) {
4377
4522
  const text = await res.text().catch(() => "");
4378
4523
  throw new HttpError(
4379
4524
  res.status,
4380
- `${res.status} ${res.statusText} on POST ${path11}: ${text}`,
4525
+ `${res.status} ${res.statusText} on POST ${path12}: ${text}`,
4381
4526
  text
4382
4527
  );
4383
4528
  }
@@ -4391,12 +4536,12 @@ function projectPath(project, suffix) {
4391
4536
  }
4392
4537
  async function runRootCause(client, input) {
4393
4538
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
4394
- const path11 = projectPath(
4539
+ const path12 = projectPath(
4395
4540
  input.project,
4396
4541
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
4397
4542
  );
4398
4543
  try {
4399
- const result = await client.get(path11);
4544
+ const result = await client.get(path12);
4400
4545
  const arrowPath = result.traversalPath.join(" \u2190 ");
4401
4546
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
4402
4547
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -4422,12 +4567,12 @@ async function runRootCause(client, input) {
4422
4567
  }
4423
4568
  async function runBlastRadius(client, input) {
4424
4569
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
4425
- const path11 = projectPath(
4570
+ const path12 = projectPath(
4426
4571
  input.project,
4427
4572
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
4428
4573
  );
4429
4574
  try {
4430
- const result = await client.get(path11);
4575
+ const result = await client.get(path12);
4431
4576
  if (result.totalAffected === 0) {
4432
4577
  return {
4433
4578
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -4461,12 +4606,12 @@ function formatBlastEntry(n) {
4461
4606
  }
4462
4607
  async function runDependencies(client, input) {
4463
4608
  const depth = input.depth ?? 3;
4464
- const path11 = projectPath(
4609
+ const path12 = projectPath(
4465
4610
  input.project,
4466
4611
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
4467
4612
  );
4468
4613
  try {
4469
- const result = await client.get(path11);
4614
+ const result = await client.get(path12);
4470
4615
  if (result.total === 0) {
4471
4616
  return {
4472
4617
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -4558,9 +4703,9 @@ function formatDuration(ms) {
4558
4703
  return `${Math.round(h / 24)}d`;
4559
4704
  }
4560
4705
  async function runIncidents(client, input) {
4561
- const path11 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
4706
+ const path12 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
4562
4707
  try {
4563
- const body = await client.get(path11);
4708
+ const body = await client.get(path12);
4564
4709
  const events = body.events;
4565
4710
  if (events.length === 0) {
4566
4711
  return {
@@ -4754,6 +4899,9 @@ function formatDivergenceLine(d) {
4754
4899
  switch (d.type) {
4755
4900
  case "missing-observed":
4756
4901
  case "missing-extracted":
4902
+ if (d.column) {
4903
+ return ` \u2022 [${d.type}] ${d.table ?? d.source} column ${d.column} \u2014 confidence ${d.confidence.toFixed(2)}`;
4904
+ }
4757
4905
  return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} (${d.edgeType}) \u2014 confidence ${d.confidence.toFixed(2)}`;
4758
4906
  case "version-mismatch":
4759
4907
  return ` \u2022 [${d.type}] ${d.source} \u2192 ${d.target} \u2014 declared ${d.extractedVersion}, observed engine ${d.observedVersion} (${d.compatibility})`;
@@ -4850,7 +4998,7 @@ async function resolveProjectEntry(opts) {
4850
4998
  const cwd = opts.cwd ?? process.cwd();
4851
4999
  const resolvedCwd = await normalizeProjectPath(cwd);
4852
5000
  for (const entry2 of entries) {
4853
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path9.sep}`)) {
5001
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path10.sep}`)) {
4854
5002
  return entry2;
4855
5003
  }
4856
5004
  }
@@ -5297,15 +5445,15 @@ async function buildPatchSections(services, project) {
5297
5445
  for (const svc of services) {
5298
5446
  const installer = await pickInstaller(svc.dir);
5299
5447
  if (!installer) continue;
5300
- const plan3 = await installer.plan(svc.dir, { project });
5301
- if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) continue;
5302
- sections.push({ installer: installer.name, plan: plan3 });
5448
+ const plan4 = await installer.plan(svc.dir, { project });
5449
+ if (isEmptyPlan(plan4) && !plan4.libOnly && plan4.runtimeKind === void 0) continue;
5450
+ sections.push({ installer: installer.name, plan: plan4 });
5303
5451
  }
5304
5452
  return sections;
5305
5453
  }
5306
5454
  async function runInit(opts) {
5307
5455
  const written = [];
5308
- const stat = await fs8.stat(opts.scanPath).catch(() => null);
5456
+ const stat = await fs9.stat(opts.scanPath).catch(() => null);
5309
5457
  if (!stat || !stat.isDirectory()) {
5310
5458
  console.error(`neat init: ${opts.scanPath} is not a directory`);
5311
5459
  return { exitCode: 2, writtenFiles: written };
@@ -5314,13 +5462,13 @@ async function runInit(opts) {
5314
5462
  printDiscoveryReport(opts, services);
5315
5463
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
5316
5464
  const patch = renderPatch(sections);
5317
- const patchPath = path10.join(opts.scanPath, "neat.patch");
5465
+ const patchPath = path11.join(opts.scanPath, "neat.patch");
5318
5466
  if (opts.dryRun) {
5319
- await fs8.writeFile(patchPath, patch, "utf8");
5467
+ await fs9.writeFile(patchPath, patch, "utf8");
5320
5468
  written.push(patchPath);
5321
5469
  console.log(`dry-run: patch written to ${patchPath}`);
5322
- const gitignorePath = path10.join(opts.scanPath, ".gitignore");
5323
- const gitignoreExists = await fs8.stat(gitignorePath).then(() => true).catch(() => false);
5470
+ const gitignorePath = path11.join(opts.scanPath, ".gitignore");
5471
+ const gitignoreExists = await fs9.stat(gitignorePath).then(() => true).catch(() => false);
5324
5472
  const verb = gitignoreExists ? "append" : "create";
5325
5473
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
5326
5474
  console.log("rerun without --dry-run to register and snapshot.");
@@ -5331,9 +5479,9 @@ async function runInit(opts) {
5331
5479
  const graph = getGraph(graphKey);
5332
5480
  const projectPaths = pathsForProject(
5333
5481
  graphKey,
5334
- path10.join(opts.scanPath, "neat-out")
5482
+ path11.join(opts.scanPath, "neat-out")
5335
5483
  );
5336
- const errorsPath = path10.join(path10.dirname(opts.outPath), path10.basename(projectPaths.errorsPath));
5484
+ const errorsPath = path11.join(path11.dirname(opts.outPath), path11.basename(projectPaths.errorsPath));
5337
5485
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
5338
5486
  await saveGraphToDisk(graph, opts.outPath);
5339
5487
  written.push(opts.outPath);
@@ -5412,7 +5560,7 @@ async function runInit(opts) {
5412
5560
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
5413
5561
  }
5414
5562
  } else {
5415
- await fs8.writeFile(patchPath, patch, "utf8");
5563
+ await fs9.writeFile(patchPath, patch, "utf8");
5416
5564
  written.push(patchPath);
5417
5565
  }
5418
5566
  }
@@ -5452,9 +5600,9 @@ var CLAUDE_SKILL_CONFIG = {
5452
5600
  };
5453
5601
  function claudeConfigPath() {
5454
5602
  const override = process.env.NEAT_CLAUDE_CONFIG;
5455
- if (override && override.length > 0) return path10.resolve(override);
5603
+ if (override && override.length > 0) return path11.resolve(override);
5456
5604
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
5457
- return path10.join(home, ".claude.json");
5605
+ return path11.join(home, ".claude.json");
5458
5606
  }
5459
5607
  async function runSkill(opts) {
5460
5608
  const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -5466,7 +5614,7 @@ async function runSkill(opts) {
5466
5614
  const target = claudeConfigPath();
5467
5615
  let existing = {};
5468
5616
  try {
5469
- existing = JSON.parse(await fs8.readFile(target, "utf8"));
5617
+ existing = JSON.parse(await fs9.readFile(target, "utf8"));
5470
5618
  } catch (err) {
5471
5619
  if (err.code !== "ENOENT") {
5472
5620
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -5478,8 +5626,8 @@ async function runSkill(opts) {
5478
5626
  ...existing,
5479
5627
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
5480
5628
  };
5481
- await fs8.mkdir(path10.dirname(target), { recursive: true });
5482
- await fs8.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
5629
+ await fs9.mkdir(path11.dirname(target), { recursive: true });
5630
+ await fs9.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
5483
5631
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
5484
5632
  console.log("restart Claude Code to pick up the new MCP server.");
5485
5633
  console.log("");
@@ -5531,7 +5679,7 @@ async function main() {
5531
5679
  }
5532
5680
  const cmd = argvParsed.positional[0];
5533
5681
  const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
5534
- const { positional, apply: apply3, dryRun, noInstall } = parsed;
5682
+ const { positional, apply: apply4, dryRun, noInstall } = parsed;
5535
5683
  const project = parsed.project ?? DEFAULT_PROJECT;
5536
5684
  if (cmd === "init") {
5537
5685
  const target = positional[0];
@@ -5540,22 +5688,22 @@ async function main() {
5540
5688
  usage2();
5541
5689
  process.exit(2);
5542
5690
  }
5543
- if (apply3 && dryRun) {
5691
+ if (apply4 && dryRun) {
5544
5692
  console.error("neat init: --apply and --dry-run are mutually exclusive");
5545
5693
  process.exit(2);
5546
5694
  }
5547
- const scanPath = path10.resolve(target);
5695
+ const scanPath = path11.resolve(target);
5548
5696
  const projectExplicit = parsed.project !== null;
5549
- const projectName = projectExplicit ? project : path10.basename(scanPath);
5697
+ const projectName = projectExplicit ? project : path11.basename(scanPath);
5550
5698
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
5551
- const fallback = pathsForProject(projectKey, path10.join(scanPath, "neat-out")).snapshotPath;
5552
- const outPath = path10.resolve(process.env.NEAT_OUT_PATH ?? fallback);
5699
+ const fallback = pathsForProject(projectKey, path11.join(scanPath, "neat-out")).snapshotPath;
5700
+ const outPath = path11.resolve(process.env.NEAT_OUT_PATH ?? fallback);
5553
5701
  const result = await runInit({
5554
5702
  scanPath,
5555
5703
  outPath,
5556
5704
  project: projectName,
5557
5705
  projectExplicit,
5558
- apply: apply3,
5706
+ apply: apply4,
5559
5707
  dryRun,
5560
5708
  noInstall,
5561
5709
  verbose: parsed.verbose
@@ -5570,21 +5718,21 @@ async function main() {
5570
5718
  usage2();
5571
5719
  process.exit(2);
5572
5720
  }
5573
- const scanPath = path10.resolve(target);
5574
- const stat = await fs8.stat(scanPath).catch(() => null);
5721
+ const scanPath = path11.resolve(target);
5722
+ const stat = await fs9.stat(scanPath).catch(() => null);
5575
5723
  if (!stat || !stat.isDirectory()) {
5576
5724
  console.error(`neat watch: ${scanPath} is not a directory`);
5577
5725
  process.exit(2);
5578
5726
  }
5579
- const projectPaths = pathsForProject(project, path10.join(scanPath, "neat-out"));
5580
- const outPath = path10.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
5581
- const errorsPath = path10.resolve(
5582
- process.env.NEAT_ERRORS_PATH ?? path10.join(path10.dirname(outPath), path10.basename(projectPaths.errorsPath))
5727
+ const projectPaths = pathsForProject(project, path11.join(scanPath, "neat-out"));
5728
+ const outPath = path11.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
5729
+ const errorsPath = path11.resolve(
5730
+ process.env.NEAT_ERRORS_PATH ?? path11.join(path11.dirname(outPath), path11.basename(projectPaths.errorsPath))
5583
5731
  );
5584
- const staleEventsPath = path10.resolve(
5585
- process.env.NEAT_STALE_EVENTS_PATH ?? path10.join(path10.dirname(outPath), path10.basename(projectPaths.staleEventsPath))
5732
+ const staleEventsPath = path11.resolve(
5733
+ process.env.NEAT_STALE_EVENTS_PATH ?? path11.join(path11.dirname(outPath), path11.basename(projectPaths.staleEventsPath))
5586
5734
  );
5587
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path10.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
5735
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path11.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
5588
5736
  const handle = await startWatch(getGraph(project), {
5589
5737
  scanPath,
5590
5738
  outPath,
@@ -5593,7 +5741,7 @@ async function main() {
5593
5741
  project,
5594
5742
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
5595
5743
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
5596
- neatHome: process.env.NEAT_HOME ? path10.resolve(process.env.NEAT_HOME) : path10.join(os2.homedir(), ".neat"),
5744
+ neatHome: process.env.NEAT_HOME ? path11.resolve(process.env.NEAT_HOME) : path11.join(os2.homedir(), ".neat"),
5597
5745
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
5598
5746
  host: process.env.HOST ?? "0.0.0.0",
5599
5747
  port: Number(process.env.PORT ?? 8080),
@@ -5770,11 +5918,11 @@ async function main() {
5770
5918
  process.exit(1);
5771
5919
  }
5772
5920
  async function tryOrchestrator(cmd, parsed) {
5773
- const scanPath = path10.resolve(cmd);
5774
- const stat = await fs8.stat(scanPath).catch(() => null);
5921
+ const scanPath = path11.resolve(cmd);
5922
+ const stat = await fs9.stat(scanPath).catch(() => null);
5775
5923
  if (!stat || !stat.isDirectory()) return null;
5776
5924
  const projectExplicit = parsed.project !== null;
5777
- const projectName = projectExplicit ? parsed.project : path10.basename(scanPath);
5925
+ const projectName = projectExplicit ? parsed.project : path11.basename(scanPath);
5778
5926
  const result = await runOrchestrator({
5779
5927
  scanPath,
5780
5928
  project: projectName,