@nathapp/nax 0.73.3 → 0.73.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.
Files changed (2) hide show
  1. package/dist/nax.js +354 -17
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -17283,6 +17283,53 @@ var init_schemas_infra = __esm(() => {
17283
17283
  });
17284
17284
  });
17285
17285
 
17286
+ // src/config/schemas-reporters.ts
17287
+ var HeadersSchema, ReporterEventSchema, WebhookReporterConfigSchema, OtelReporterConfigSchema, ReportersConfigSchema;
17288
+ var init_schemas_reporters = __esm(() => {
17289
+ init_zod();
17290
+ HeadersSchema = exports_external.record(exports_external.string(), exports_external.string()).default({});
17291
+ ReporterEventSchema = exports_external.enum(["onRunStart", "onStoryComplete", "onRunEnd"]);
17292
+ WebhookReporterConfigSchema = exports_external.object({
17293
+ enabled: exports_external.boolean().default(false),
17294
+ url: exports_external.string().url().optional(),
17295
+ headers: HeadersSchema,
17296
+ events: exports_external.array(ReporterEventSchema).optional(),
17297
+ timeoutMs: exports_external.number().int().positive().default(5000)
17298
+ }).default({
17299
+ enabled: false,
17300
+ headers: {},
17301
+ timeoutMs: 5000
17302
+ });
17303
+ OtelReporterConfigSchema = exports_external.object({
17304
+ enabled: exports_external.boolean().default(false),
17305
+ endpoint: exports_external.string().url().optional(),
17306
+ headers: HeadersSchema,
17307
+ serviceName: exports_external.string().default("nax"),
17308
+ timeoutMs: exports_external.number().int().positive().default(5000)
17309
+ }).default({
17310
+ enabled: false,
17311
+ headers: {},
17312
+ serviceName: "nax",
17313
+ timeoutMs: 5000
17314
+ });
17315
+ ReportersConfigSchema = exports_external.object({
17316
+ webhook: WebhookReporterConfigSchema,
17317
+ otel: OtelReporterConfigSchema
17318
+ }).default({
17319
+ webhook: {
17320
+ enabled: false,
17321
+ headers: {},
17322
+ timeoutMs: 5000
17323
+ },
17324
+ otel: {
17325
+ enabled: false,
17326
+ headers: {},
17327
+ serviceName: "nax",
17328
+ timeoutMs: 5000
17329
+ }
17330
+ });
17331
+ });
17332
+
17286
17333
  // src/config/schemas-review.ts
17287
17334
  var SemanticReviewConfigSchema, AdversarialReviewConfigSchema, ReviewConfigSchema;
