nexus-agents 2.125.41 → 2.127.0

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.
@@ -47,7 +47,7 @@ import {
47
47
  } from "./chunk-PI4RZNLE.js";
48
48
  import {
49
49
  withPrerequisite
50
- } from "./chunk-GWUMFIQP.js";
50
+ } from "./chunk-M5QEWTVL.js";
51
51
  import {
52
52
  NOOP_NOTIFIER,
53
53
  RateLimiter,
@@ -122,7 +122,7 @@ import {
122
122
  DEFAULT_TASK_TTL_MS,
123
123
  DEFAULT_TOOL_RATE_LIMITS,
124
124
  clampTaskTtl
125
- } from "./chunk-323AIMTE.js";
125
+ } from "./chunk-QPVEGYBW.js";
126
126
  import {
127
127
  resolveInsideRoot
128
128
  } from "./chunk-NUBSJGQZ.js";
@@ -50477,4 +50477,4 @@ export {
50477
50477
  shutdownFeedbackSubscriber,
50478
50478
  createEventBusBridge
50479
50479
  };
50480
- //# sourceMappingURL=chunk-6KQI4SJT.js.map
50480
+ //# sourceMappingURL=chunk-JE6K7PD6.js.map
@@ -18,14 +18,15 @@ import {
18
18
  getTimeProvider
19
19
  } from "./chunk-QT3VRHNP.js";
20
20
  import {
21
- getNexusDataDir
21
+ getNexusDataDir,
22
+ nexusDataPath
22
23
  } from "./chunk-ZGLIHPGJ.js";
23
24
 
24
25
  // src/mcp/tools/improvement-review.ts
25
26
  import { execFile as execFile2 } from "child_process";
26
27
  import { readFile } from "fs/promises";
27
28
  import { promisify as promisify2 } from "util";
28
- import { z } from "zod";
29
+ import { z as z2 } from "zod";
29
30
 
30
31
  // src/mcp/middleware/tool-prerequisites.ts
31
32
  import { execFile } from "child_process";
@@ -741,6 +742,163 @@ function improvementSignalsToTasks(signals, existingTaskIds) {
741
742
  return tasks;
742
743
  }
743
744
 
745
+ // src/mcp/tools/improvement-remediation-shadow.ts
746
+ import { z } from "zod";
747
+
748
+ // src/config/jsonl-store.ts
749
+ import { appendFileSync, existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
750
+ import { dirname as dirname2 } from "path";
751
+ var DIR_MODE = 448;
752
+ var JsonlStore = class {
753
+ filePath;
754
+ schema;
755
+ maxRecords;
756
+ logger;
757
+ records = [];
758
+ constructor(config) {
759
+ this.filePath = config.filePath;
760
+ this.schema = config.schema;
761
+ this.maxRecords = Math.max(1, config.maxRecords);
762
+ this.logger = config.logger ?? createLogger({ component: config.component ?? "JsonlStore" });
763
+ this.ensureDir();
764
+ this.hydrate();
765
+ }
766
+ /** Append one record durably. Validates at the boundary; never throws. */
767
+ append(record) {
768
+ const result = this.schema.safeParse(record);
769
+ if (!result.success) {
770
+ this.logger.warn("Refusing to persist invalid JSONL record", {
771
+ path: this.filePath,
772
+ issues: result.error.issues.map((i) => i.message).join("; ")
773
+ });
774
+ return;
775
+ }
776
+ this.records.push(result.data);
777
+ if (this.records.length > this.maxRecords) {
778
+ this.records.splice(0, this.records.length - this.maxRecords);
779
+ this.rewriteFile();
780
+ return;
781
+ }
782
+ this.persistLine(result.data);
783
+ }
784
+ /** All retained records, oldest first. */
785
+ all() {
786
+ return this.records;
787
+ }
788
+ /** Number of retained records. */
789
+ count() {
790
+ return this.records.length;
791
+ }
792
+ // ==========================================================================
793
+ // Private
794
+ // ==========================================================================
795
+ ensureDir() {
796
+ try {
797
+ mkdirSync(dirname2(this.filePath), { recursive: true, mode: DIR_MODE });
798
+ } catch (error) {
799
+ this.logger.warn("Failed to create JSONL store directory", {
800
+ error: getErrorMessage(error),
801
+ path: this.filePath
802
+ });
803
+ }
804
+ }
805
+ hydrate() {
806
+ if (!existsSync2(this.filePath)) {
807
+ this.logger.debug("No JSONL file found, starting fresh", { path: this.filePath });
808
+ return;
809
+ }
810
+ let loaded = 0;
811
+ let skipped = 0;
812
+ try {
813
+ const content = readFileSync2(this.filePath, "utf-8");
814
+ const lines = content.split("\n").filter((line) => line.trim().length > 0);
815
+ for (const line of lines) {
816
+ try {
817
+ const parsed = JSON.parse(line);
818
+ const result = this.schema.safeParse(parsed);
819
+ if (result.success) {
820
+ this.records.push(result.data);
821
+ loaded++;
822
+ } else {
823
+ skipped++;
824
+ }
825
+ } catch {
826
+ skipped++;
827
+ }
828
+ }
829
+ } catch (error) {
830
+ this.logger.warn("Failed to hydrate JSONL store from disk", {
831
+ error: getErrorMessage(error),
832
+ path: this.filePath
833
+ });
834
+ return;
835
+ }
836
+ if (this.records.length > this.maxRecords) {
837
+ this.records.splice(0, this.records.length - this.maxRecords);
838
+ this.rewriteFile();
839
+ }
840
+ this.logger.debug("Hydrated JSONL store from disk", {
841
+ loaded,
842
+ skipped,
843
+ retained: this.records.length,
844
+ path: this.filePath
845
+ });
846
+ }
847
+ persistLine(record) {
848
+ try {
849
+ appendFileSync(this.filePath, JSON.stringify(record) + "\n", "utf-8");
850
+ } catch (error) {
851
+ this.logger.warn("Failed to append JSONL record to disk", {
852
+ error: getErrorMessage(error),
853
+ path: this.filePath
854
+ });
855
+ }
856
+ }
857
+ rewriteFile() {
858
+ try {
859
+ const content = this.records.map((r) => JSON.stringify(r)).join("\n") + "\n";
860
+ writeFileSync(this.filePath, content, "utf-8");
861
+ } catch (error) {
862
+ this.logger.warn("Failed to rewrite JSONL store file", {
863
+ error: getErrorMessage(error),
864
+ path: this.filePath
865
+ });
866
+ }
867
+ }
868
+ };
869
+
870
+ // src/mcp/tools/diff-secret-scan.ts
871
+ var SECRET_PATTERNS = [
872
+ { name: "private-key-block", re: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/ },
873
+ { name: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/ },
874
+ { name: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/ },
875
+ { name: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
876
+ { name: "google-api-key", re: /\bAIza[0-9A-Za-z_-]{35}\b/ },
877
+ { name: "openai-key", re: /\bsk-[A-Za-z0-9]{32,}\b/ },
878
+ { name: "anthropic-key", re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/ },
879
+ { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/ },
880
+ // Generic `secret/token/password/api_key = "long-value"` assignments.
881
+ {
882
+ name: "generic-credential-assignment",
883
+ re: /(?:api[_-]?key|secret|token|password|passwd|credential)\s*[:=]\s*["'][A-Za-z0-9/+_-]{16,}["']/i
884
+ }
885
+ ];
886
+ function scanForSecrets(text) {
887
+ const findings = [];
888
+ const lines = text.split("\n");
889
+ for (let i = 0; i < lines.length; i++) {
890
+ const line = lines[i] ?? "";
891
+ for (const { name, re } of SECRET_PATTERNS) {
892
+ if (re.test(line)) findings.push({ pattern: name, line: i + 1 });
893
+ }
894
+ }
895
+ return { clean: findings.length === 0, findings };
896
+ }
897
+ function describeSecretFindings(result) {
898
+ if (result.clean) return "no secrets detected";
899
+ return result.findings.map((f) => `${f.pattern}@L${String(f.line)}`).join(", ");
900
+ }
901
+
744
902
  // src/mcp/tools/improvement-remediation-shadow.ts
745
903
  function isSecuritySignal(signal) {
746
904
  if (signal.category === "security") return true;
@@ -787,6 +945,180 @@ function recordRemediationShadow(signals, sink = getRemediationShadowSink()) {
787
945
  for (const record of records) sink.record(record);
788
946
  return records;
789
947
  }
948
+ var SoakVoteOutcomeSchema = z.object({
949
+ approved: z.boolean(),
950
+ /** Approval percentage at vote time, 0–100 (the consensus tally). */
951
+ approvalPercentage: z.number()
952
+ });
953
+ var RemediationSoakRecordSchema = z.object({
954
+ /** ISO-8601 capture time. */
955
+ timestamp: z.string(),
956
+ /** The improvement signal's stable key. */
957
+ signalKey: z.string(),
958
+ /** The signal's category. */
959
+ category: z.string(),
960
+ /** The classified remediation priority (p0–p4). */
961
+ priority: z.string(),
962
+ /** The signal's declared severity. */
963
+ severity: z.string(),
964
+ /** Vote outcome (approved/rejected + tally), undefined if the signal never reached a vote. */
965
+ voteOutcome: SoakVoteOutcomeSchema.optional(),
966
+ /** Number of plan steps research produced (0 if research/plan failed). */
967
+ planStepCount: z.number(),
968
+ /** p0 dry-run result detail (scrubbed); undefined for non-p0 or when no dry-run ran. */
969
+ dryRunResult: z.string().optional(),
970
+ /** Human-readable verdict reason (scrubbed). */
971
+ reason: z.string()
972
+ });
973
+ var SOAK_MAX_RECORDS = 1e4;
974
+ function getRemediationSoakFile() {
975
+ return nexusDataPath("learning", "remediation-soak.jsonl");
976
+ }
977
+ function scrubSoakRecord(record) {
978
+ const scrub = (text) => {
979
+ const result = scanForSecrets(text);
980
+ if (result.clean) return text;
981
+ return `[redacted: ${describeSecretFindings(result)}]`;
982
+ };
983
+ const scrubbed = {
984
+ ...record,
985
+ reason: scrub(record.reason),
986
+ ...record.dryRunResult !== void 0 ? { dryRunResult: scrub(record.dryRunResult) } : {}
987
+ };
988
+ return scrubbed;
989
+ }
990
+ function createRemediationSoakSink(filePath = getRemediationSoakFile(), maxRecords = SOAK_MAX_RECORDS) {
991
+ const store = new JsonlStore({
992
+ filePath,
993
+ schema: RemediationSoakRecordSchema,
994
+ maxRecords,
995
+ component: "RemediationSoakSink"
996
+ });
997
+ return {
998
+ record(record) {
999
+ store.append(scrubSoakRecord(record));
1000
+ },
1001
+ getRecords() {
1002
+ return store.all();
1003
+ }
1004
+ };
1005
+ }
1006
+ var soakSingleton;
1007
+ function getRemediationSoakSink() {
1008
+ soakSingleton ??= createRemediationSoakSink();
1009
+ return soakSingleton;
1010
+ }
1011
+ function tallySoakRecord(t, r) {
1012
+ t.byCategory[r.category] = (t.byCategory[r.category] ?? 0) + 1;
1013
+ t.byPriority[r.priority] = (t.byPriority[r.priority] ?? 0) + 1;
1014
+ if (r.voteOutcome !== void 0) {
1015
+ t.voted++;
1016
+ if (r.voteOutcome.approved) t.approved++;
1017
+ }
1018
+ if (r.dryRunResult !== void 0) t.dryRunsCaptured++;
1019
+ }
1020
+ function summarizeRemediationSoak(records) {
1021
+ const t = {
1022
+ byCategory: {},
1023
+ byPriority: {},
1024
+ voted: 0,
1025
+ approved: 0,
1026
+ dryRunsCaptured: 0
1027
+ };
1028
+ for (const r of records) tallySoakRecord(t, r);
1029
+ const first = records[0]?.timestamp;
1030
+ const last = records[records.length - 1]?.timestamp;
1031
+ return {
1032
+ total: records.length,
1033
+ voted: t.voted,
1034
+ approved: t.approved,
1035
+ rejected: t.voted - t.approved,
1036
+ approvalRate: t.voted === 0 ? 0 : t.approved / t.voted,
1037
+ dryRunsCaptured: t.dryRunsCaptured,
1038
+ byCategory: t.byCategory,
1039
+ byPriority: t.byPriority,
1040
+ ...first !== void 0 ? { firstTimestamp: first } : {},
1041
+ ...last !== void 0 ? { lastTimestamp: last } : {}
1042
+ };
1043
+ }
1044
+ function readRemediationSoakSummary(sink = getRemediationSoakSink()) {
1045
+ return summarizeRemediationSoak(sink.getRecords());
1046
+ }
1047
+ function parseVoteDetail(detail) {
1048
+ const approved = /\bapproved\b/.test(detail);
1049
+ const rejected = /\brejected\b/.test(detail);
1050
+ if (!approved && !rejected) return void 0;
1051
+ const pct = /(\d+(?:\.\d+)?)%/.exec(detail);
1052
+ return {
1053
+ approved,
1054
+ approvalPercentage: pct?.[1] !== void 0 ? Number(pct[1]) : approved ? 100 : 0
1055
+ };
1056
+ }
1057
+ var SOAK_TERMINAL_REASON_STEPS = /* @__PURE__ */ new Set([
1058
+ "skip",
1059
+ "research-failed",
1060
+ "protected-path"
1061
+ ]);
1062
+ function applySoakEvent(d, event) {
1063
+ if (event.step === "plan") {
1064
+ const n = /(\d+)\s*steps?/.exec(event.detail);
1065
+ d.planStepCount = n?.[1] !== void 0 ? Number(n[1]) : 0;
1066
+ if (d.reason === "") d.reason = "plan produced";
1067
+ return;
1068
+ }
1069
+ if (event.step === "vote") {
1070
+ const outcome = parseVoteDetail(event.detail);
1071
+ if (outcome !== void 0) d.voteOutcome = outcome;
1072
+ d.reason = event.detail;
1073
+ return;
1074
+ }
1075
+ if (event.step === "dry-run") {
1076
+ d.dryRunResult = event.detail;
1077
+ return;
1078
+ }
1079
+ if (SOAK_TERMINAL_REASON_STEPS.has(event.step)) d.reason = event.detail;
1080
+ }
1081
+ function draftToSoakRecord(d, meta, timestamp) {
1082
+ return {
1083
+ timestamp,
1084
+ signalKey: d.signalKey,
1085
+ category: meta?.category ?? "unknown",
1086
+ priority: meta?.priority ?? "unknown",
1087
+ severity: meta?.severity ?? "unknown",
1088
+ ...d.voteOutcome !== void 0 ? { voteOutcome: d.voteOutcome } : {},
1089
+ planStepCount: d.planStepCount,
1090
+ ...d.dryRunResult !== void 0 ? { dryRunResult: d.dryRunResult } : {},
1091
+ reason: d.reason === "" ? "no verdict recorded" : d.reason
1092
+ };
1093
+ }
1094
+ function createRemediationSoakCollector(metaFor, sink = getRemediationSoakSink()) {
1095
+ const drafts = /* @__PURE__ */ new Map();
1096
+ const draftFor = (signalKey) => {
1097
+ let d = drafts.get(signalKey);
1098
+ if (d === void 0) {
1099
+ d = { signalKey, planStepCount: 0, reason: "" };
1100
+ drafts.set(signalKey, d);
1101
+ }
1102
+ return d;
1103
+ };
1104
+ return {
1105
+ observe(event) {
1106
+ if (event.signalKey === void 0) return;
1107
+ applySoakEvent(draftFor(event.signalKey), event);
1108
+ },
1109
+ flush() {
1110
+ const ts = new Date(getTimeProvider().now()).toISOString();
1111
+ const out = [];
1112
+ for (const d of drafts.values()) {
1113
+ const record = draftToSoakRecord(d, metaFor(d.signalKey), ts);
1114
+ sink.record(record);
1115
+ out.push(record);
1116
+ }
1117
+ drafts.clear();
1118
+ return out;
1119
+ }
1120
+ };
1121
+ }
790
1122
 
791
1123
  // src/mcp/tools/remediation-priority.ts
792
1124
  function priorityLabel(priority) {
@@ -816,14 +1148,14 @@ function classifySignalPriority(signal) {
816
1148
 
817
1149
  // src/mcp/tools/improvement-review.ts
818
1150
  var execFileAsync2 = promisify2(execFile2);
819
- var ImprovementReviewInputSchema = z.object({
820
- lookbackDays: z.number().int().min(1).max(90).optional().default(7).describe("Lookback window for outcome data, in days. Default 7."),
821
- fileIssues: z.boolean().optional().default(false).describe(
1151
+ var ImprovementReviewInputSchema = z2.object({
1152
+ lookbackDays: z2.number().int().min(1).max(90).optional().default(7).describe("Lookback window for outcome data, in days. Default 7."),
1153
+ fileIssues: z2.boolean().optional().default(false).describe(
822
1154
  "When true, file candidate issues via `gh issue create` for crossed thresholds (rate-limited to 5 per run, deduped against open issues). When false (default), return signals only."
823
1155
  ),
824
- minSampleSize: z.number().int().min(1).max(1e3).optional().default(5).describe("Minimum sample size before a CLI/category signal can fire."),
825
- fitnessFloor: z.number().int().min(0).max(100).optional().default(90).describe("Fitness score below this threshold triggers a tech-debt signal."),
826
- selfEvalReportPath: z.string().optional().describe(
1156
+ minSampleSize: z2.number().int().min(1).max(1e3).optional().default(5).describe("Minimum sample size before a CLI/category signal can fire."),
1157
+ fitnessFloor: z2.number().int().min(0).max(100).optional().default(90).describe("Fitness score below this threshold triggers a tech-debt signal."),
1158
+ selfEvalReportPath: z2.string().optional().describe(
827
1159
  "Optional path to a self-eval JSON report (from `self-eval --json`). When set, high-confidence unanimous deprecate/refactor findings are surfaced as tech-debt signals through the same deduped/rate-limited issue path (#3224). Unreadable/malformed reports are skipped (no signal). Absent \u2192 no self-eval signals."
828
1160
  )
829
1161
  });
@@ -1021,14 +1353,14 @@ function detectFitnessSignals(audit, fitnessFloor) {
1021
1353
  return signals;
1022
1354
  }
1023
1355
  var SELF_EVAL_CONFIDENCE_FLOOR = 0.8;
1024
- var SelfEvalReportSchema = z.object({
1025
- results: z.array(
1026
- z.object({
1027
- component: z.string(),
1028
- finalRecommendation: z.string(),
1029
- confidence: z.number(),
1030
- dissent: z.array(z.unknown()).optional().default([]),
1031
- evidenceQuality: z.number().optional()
1356
+ var SelfEvalReportSchema = z2.object({
1357
+ results: z2.array(
1358
+ z2.object({
1359
+ component: z2.string(),
1360
+ finalRecommendation: z2.string(),
1361
+ confidence: z2.number(),
1362
+ dissent: z2.array(z2.unknown()).optional().default([]),
1363
+ evidenceQuality: z2.number().optional()
1032
1364
  })
1033
1365
  )
1034
1366
  });
@@ -1243,13 +1575,13 @@ async function reviewHandler(args, ctx) {
1243
1575
  }
1244
1576
  var description = "Periodic threshold-gated observability-driven improvement loop. Reads OutcomeStore + fitness audit, surfaces patterns crossing documented thresholds as candidate findings. When fileIssues=true, files candidate GitHub issues via `gh issue create` (rate-limited to 5 per run, deduped against open issues). Never auto-merges. Replaces the deleted self-development engine (#2402).";
1245
1577
  var TOOL_INPUT_SCHEMA = {
1246
- lookbackDays: z.number().int().min(1).max(90).optional().describe("Lookback window for outcome data, in days. Default 7."),
1247
- fileIssues: z.boolean().optional().describe(
1578
+ lookbackDays: z2.number().int().min(1).max(90).optional().describe("Lookback window for outcome data, in days. Default 7."),
1579
+ fileIssues: z2.boolean().optional().describe(
1248
1580
  "When true, file candidate issues for crossed thresholds (default false \u2014 return signals only)"
1249
1581
  ),
1250
- minSampleSize: z.number().int().min(1).max(1e3).optional().describe("Minimum sample size before a CLI/category signal fires (default 5)."),
1251
- fitnessFloor: z.number().int().min(0).max(100).optional().describe("Fitness score below this threshold triggers a tech-debt signal (default 90)."),
1252
- selfEvalReportPath: z.string().optional().describe(
1582
+ minSampleSize: z2.number().int().min(1).max(1e3).optional().describe("Minimum sample size before a CLI/category signal fires (default 5)."),
1583
+ fitnessFloor: z2.number().int().min(0).max(100).optional().describe("Fitness score below this threshold triggers a tech-debt signal (default 90)."),
1584
+ selfEvalReportPath: z2.string().optional().describe(
1253
1585
  "Optional path to a self-eval JSON report. High-confidence unanimous deprecate/refactor findings surface as tech-debt signals (#3224)."
1254
1586
  )
1255
1587
  };
@@ -1282,6 +1614,12 @@ export {
1282
1614
  withPrerequisite,
1283
1615
  createFitnessScoreCalculator,
1284
1616
  calculateFitnessScore,
1617
+ JsonlStore,
1618
+ scanForSecrets,
1619
+ describeSecretFindings,
1620
+ getRemediationSoakSink,
1621
+ readRemediationSoakSummary,
1622
+ createRemediationSoakCollector,
1285
1623
  consensusFor,
1286
1624
  classifySignalPriority,
1287
1625
  ImprovementReviewInputSchema,
@@ -1296,4 +1634,4 @@ export {
1296
1634
  runImprovementReview,
1297
1635
  registerImprovementReviewTool
1298
1636
  };
1299
- //# sourceMappingURL=chunk-GWUMFIQP.js.map
1637
+ //# sourceMappingURL=chunk-M5QEWTVL.js.map