@neat.is/core 0.4.9 → 0.4.11

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
@@ -41,7 +41,7 @@ import {
41
41
  setStatus,
42
42
  startPersistLoop,
43
43
  startStalenessLoop
44
- } from "./chunk-J5CEKCTR.js";
44
+ } from "./chunk-OJHI33LD.js";
45
45
  import {
46
46
  startOtelGrpcReceiver
47
47
  } from "./chunk-3QCRUEQD.js";
@@ -424,6 +424,7 @@ async function startWatch(graph, opts) {
424
424
  const onSpan = makeSpanHandler({
425
425
  graph,
426
426
  errorsPath: opts.errorsPath,
427
+ scanPath: opts.scanPath,
427
428
  project: projectName,
428
429
  writeErrorEventInline: false,
429
430
  onPolicyTrigger
@@ -438,6 +439,7 @@ async function startWatch(graph, opts) {
438
439
  const onSpanGrpc = makeSpanHandler({
439
440
  graph,
440
441
  errorsPath: opts.errorsPath,
442
+ scanPath: opts.scanPath,
441
443
  project: projectName,
442
444
  onPolicyTrigger
443
445
  });
@@ -697,13 +699,20 @@ import path4 from "path";
697
699
 
698
700
  // src/installers/templates.ts
699
701
  var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
700
- var OTEL_INIT_STAMP = "// neat-template-version: 3 \u2014 file-first call-site capture (ADR-089) + OTLP bearer auth (#410).";
702
+ var OTEL_INIT_STAMP = "// neat-template-version: 4 \u2014 layered file-first capture (ADR-090): stack walk + handler-entry + off-stack facades.";
701
703
  var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
702
- function neatCallsiteProcessorSource(ts) {
703
- const stackT = ts ? ": string" : "";
704
+ function neatCaptureSource(ts) {
704
705
  const spanT = ts ? ": any" : "";
705
706
  const strOpt = ts ? ": string | undefined" : "";
706
- return `function __neatPickUserFrame(stack${stackT}) {
707
+ const anyT = ts ? ": any" : "";
708
+ const fnT = ts ? ": any" : "";
709
+ const arrAny = ts ? ": any[]" : "";
710
+ return `// Context key shared by the facade/handler wraps (writers) and the
711
+ // processor fallback (reader). Symbol.for keeps it stable even if the wraps
712
+ // and the processor end up in different module instances.
713
+ const NEAT_USER_FRAME = Symbol.for('neat.user-frame')
714
+
715
+ function __neatPickUserFrame(stack${strOpt}) {
707
716
  const lines = String(stack || '').split('\\n')
708
717
  for (let i = 0; i < lines.length; i++) {
709
718
  const raw = lines[i].trim()
@@ -712,6 +721,9 @@ function neatCallsiteProcessorSource(ts) {
712
721
  if (raw.indexOf('@opentelemetry') !== -1) continue
713
722
  if (raw.indexOf('node:') !== -1) continue
714
723
  if (raw.indexOf('NeatCallSiteSpanProcessor') !== -1) continue
724
+ // Skip NEAT's own inlined wraps/helpers (all carry the __neat prefix) so a
725
+ // facade frame never masquerades as the user's call site.
726
+ if (raw.indexOf('__neat') !== -1) continue
715
727
  const bodyText = raw.slice(3)
716
728
  const loc = bodyText.match(/:(\\d+):(\\d+)\\)?$/)
717
729
  if (!loc) continue
@@ -733,32 +745,245 @@ function neatCallsiteProcessorSource(ts) {
733
745
  return null
734
746
  }
735
747
 
748
+ // Layer 2/3 fallback source: the frame a handler-entry or facade wrap pushed
749
+ // into context. Reads the span's own parent context first, then the active
750
+ // context \u2014 the capture spike confirmed both return the value for undici.
751
+ function __neatFrameFromContext(parentContext${anyT}) {
752
+ try {
753
+ const fromParent =
754
+ parentContext && typeof parentContext.getValue === 'function'
755
+ ? parentContext.getValue(NEAT_USER_FRAME)
756
+ : undefined
757
+ return fromParent || context.active().getValue(NEAT_USER_FRAME) || null
758
+ } catch (_e) {
759
+ return null
760
+ }
761
+ }
762
+
763
+ function __neatSetCodeAttrs(span${spanT}, frame${anyT}) {
764
+ span.setAttribute('code.filepath', frame.filepath)
765
+ if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
766
+ if (frame.function) span.setAttribute('code.function', frame.function)
767
+ }
768
+
736
769
  class NeatCallSiteSpanProcessor {
737
- onStart(span${spanT}) {
770
+ onStart(span${spanT}, parentContext${anyT}) {
738
771
  if (!span || (span.kind !== 2 && span.kind !== 3)) return
739
- const frame = __neatPickUserFrame(new Error().stack)
772
+ // Layer 1 \u2014 synchronous stack walk (sync-wrapper instrumentations).
773
+ let frame = __neatPickUserFrame(new Error().stack)
774
+ // Layer 2/3 \u2014 the handler-entry or off-stack-facade frame from context.
775
+ if (!frame) frame = __neatFrameFromContext(parentContext)
740
776
  if (!frame) return
741
- span.setAttribute('code.filepath', frame.filepath)
742
- if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
743
- if (frame.function) span.setAttribute('code.function', frame.function)
777
+ __neatSetCodeAttrs(span, frame)
744
778
  }
745
779
  onEnd() {}
746
780
  forceFlush() { return Promise.resolve() }
747
781
  shutdown() { return Promise.resolve() }
782
+ }
783
+
784
+ // Capture the caller's synchronous frame at the wrap point and run \`fn\` with
785
+ // that frame pushed into the active context (file-awareness.md \xA74 layer 3). An
786
+ // off-stack instrumentation that creates its span inside \`fn\` inherits the
787
+ // frame; the processor reads it via __neatFrameFromContext.
788
+ function __neatRunWithUserFrame(fn${fnT}) {
789
+ const frame = __neatPickUserFrame(new Error().stack)
790
+ if (!frame) return fn()
791
+ return context.with(context.active().setValue(NEAT_USER_FRAME, frame), fn)
792
+ }
793
+
794
+ // Handler-entry attribution (file-awareness.md \xA74 layer 2). Stamp the framework
795
+ // SERVER span with the handler frame captured at route registration, and push
796
+ // the same frame into context so downstream CLIENT/PRODUCER spans inherit the
797
+ // handler-file floor when their own stack carries none.
798
+ function __neatStampHandler(frame${anyT}, run${fnT}) {
799
+ try {
800
+ const active = trace.getActiveSpan()
801
+ if (active && active.kind === 1 && typeof active.setAttribute === 'function') {
802
+ __neatSetCodeAttrs(active, frame)
803
+ }
804
+ } catch (_e) {}
805
+ try {
806
+ return context.with(context.active().setValue(NEAT_USER_FRAME, frame), run)
807
+ } catch (_e) {
808
+ return run()
809
+ }
810
+ }
811
+
812
+ // require-in-the-middle is a transitive dependency of the OTel instrumentation
813
+ // packages NEAT installs, so it resolves in any instrumented CJS service.
814
+ // Guarded: when it's absent (or the host runs as pure ESM, where require isn't
815
+ // defined) the off-stack/handler wraps degrade to the stack walk + context
816
+ // floor, never to a crash.
817
+ function __neatHook(modules${arrAny}, onload${fnT}) {
818
+ try {
819
+ const RITM = require('require-in-the-middle')
820
+ const Hook = RITM && RITM.Hook ? RITM.Hook : RITM
821
+ new Hook(modules, { internals: false }, onload)
822
+ return true
823
+ } catch (_e) {
824
+ return false
825
+ }
826
+ }
827
+
828
+ // Off-stack facade: Node's built-in fetch / undici. The instrumentation creates
829
+ // the CLIENT span inside a diagnostics_channel handler detached from the
830
+ // caller's stack, so wrap the global so the user frame is in context when the
831
+ // span is created. The capture spike (2026-05-28) validated this on real
832
+ // undici. .name is restored to 'fetch' for ecosystem compatibility; the inner
833
+ // __neat name is what the frame skip keys on.
834
+ function __neatWrapFetch() {
835
+ try {
836
+ const g${anyT} = globalThis
837
+ if (typeof g.fetch === 'function' && !g.fetch.__neatWrapped) {
838
+ const realFetch = g.fetch
839
+ // The wrapper keeps its __neat-prefixed name so the frame skip in
840
+ // __neatPickUserFrame never mistakes the wrapper's own frame for the
841
+ // user's call site (renaming it to 'fetch' would defeat the skip).
842
+ const __neatFetch${anyT} = function (input${anyT}, init${anyT}) {
843
+ return __neatRunWithUserFrame(function () { return realFetch(input, init) })
844
+ }
845
+ __neatFetch.__neatWrapped = true
846
+ g.fetch = __neatFetch
847
+ }
848
+ } catch (_e) {}
849
+ }
850
+
851
+ // Off-stack facade: @prisma/client. Prisma's query engine backdates its spans
852
+ // from Rust, off the caller's stack. Wrap the model methods on the client
853
+ // prototype so the call-site frame (still synchronous at \`prisma.user.find\`)
854
+ // is pushed into context for the engine dispatch.
855
+ function __neatWrapPrisma() {
856
+ __neatHook(['@prisma/client'], function (exports${anyT}) {
857
+ try {
858
+ const Client = exports && exports.PrismaClient
859
+ if (typeof Client === 'function' && !Client.__neatWrapped) {
860
+ const ops = ['findUnique','findUniqueOrThrow','findFirst','findFirstOrThrow','findMany','create','createMany','update','updateMany','upsert','delete','deleteMany','count','aggregate','groupBy']
861
+ const __neatPrismaWrapModel = function (model${anyT}) {
862
+ if (!model || model.__neatWrapped) return model
863
+ for (let i = 0; i < ops.length; i++) {
864
+ const op = ops[i]
865
+ const orig = model[op]
866
+ if (typeof orig !== 'function') continue
867
+ model[op] = function __neatPrismaOp(${ts ? "...args: any[]" : "...args"}) {
868
+ const self = this
869
+ return __neatRunWithUserFrame(function () { return orig.apply(self, args) })
870
+ }
871
+ }
872
+ model.__neatWrapped = true
873
+ return model
874
+ }
875
+ const proto = Client.prototype
876
+ const handler = function (model${anyT}) { return __neatPrismaWrapModel(model) }
877
+ // Model accessors are lazily created getters on the instance; wrap the
878
+ // \`$extends\`-free common path by trapping property access via a proxy
879
+ // on each constructed client.
880
+ exports.PrismaClient = new Proxy(Client, {
881
+ construct(Target${anyT}, argList${anyT}, NewTarget${anyT}) {
882
+ const instance = Reflect.construct(Target, argList, NewTarget)
883
+ return new Proxy(instance, {
884
+ get(target${anyT}, prop${anyT}, receiver${anyT}) {
885
+ const value = Reflect.get(target, prop, receiver)
886
+ if (value && typeof value === 'object' && typeof prop === 'string' && prop[0] !== '$' && prop[0] !== '_') {
887
+ return handler(value)
888
+ }
889
+ return value
890
+ },
891
+ })
892
+ },
893
+ })
894
+ exports.PrismaClient.__neatWrapped = true
895
+ void proto
896
+ }
897
+ } catch (_e) {}
898
+ return exports
899
+ })
900
+ }
901
+
902
+ // Handler-entry facades. express / connect share the Layer model (each route
903
+ // handler is a function registered on a Router); the wrap captures the
904
+ // registration frame and stamps + propagates it when the handler runs. The
905
+ // registry is the extensibility seam \u2014 koa, fastify (via @fastify/otel),
906
+ // nestjs, restify, and hapi add an entry as their patch surface is wired.
907
+ function __neatWrapConnectStyle(mod${anyT}) {
908
+ try {
909
+ // express() returns an app whose route verbs live on Router.prototype and
910
+ // the application proto; connect apps expose \`use\`. Wrap the registration
911
+ // verbs so the user handler is wound with its registration frame.
912
+ const verbs = ['use','get','post','put','delete','patch','all','options','head']
913
+ const wrapTarget = function (target${anyT}) {
914
+ if (!target || target.__neatVerbsWrapped) return
915
+ for (let i = 0; i < verbs.length; i++) {
916
+ const verb = verbs[i]
917
+ const orig = target[verb]
918
+ if (typeof orig !== 'function') continue
919
+ target[verb] = function __neatVerb(${ts ? "...args: any[]" : "...args"}) {
920
+ const frame = __neatPickUserFrame(new Error().stack)
921
+ if (frame) {
922
+ for (let a = 0; a < args.length; a++) {
923
+ const h = args[a]
924
+ if (typeof h === 'function' && !h.__neatHandlerWrapped && h.length <= 4) {
925
+ const inner = h
926
+ const __neatHandler${anyT} = function (${ts ? "...hargs: any[]" : "...hargs"}) {
927
+ const self = this
928
+ return __neatStampHandler(frame, function () { return inner.apply(self, hargs) })
929
+ }
930
+ __neatHandler.__neatHandlerWrapped = true
931
+ args[a] = __neatHandler
932
+ }
933
+ }
934
+ }
935
+ return orig.apply(this, args)
936
+ }
937
+ }
938
+ target.__neatVerbsWrapped = true
939
+ }
940
+ if (mod && mod.Router && mod.Router.prototype) wrapTarget(mod.Router.prototype)
941
+ if (mod && mod.application) wrapTarget(mod.application)
942
+ if (mod && mod.prototype) wrapTarget(mod.prototype)
943
+ } catch (_e) {}
944
+ }
945
+
946
+ function __neatInstallHandlerEntry() {
947
+ __neatHook(['express'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
948
+ __neatHook(['connect'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
949
+ }
950
+
951
+ // Install every off-stack and handler-entry wrap. Called once after sdk.start()
952
+ // \u2014 before user code requires the framework / Prisma modules, so the hooks land
953
+ // on first require. Each wrap is independently guarded.
954
+ function __neatInstallFacades() {
955
+ __neatWrapFetch()
956
+ __neatWrapPrisma()
957
+ __neatInstallHandlerEntry()
748
958
  }`;
749
959
  }
750
- var CALLSITE_PROCESSOR_JS = neatCallsiteProcessorSource(false);
751
- var CALLSITE_PROCESSOR_TS = neatCallsiteProcessorSource(true);
752
- function neatRegisterCallsiteSource(ts) {
960
+ var CALLSITE_PROCESSOR_JS = neatCaptureSource(false);
961
+ var CALLSITE_PROCESSOR_TS = neatCaptureSource(true);
962
+ function neatWireCaptureSource(ts) {
753
963
  const providerT = ts ? ": any" : "";
754
964
  return `try {
755
965
  const provider${providerT} = trace.getTracerProvider()
756
966
  const delegate = provider && typeof provider.getDelegate === 'function' ? provider.getDelegate() : provider
757
- if (delegate && typeof delegate.addSpanProcessor === 'function') {
758
- delegate.addSpanProcessor(new NeatCallSiteSpanProcessor())
759
- }
760
- } catch {
761
- // capture is best-effort; span export is unaffected
967
+ if (!delegate || typeof delegate.addSpanProcessor !== 'function') {
968
+ throw new Error('[neat] could not resolve a TracerProvider to attach the call-site processor; file-first OBSERVED capture would be silent (file-awareness.md \xA74)')
969
+ }
970
+ const __neatProcessor = new NeatCallSiteSpanProcessor()
971
+ delegate.addSpanProcessor(__neatProcessor)
972
+ // Post-init assertion: confirm attachment on providers that expose their
973
+ // processor list. An un-introspectable provider is trusted (we just called
974
+ // its addSpanProcessor); a list that exists and lacks our processor throws.
975
+ const registered = delegate._registeredSpanProcessors
976
+ if (Array.isArray(registered) && registered.indexOf(__neatProcessor) === -1) {
977
+ throw new Error('[neat] call-site processor did not attach to the active TracerProvider (file-awareness.md \xA74)')
978
+ }
979
+ } catch (err) {
980
+ throw err
981
+ }
982
+ try {
983
+ __neatInstallFacades()
984
+ } catch (_e) {
985
+ // Facade install is best-effort: the stack-walk + handler-entry floor still
986
+ // attribute the sync-wrapper majority even if an off-stack wrap fails.
762
987
  }`;
763
988
  }
764
989
  var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
@@ -769,7 +994,7 @@ ${OTEL_OTLP_HEADERS_JS}
769
994
 
770
995
  const { NodeSDK } = require('@opentelemetry/sdk-node')
771
996
  const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
772
- const { trace } = require('@opentelemetry/api')
997
+ const { trace, context } = require('@opentelemetry/api')
773
998
 
774
999
  ${CALLSITE_PROCESSOR_JS}
775
1000
 
@@ -777,7 +1002,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
777
1002
  __INSTRUMENTATION_BLOCK__
778
1003
  const sdk = new NodeSDK({ instrumentations })
779
1004
  sdk.start()
780
- ${neatRegisterCallsiteSource(false)}
1005
+ ${neatWireCaptureSource(false)}
781
1006
  `;
782
1007
  var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
783
1008
  ${OTEL_INIT_STAMP}
@@ -787,7 +1012,7 @@ ${OTEL_OTLP_HEADERS_JS}
787
1012
 
788
1013
  import { NodeSDK } from '@opentelemetry/sdk-node'
789
1014
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
790
- import { trace } from '@opentelemetry/api'
1015
+ import { trace, context } from '@opentelemetry/api'
791
1016
 
792
1017
  ${CALLSITE_PROCESSOR_JS}
793
1018
 
@@ -795,17 +1020,22 @@ const instrumentations = [getNodeAutoInstrumentations()]
795
1020
  __INSTRUMENTATION_BLOCK__
796
1021
  const sdk = new NodeSDK({ instrumentations })
797
1022
  sdk.start()
798
- ${neatRegisterCallsiteSource(false)}
1023
+ ${neatWireCaptureSource(false)}
799
1024
  `;
800
1025
  var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
801
1026
  ${OTEL_INIT_STAMP}
1027
+ // @ts-nocheck \u2014 generated runtime shim. The layered capture mechanism uses
1028
+ // dynamic patterns (facade wraps, Proxy traps, a createRequire bridge) that a
1029
+ // strict user tsconfig would reject; suppressing type-checking here keeps the
1030
+ // generated file from breaking the host project's \`tsc\` gate (#427) without
1031
+ // constraining the runtime logic. The file is regenerated, never hand-edited.
802
1032
  process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
803
1033
  process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
804
1034
  ${OTEL_OTLP_HEADERS_JS}
805
1035
 
806
1036
  import { NodeSDK } from '@opentelemetry/sdk-node'
807
1037
  import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
808
- import { trace } from '@opentelemetry/api'
1038
+ import { trace, context } from '@opentelemetry/api'
809
1039
 
810
1040
  ${CALLSITE_PROCESSOR_TS}
811
1041
 
@@ -813,7 +1043,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
813
1043
  __INSTRUMENTATION_BLOCK__
814
1044
  const sdk = new NodeSDK({ instrumentations })
815
1045
  sdk.start()
816
- ${neatRegisterCallsiteSource(true)}
1046
+ ${neatWireCaptureSource(true)}
817
1047
  `;
818
1048
  function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
819
1049
  const block = registrations.length === 0 ? "" : `