17288
17335
  var init_schemas_review = __esm(() => {
@@ -17369,6 +17416,7 @@ var init_schemas3 = __esm(() => {
17369
17416
  init_schemas_execution();
17370
17417
  init_schemas_infra();
17371
17418
  init_schemas_model();
17419
+ init_schemas_reporters();
17372
17420
  init_schemas_review();
17373
17421
  init_schemas_infra();
17374
17422
  init_schemas_review();
@@ -17739,6 +17787,7 @@ var init_schemas3 = __esm(() => {
17739
17787
  enabled: exports_external.boolean().default(false),
17740
17788
  draft: exports_external.boolean().default(true)
17741
17789
  }).optional().default({ enabled: false, draft: true }),
17790
+ reporters: ReportersConfigSchema,
17742
17791
  profile: exports_external.string().default("default"),
17743
17792
  profileChain: exports_external.array(exports_external.string()).default([])
17744
17793
  }).refine((data) => data.version === 1, {
@@ -38977,9 +39026,10 @@ var init_full_suite_rectify_op = __esm(() => {
38977
39026
  });
38978
39027
 
38979
39028
  // src/operations/full-suite-rectify.ts
38980
- function makeFullSuiteRectifyStrategy(story, config2, sink) {
38981
- const appliesTo = (finding) => finding.source === "test-runner" && (finding.category === "failed-test" || finding.category === "execution-failed");
39029
+ function makeFullSuiteRectifyStrategy(story, config2, sink, hasTestWriterDrainer) {
39030
+ const claimsFailingTest = (finding) => finding.source === "test-runner" && (finding.category === "failed-test" || finding.category === "execution-failed");
38982
39031
  if (sink) {
39032
+ const appliesTo = (finding) => claimsFailingTest(finding) && !(hasTestWriterDrainer === true && sink.mockHandoffs.length > 0);
38983
39033
  return {
38984
39034
  name: "full-suite-rectify",
38985
39035
  appliesTo,
@@ -39007,7 +39057,7 @@ function makeFullSuiteRectifyStrategy(story, config2, sink) {
39007
39057
  }
39008
39058
  return {
39009
39059
  name: "full-suite-rectify",
39010
- appliesTo,
39060
+ appliesTo: claimsFailingTest,
39011
39061
  fixOp: implementerOp,
39012
39062
  buildInput: (findings) => ({
39013
39063
  story,
@@ -41765,7 +41815,7 @@ var package_default;
41765
41815
  var init_package = __esm(() => {
41766
41816
  package_default = {
41767
41817
  name: "@nathapp/nax",
41768
- version: "0.73.3",
41818
+ version: "0.73.4",
41769
41819
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
41770
41820
  type: "module",
41771
41821
  bin: {
@@ -41866,8 +41916,8 @@ var init_version = __esm(() => {
41866
41916
  NAX_VERSION = package_default.version;
41867
41917
  NAX_COMMIT = (() => {
41868
41918
  try {
41869
- if (/^[0-9a-f]{6,10}$/.test("a98a211e"))
41870
- return "a98a211e";
41919
+ if (/^[0-9a-f]{6,10}$/.test("ec04b570"))
41920
+ return "ec04b570";
41871
41921
  } catch {}
41872
41922
  try {
41873
41923
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -58944,7 +58994,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
58944
58994
  const fullSuiteGatePhasePresent = Boolean(inputs.fullSuiteGate) && (isThreeSession || regressionMode === "per-story");
58945
58995
  const verifyScopedPhasePresent = !isThreeSession && Boolean(inputs.verifyScoped);
58946
58996
  if (fullSuiteGatePhasePresent || verifyScopedPhasePresent) {
58947
- strategies.push(makeFullSuiteRectifyStrategy(story, config2, sink));
58997
+ strategies.push(makeFullSuiteRectifyStrategy(story, config2, sink, isThreeSession));
58948
58998
  }
58949
58999
  if (config2.quality.autofix?.enabled !== false) {
58950
59000
  strategies.push(makeAutofixImplementerStrategy(story, config2, sink, {
@@ -59014,7 +59064,8 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
59014
59064
  promptSeverityFloor: "info"
59015
59065
  }));
59016
59066
  }
59017
- nbStrategies.push(makeFullSuiteRectifyStrategy(story, config2, nbSink));
59067
+ const nbHasTestWriterDrainer = isThreeSession && nbf.scope !== "source";
59068
+ nbStrategies.push(makeFullSuiteRectifyStrategy(story, config2, nbSink, nbHasTestWriterDrainer));
59018
59069
  const nbPostValidate = async (findings, validateCtx) => {
59019
59070
  if (nbSink.testEdits.length === 0 && nbSink.mockHandoffs.length === 0)
59020
59071
  return findings;
@@ -63422,6 +63473,264 @@ var init_curator = __esm(() => {
63422
63473
  };
63423
63474
  });
63424
63475
 
63476
+ // src/plugins/builtin/reporter-shared/interpolate.ts
63477
+ function interpolateHeaders(headers, env2 = process.env) {
63478
+ const resolved = {};
63479
+ const missing = new Set;
63480
+ for (const [key, value] of Object.entries(headers)) {
63481
+ resolved[key] = value.replace(ENV_PLACEHOLDER, (_match, name) => {
63482
+ const v = env2[name];
63483
+ if (v === undefined) {
63484
+ missing.add(name);
63485
+ return "";
63486
+ }
63487
+ return v;
63488
+ });
63489
+ }
63490
+ return { resolved, missing: [...missing] };
63491
+ }
63492
+ var ENV_PLACEHOLDER;
63493
+ var init_interpolate = __esm(() => {
63494
+ ENV_PLACEHOLDER = /\$\{([A-Z0-9_]+)\}/g;
63495
+ });
63496
+
63497
+ // src/plugins/builtin/reporter-shared/post-json.ts
63498
+ async function postJson(url2, body, opts) {
63499
+ const deps = opts.deps ?? _postJsonDeps;
63500
+ const logger = getSafeLogger();
63501
+ try {
63502
+ const res = await deps.fetch(url2, {
63503
+ method: "POST",
63504
+ headers: { "Content-Type": "application/json", ...opts.headers },
63505
+ body: JSON.stringify(body),
63506
+ signal: AbortSignal.timeout(opts.timeoutMs)
63507
+ });
63508
+ if (!res.ok) {
63509
+ logger?.warn(opts.stage, "Telemetry POST returned non-2xx", { url: url2, status: res.status });
63510
+ return false;
63511
+ }
63512
+ return true;
63513
+ } catch (err) {
63514
+ logger?.warn(opts.stage, "Telemetry POST failed", { url: url2, error: errorMessage(err) });
63515
+ return false;
63516
+ }
63517
+ }
63518
+ var _postJsonDeps;
63519
+ var init_post_json = __esm(() => {
63520
+ init_logger2();
63521
+ _postJsonDeps = { fetch: globalThis.fetch };
63522
+ });
63523
+
63524
+ // src/plugins/builtin/reporter-shared/index.ts
63525
+ var init_reporter_shared = __esm(() => {
63526
+ init_interpolate();
63527
+ init_post_json();
63528
+ });
63529
+
63530
+ // src/plugins/builtin/otel-reporter/ids.ts
63531
+ function randomHex(bytes) {
63532
+ const arr = new Uint8Array(bytes);
63533
+ crypto.getRandomValues(arr);
63534
+ let out = "";
63535
+ for (const b of arr)
63536
+ out += b.toString(16).padStart(2, "0");
63537
+ return out;
63538
+ }
63539
+ var newTraceId = () => randomHex(16), newSpanId = () => randomHex(8);
63540
+
63541
+ // src/plugins/builtin/otel-reporter/otlp.ts
63542
+ function attr(key, value) {
63543
+ return typeof value === "number" ? { key, value: { doubleValue: value } } : { key, value: { stringValue: value } };
63544
+ }
63545
+ function msToUnixNano(ms) {
63546
+ return (BigInt(Math.round(ms)) * 1000000n).toString();
63547
+ }
63548
+ function buildTracesPayload(p) {
63549
+ const span = {
63550
+ traceId: p.traceId,
63551
+ spanId: p.spanId,
63552
+ name: "nax.run",
63553
+ kind: 1,
63554
+ startTimeUnixNano: p.startUnixNano,
63555
+ endTimeUnixNano: p.endUnixNano,
63556
+ attributes: [
63557
+ attr("feature", p.feature),
63558
+ attr("runId", p.runId),
63559
+ attr("stories.completed", p.storySummary.completed),
63560
+ attr("stories.failed", p.storySummary.failed),
63561
+ attr("stories.skipped", p.storySummary.skipped),
63562
+ attr("stories.paused", p.storySummary.paused),
63563
+ attr("cost.total", p.totalCost)
63564
+ ],
63565
+ events: p.events,
63566
+ status: { code: p.storySummary.failed > 0 ? 2 : 1 }
63567
+ };
63568
+ return {
63569
+ resourceSpans: [
63570
+ {
63571
+ resource: { attributes: [attr("service.name", p.serviceName)] },
63572
+ scopeSpans: [{ scope: { name: "nax" }, spans: [span] }]
63573
+ }
63574
+ ]
63575
+ };
63576
+ }
63577
+ function buildMetricsPayload(p) {
63578
+ const statusEntries = Object.entries(p.storySummary).filter(([, n]) => n > 0);
63579
+ const storiesSum = {
63580
+ name: "nax.stories.total",
63581
+ sum: {
63582
+ aggregationTemporality: 2,
63583
+ isMonotonic: true,
63584
+ dataPoints: statusEntries.map(([status, count]) => ({
63585
+ asInt: String(count),
63586
+ timeUnixNano: p.timeUnixNano,
63587
+ attributes: [attr("status", status)]
63588
+ }))
63589
+ }
63590
+ };
63591
+ const gauge = (name, value) => ({
63592
+ name,
63593
+ gauge: { dataPoints: [{ asDouble: value, timeUnixNano: p.timeUnixNano }] }
63594
+ });
63595
+ return {
63596
+ resourceMetrics: [
63597
+ {
63598
+ resource: { attributes: [attr("service.name", p.serviceName)] },
63599
+ scopeMetrics: [
63600
+ {
63601
+ scope: { name: "nax" },
63602
+ metrics: [storiesSum, gauge("nax.run.cost", p.totalCost), gauge("nax.run.duration_ms", p.totalDurationMs)]
63603
+ }
63604
+ ]
63605
+ }
63606
+ ]
63607
+ };
63608
+ }
63609
+
63610
+ // src/plugins/builtin/otel-reporter/index.ts
63611
+ function createOtelReporterPlugin(cfg, deps) {
63612
+ const states = new Map;
63613
+ const base = cfg.endpoint?.replace(/\/$/, "");
63614
+ const flush = async (st, endMs, e) => {
63615
+ if (!base)
63616
+ return;
63617
+ const { resolved, missing } = interpolateHeaders(cfg.headers);
63618
+ if (missing.length > 0) {
63619
+ getSafeLogger()?.warn(STAGE, "Skipping OTLP export \u2014 unresolved env vars", { missing });
63620
+ return;
63621
+ }
63622
+ const startUnixNano = msToUnixNano(st.startMs);
63623
+ const endUnixNano = msToUnixNano(endMs);
63624
+ const traces = buildTracesPayload({
63625
+ serviceName: cfg.serviceName,
63626
+ traceId: st.traceId,
63627
+ spanId: st.spanId,
63628
+ startUnixNano,
63629
+ endUnixNano,
63630
+ feature: st.feature,
63631
+ runId: e.runId,
63632
+ storySummary: e.storySummary,
63633
+ totalCost: e.totalCost,
63634
+ events: st.events
63635
+ });
63636
+ const metrics = buildMetricsPayload({
63637
+ serviceName: cfg.serviceName,
63638
+ runId: e.runId,
63639
+ timeUnixNano: endUnixNano,
63640
+ storySummary: e.storySummary,
63641
+ totalCost: e.totalCost,
63642
+ totalDurationMs: e.totalDurationMs
63643
+ });
63644
+ const opts = { headers: resolved, timeoutMs: cfg.timeoutMs, stage: STAGE, deps };
63645
+ await postJson(`${base}/v1/traces`, traces, opts);
63646
+ await postJson(`${base}/v1/metrics`, metrics, opts);
63647
+ };
63648
+ const reporter = {
63649
+ name: STAGE,
63650
+ async onRunStart(event) {
63651
+ states.set(event.runId, {
63652
+ traceId: newTraceId(),
63653
+ spanId: newSpanId(),
63654
+ startMs: Date.parse(event.startTime),
63655
+ feature: event.feature,
63656
+ events: []
63657
+ });
63658
+ },
63659
+ async onStoryComplete(event) {
63660
+ const st = states.get(event.runId);
63661
+ if (!st)
63662
+ return;
63663
+ st.events.push({
63664
+ timeUnixNano: msToUnixNano(st.startMs + event.runElapsedMs),
63665
+ name: "story.complete",
63666
+ attributes: [
63667
+ attr("storyId", event.storyId),
63668
+ attr("status", event.status),
63669
+ attr("cost", event.cost),
63670
+ attr("tier", event.tier),
63671
+ attr("testStrategy", event.testStrategy)
63672
+ ]
63673
+ });
63674
+ },
63675
+ async onRunEnd(event) {
63676
+ const existing = states.get(event.runId);
63677
+ const startMs = existing?.startMs ?? Date.now() - event.totalDurationMs;
63678
+ const st = existing ?? {
63679
+ traceId: newTraceId(),
63680
+ spanId: newSpanId(),
63681
+ startMs,
63682
+ feature: "",
63683
+ events: []
63684
+ };
63685
+ states.delete(event.runId);
63686
+ await flush(st, startMs + event.totalDurationMs, event);
63687
+ }
63688
+ };
63689
+ return {
63690
+ name: STAGE,
63691
+ version: "1.0.0",
63692
+ provides: ["reporter"],
63693
+ extensions: { reporter }
63694
+ };
63695
+ }
63696
+ var STAGE = "otel-reporter";
63697
+ var init_otel_reporter = __esm(() => {
63698
+ init_logger2();
63699
+ init_reporter_shared();
63700
+ });
63701
+
63702
+ // src/plugins/builtin/webhook-reporter/index.ts
63703
+ function createWebhookReporterPlugin(cfg, deps) {
63704
+ const enabledEvent = (event) => cfg.events === undefined || cfg.events.includes(event);
63705
+ const emit = async (type, data) => {
63706
+ if (!cfg.url || !enabledEvent(type))
63707
+ return;
63708
+ const { resolved, missing } = interpolateHeaders(cfg.headers);
63709
+ if (missing.length > 0) {
63710
+ getSafeLogger()?.warn(STAGE2, "Skipping webhook \u2014 unresolved env vars", { missing });
63711
+ return;
63712
+ }
63713
+ await postJson(cfg.url, { type, emittedAt: new Date().toISOString(), data }, { headers: resolved, timeoutMs: cfg.timeoutMs, stage: STAGE2, deps });
63714
+ };
63715
+ const reporter = {
63716
+ name: STAGE2,
63717
+ onRunStart: (event) => emit("onRunStart", event),
63718
+ onStoryComplete: (event) => emit("onStoryComplete", event),
63719
+ onRunEnd: (event) => emit("onRunEnd", event)
63720
+ };
63721
+ return {
63722
+ name: STAGE2,
63723
+ version: "1.0.0",
63724
+ provides: ["reporter"],
63725
+ extensions: { reporter }
63726
+ };
63727
+ }
63728
+ var STAGE2 = "webhook-reporter";
63729
+ var init_webhook_reporter = __esm(() => {
63730
+ init_logger2();
63731
+ init_reporter_shared();
63732
+ });
63733
+
63425
63734
  // src/plugins/plugin-logger.ts
63426
63735
  function createPluginLogger(pluginName) {
63427
63736
  const stage = `plugin:${pluginName}`;
@@ -63777,7 +64086,7 @@ function extractPluginName(pluginPath) {
63777
64086
  }
63778
64087
  return basename12.replace(/\.(ts|js|mjs)$/, "");
63779
64088
  }
63780
- async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins, isTestFileFn) {
64089
+ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins, isTestFileFn, reporters) {
63781
64090
  const loadedPlugins = [];
63782
64091
  const builtinPostRunActions = [];
63783
64092
  const effectiveProjectRoot = projectRoot || projectDir;
@@ -63821,6 +64130,32 @@ async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, di
63821
64130
  } else {
63822
64131
  logger?.info("plugins", `Skipping disabled plugin: '${autoRoutePlugin.name}' (built-in)`);
63823
64132
  }
64133
+ const reporterFactories = reporters ? [
64134
+ {
64135
+ name: "webhook-reporter",
64136
+ enabled: reporters.webhook.enabled,
64137
+ make: () => createWebhookReporterPlugin(reporters.webhook)
64138
+ },
64139
+ {
64140
+ name: "otel-reporter",
64141
+ enabled: reporters.otel.enabled,
64142
+ make: () => createOtelReporterPlugin(reporters.otel)
64143
+ }
64144
+ ] : [];
64145
+ for (const { name, enabled: reporterEnabled, make } of reporterFactories) {
64146
+ if (!reporterEnabled)
64147
+ continue;
64148
+ if (disabledSet.has(name)) {
64149
+ logger?.info("plugins", `Skipping disabled plugin: '${name}' (built-in)`);
64150
+ continue;
64151
+ }
64152
+ const plugin = make();
64153
+ if (plugin.setup) {
64154
+ await plugin.setup({}, createPluginLogger(plugin.name));
64155
+ }
64156
+ loadedPlugins.push({ plugin, source: { type: "builtin", path: plugin.name } });
64157
+ pluginNames.add(plugin.name);
64158
+ }
63824
64159
  const globalPlugins = await discoverPlugins(globalDir, isTestFileFn);
63825
64160
  for (const plugin of globalPlugins) {
63826
64161
  const pluginName = extractPluginName(plugin.path);
@@ -63985,6 +64320,8 @@ var init_loader4 = __esm(() => {
63985
64320
  init_auto_pr();
63986
64321
  init_auto_route();
63987
64322
  init_curator();
64323
+ init_otel_reporter();
64324
+ init_webhook_reporter();
63988
64325
  init_plugin_logger();
63989
64326
  init_registry5();
63990
64327
  init_validator();
@@ -70674,7 +71011,7 @@ async function setupRun(options) {
70674
71011
  const configPlugins = config2.plugins || [];
70675
71012
  const resolvedPatterns = await resolveTestFilePatterns(config2, workdir);
70676
71013
  const isTestFileFn = (filename) => resolvedPatterns.regex.some((re) => re.test(filename));
70677
- const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins, isTestFileFn);
71014
+ const pluginRegistry = await loadPlugins(globalPluginsDir, projectPluginsDir, configPlugins, workdir, config2.disabledPlugins, isTestFileFn, config2.reporters);
70678
71015
  clearCache();
70679
71016
  logger?.info("plugins", `Loaded ${pluginRegistry.plugins.length} plugins`, {
70680
71017
  plugins: pluginRegistry.plugins.map((p) => ({ name: p.name, version: p.version, provides: p.provides }))
@@ -86662,13 +86999,13 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho
86662
86999
  return false;
86663
87000
  }
86664
87001
  function utils_getInObject(object2, path31) {
86665
- return path31.reduce(function(reduced, attr) {
87002
+ return path31.reduce(function(reduced, attr2) {
86666
87003
  if (reduced) {
86667
- if (utils_hasOwnProperty.call(reduced, attr)) {
86668
- return reduced[attr];
87004
+ if (utils_hasOwnProperty.call(reduced, attr2)) {
87005
+ return reduced[attr2];
86669
87006
  }
86670
87007
  if (typeof reduced[Symbol.iterator] === "function") {
86671
- return Array.from(reduced)[attr];
87008
+ return Array.from(reduced)[attr2];
86672
87009
  }
86673
87010
  }
86674
87011
  return null;
@@ -97663,9 +98000,9 @@ The error thrown in the component is:
97663
98000
  getEnvironmentNames
97664
98001
  }, internalMcpFunctions);
97665
98002
  }
97666
- function decorate(object2, attr, fn) {
97667
- var old = object2[attr];
97668
- object2[attr] = function(instance2) {
98003
+ function decorate(object2, attr2, fn) {
98004
+ var old = object2[attr2];
98005
+ object2[attr2] = function(instance2) {
97669
98006
  return fn.call(this, old, arguments);
97670
98007
  };
97671
98008
  return old;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.73.3",
3
+ "version": "0.73.4",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {