nexus-agents 2.125.24 → 2.125.25

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.
@@ -40,7 +40,7 @@ import {
40
40
  } from "./chunk-CH7QIDHQ.js";
41
41
 
42
42
  // src/version.ts
43
- var VERSION = true ? "2.125.24" : "dev";
43
+ var VERSION = true ? "2.125.25" : "dev";
44
44
 
45
45
  // src/config/schemas-core.ts
46
46
  import { z } from "zod";
@@ -2099,7 +2099,7 @@ async function runDoctorFix(result) {
2099
2099
  writeLine2("\u2500".repeat(40));
2100
2100
  let fixCount = 0;
2101
2101
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
2102
- const { runSetup } = await import("./setup-command-63KWDRWL.js");
2102
+ const { runSetup } = await import("./setup-command-PRTMR4DK.js");
2103
2103
  const setupResult = runSetup({
2104
2104
  skipMcp: true,
2105
2105
  skipRules: true,
@@ -2211,4 +2211,4 @@ export {
2211
2211
  startStdioServer,
2212
2212
  closeServer
2213
2213
  };
2214
- //# sourceMappingURL=chunk-647PSXJ5.js.map
2214
+ //# sourceMappingURL=chunk-B3TJNOKQ.js.map
@@ -123,7 +123,7 @@ import {
123
123
  DEFAULT_TASK_TTL_MS,
124
124
  DEFAULT_TOOL_RATE_LIMITS,
125
125
  clampTaskTtl
126
- } from "./chunk-647PSXJ5.js";
126
+ } from "./chunk-B3TJNOKQ.js";
127
127
  import {
128
128
  resolveInsideRoot
129
129
  } from "./chunk-NUBSJGQZ.js";
@@ -49800,4 +49800,4 @@ export {
49800
49800
  shutdownFeedbackSubscriber,
49801
49801
  createEventBusBridge
49802
49802
  };
49803
- //# sourceMappingURL=chunk-SDA5GEWO.js.map
49803
+ //# sourceMappingURL=chunk-F6VJEXXJ.js.map
@@ -8,7 +8,7 @@ import {
8
8
  checkSqlite,
9
9
  defaultConfig,
10
10
  initDataDirectories
11
- } from "./chunk-647PSXJ5.js";
11
+ } from "./chunk-B3TJNOKQ.js";
12
12
  import {
13
13
  probeAllClis
14
14
  } from "./chunk-QN3353M2.js";
@@ -2000,4 +2000,4 @@ export {
2000
2000
  setupCommand,
2001
2001
  setupCommandAsync
2002
2002
  };
2003
- //# sourceMappingURL=chunk-QIEXH2EA.js.map
2003
+ //# sourceMappingURL=chunk-YQARWBVL.js.map
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  import {
25
25
  setupCommandAsync,
26
26
  verifyCommand
27
- } from "./chunk-QIEXH2EA.js";
27
+ } from "./chunk-YQARWBVL.js";
28
28
  import "./chunk-MMWNGT2K.js";
29
29
  import {
30
30
  AuthHandler,
@@ -144,7 +144,7 @@ import {
144
144
  validateCommand,
145
145
  validateWorkflow,
146
146
  wrapInMarkdownFence
147
- } from "./chunk-SDA5GEWO.js";
147
+ } from "./chunk-F6VJEXXJ.js";
148
148
  import "./chunk-3ACDP4E6.js";
149
149
  import {
150
150
  CATEGORY_DISPLAY_NAMES,
@@ -246,7 +246,7 @@ import {
246
246
  loadConfig,
247
247
  runDoctor,
248
248
  validateNexusEnv
249
- } from "./chunk-647PSXJ5.js";
249
+ } from "./chunk-B3TJNOKQ.js";
250
250
  import "./chunk-NUBSJGQZ.js";
251
251
  import {
252
252
  capitalize,
@@ -12743,8 +12743,8 @@ async function exportSessionMetrics(storage, sessionId, exportPath) {
12743
12743
  const session = sessionResult.value;
12744
12744
  const metrics = buildMetricsObject(session, sessionId);
12745
12745
  if (exportPath !== void 0) {
12746
- const { writeFile: writeFile6 } = await import("fs/promises");
12747
- await writeFile6(exportPath, JSON.stringify(metrics, null, 2));
12746
+ const { writeFile: writeFile7 } = await import("fs/promises");
12747
+ await writeFile7(exportPath, JSON.stringify(metrics, null, 2));
12748
12748
  logger14.info("Metrics exported", { path: exportPath });
12749
12749
  } else {
12750
12750
  logger14.debug("Session metrics", summarizeMetricsForDebug(metrics));
@@ -23036,6 +23036,235 @@ function stripRefsPrefix(ref) {
23036
23036
  return ref.replace(/^refs\//, "");
23037
23037
  }
23038
23038
 
23039
+ // src/mcp/tools/remediation-proposal-pr.ts
23040
+ import { execFile as execFile4 } from "child_process";
23041
+ import { promisify as promisify4 } from "util";
23042
+ import { writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
23043
+ import { join as join18, dirname as dirname11 } from "path";
23044
+
23045
+ // src/mcp/tools/improvement-remediation-capability.ts
23046
+ import { z as z8 } from "zod";
23047
+ var PHASE_CAPABILITIES = {
23048
+ research: /* @__PURE__ */ new Set(["untrusted-input", "secrets"]),
23049
+ implement: /* @__PURE__ */ new Set(["repo-write", "secrets"])
23050
+ };
23051
+ var RuleOfTwoViolation = class extends Error {
23052
+ constructor(message) {
23053
+ super(message);
23054
+ this.name = "RuleOfTwoViolation";
23055
+ }
23056
+ };
23057
+ var CapabilityLedger = class {
23058
+ phase;
23059
+ /** Enter a phase, setting its declared capability set as active. */
23060
+ enterPhase(phase) {
23061
+ this.phase = phase;
23062
+ }
23063
+ /** The active phase, or undefined if none entered. */
23064
+ currentPhase() {
23065
+ return this.phase;
23066
+ }
23067
+ /**
23068
+ * Assert that `capability` is permitted right now. Throws {@link RuleOfTwoViolation}
23069
+ * (fail-closed) if no phase is active or the active phase doesn't grant it —
23070
+ * which structurally prevents the forbidden three-leg conjunction, since no
23071
+ * phase grants more than two legs.
23072
+ */
23073
+ assertCapability(capability) {
23074
+ if (this.phase === void 0) {
23075
+ throw new RuleOfTwoViolation(
23076
+ `capability '${capability}' requested before any remediation phase was entered (fail-closed)`
23077
+ );
23078
+ }
23079
+ const allowed = PHASE_CAPABILITIES[this.phase];
23080
+ if (!allowed.has(capability)) {
23081
+ throw new RuleOfTwoViolation(
23082
+ `capability '${capability}' is not permitted in phase '${this.phase}' (allowed: ${[...allowed].join(", ")})`
23083
+ );
23084
+ }
23085
+ }
23086
+ };
23087
+ var RemediationActionKindSchema = z8.enum([
23088
+ "investigate",
23089
+ "adjust-routing",
23090
+ "add-test",
23091
+ "refactor",
23092
+ "update-docs",
23093
+ "fix-bug"
23094
+ ]);
23095
+ var RemediationStepSchema = z8.object({
23096
+ kind: RemediationActionKindSchema,
23097
+ description: z8.string().min(1).max(500),
23098
+ /** Optional path hint; bounded inert string, validated for traversal at use. */
23099
+ targetPath: z8.string().min(1).max(300).optional()
23100
+ }).strict();
23101
+ var RemediationPlanSchema = z8.object({
23102
+ /** Source signal key (mirrors ImprovementSignal.signalKey). */
23103
+ signalKey: z8.string().min(1).max(200),
23104
+ /** Signal category (mirrors SignalCategory). */
23105
+ category: z8.enum(["routing", "tech-debt", "bug", "security", "consensus"]),
23106
+ summary: z8.string().min(1).max(1e3),
23107
+ steps: z8.array(RemediationStepSchema).min(1).max(20)
23108
+ }).strict();
23109
+ function parseRemediationPlan(raw) {
23110
+ const result = RemediationPlanSchema.safeParse(raw);
23111
+ if (!result.success) {
23112
+ throw new RuleOfTwoViolation(
23113
+ `RemediationPlan failed strict validation at the RESEARCH\u2192IMPLEMENT boundary: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
23114
+ );
23115
+ }
23116
+ return result.data;
23117
+ }
23118
+ function renderPlanAsResearch(plan) {
23119
+ const steps = plan.steps.map((s, i) => {
23120
+ const target = s.targetPath !== void 0 ? ` (target: ${s.targetPath})` : "";
23121
+ return `${String(i + 1)}. [${s.kind}] ${s.description}${target}`;
23122
+ }).join("\n");
23123
+ return `Remediation plan for signal '${plan.signalKey}' (category: ${plan.category}).
23124
+
23125
+ ${plan.summary}
23126
+
23127
+ Steps:
23128
+ ${steps}`;
23129
+ }
23130
+
23131
+ // src/mcp/tools/auto-remediation-branch.ts
23132
+ var AUTO_REMEDIATION_BRANCH_PREFIX = "auto-remediation/";
23133
+ function autoRemediationBranchName(signalKey) {
23134
+ const slug = signalKey.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 100);
23135
+ return `${AUTO_REMEDIATION_BRANCH_PREFIX}${slug || "signal"}`;
23136
+ }
23137
+
23138
+ // src/mcp/tools/diff-secret-scan.ts
23139
+ var SECRET_PATTERNS = [
23140
+ { name: "private-key-block", re: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/ },
23141
+ { name: "aws-access-key-id", re: /\bAKIA[0-9A-Z]{16}\b/ },
23142
+ { name: "github-token", re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/ },
23143
+ { name: "slack-token", re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/ },
23144
+ { name: "google-api-key", re: /\bAIza[0-9A-Za-z_-]{35}\b/ },
23145
+ { name: "openai-key", re: /\bsk-[A-Za-z0-9]{32,}\b/ },
23146
+ { name: "anthropic-key", re: /\bsk-ant-[A-Za-z0-9_-]{20,}\b/ },
23147
+ { name: "jwt", re: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/ },
23148
+ // Generic `secret/token/password/api_key = "long-value"` assignments.
23149
+ {
23150
+ name: "generic-credential-assignment",
23151
+ re: /(?:api[_-]?key|secret|token|password|passwd|credential)\s*[:=]\s*["'][A-Za-z0-9/+_-]{16,}["']/i
23152
+ }
23153
+ ];
23154
+ function scanForSecrets(text) {
23155
+ const findings = [];
23156
+ const lines = text.split("\n");
23157
+ for (let i = 0; i < lines.length; i++) {
23158
+ const line = lines[i] ?? "";
23159
+ for (const { name, re } of SECRET_PATTERNS) {
23160
+ if (re.test(line)) findings.push({ pattern: name, line: i + 1 });
23161
+ }
23162
+ }
23163
+ return { clean: findings.length === 0, findings };
23164
+ }
23165
+ function describeSecretFindings(result) {
23166
+ if (result.clean) return "no secrets detected";
23167
+ return result.findings.map((f) => `${f.pattern}@L${String(f.line)}`).join(", ");
23168
+ }
23169
+
23170
+ // src/mcp/tools/remediation-proposal-pr.ts
23171
+ var execFileAsync4 = promisify4(execFile4);
23172
+ function buildProposalDoc(plan) {
23173
+ return `# Auto-remediation proposal \u2014 \`${plan.signalKey}\`
23174
+
23175
+ > Consensus-approved remediation **proposal** (not auto-implemented). A human or coder implements the steps below; this PR is the reviewable plan.
23176
+
23177
+ ${renderPlanAsResearch(plan)}
23178
+ `;
23179
+ }
23180
+ function planSlug(signalKey) {
23181
+ return autoRemediationBranchName(signalKey).replace(/^auto-remediation\//, "");
23182
+ }
23183
+ function makeProposalPrImplementAdapter(opts) {
23184
+ const scan = opts.scan ?? scanForSecrets;
23185
+ const baseBranch = opts.baseBranch ?? "main";
23186
+ const logger19 = opts.logger ?? createLogger({ tool: "auto-remediation-pr" });
23187
+ return async (plan, ledger) => {
23188
+ ledger.assertCapability("repo-write");
23189
+ const branch = autoRemediationBranchName(plan.signalKey);
23190
+ const doc = buildProposalDoc(plan);
23191
+ const result = scan(doc);
23192
+ if (!result.clean) {
23193
+ throw new Error(
23194
+ `proposal PR aborted \u2014 secrets in plan doc: ${describeSecretFindings(result)}`
23195
+ );
23196
+ }
23197
+ const worktree = await opts.ops.addWorktree(branch, baseBranch);
23198
+ try {
23199
+ await opts.ops.writeFileIn(worktree, `remediation-plans/${planSlug(plan.signalKey)}.md`, doc);
23200
+ await opts.ops.commitAll(worktree, `chore(auto-remediation): proposal for ${plan.signalKey}`);
23201
+ await opts.ops.pushBranch(worktree, branch);
23202
+ const prUrl = await opts.pr.createDraftPr({
23203
+ branch,
23204
+ baseBranch,
23205
+ title: `auto-remediation proposal: ${plan.signalKey}`,
23206
+ body: doc
23207
+ });
23208
+ logger19.info("auto-remediation proposal PR opened", { branch, prUrl });
23209
+ return { branch, prUrl };
23210
+ } finally {
23211
+ await opts.ops.removeWorktree(worktree).catch((err2) => {
23212
+ logger19.warn("worktree cleanup failed (non-fatal)", {
23213
+ worktree,
23214
+ error: err2 instanceof Error ? err2.message : String(err2)
23215
+ });
23216
+ });
23217
+ }
23218
+ };
23219
+ }
23220
+ function makeGitWorktreeOps(repoRoot) {
23221
+ const git = async (args, cwd) => {
23222
+ await execFileAsync4("git", [...args], { cwd });
23223
+ };
23224
+ return {
23225
+ async addWorktree(branch, baseBranch) {
23226
+ const path21 = join18(repoRoot, ".nexus-worktrees", branch.replace(/\//g, "_"));
23227
+ await git(["worktree", "add", "-b", branch, path21, baseBranch], repoRoot);
23228
+ return path21;
23229
+ },
23230
+ async writeFileIn(worktreePath, relPath, content) {
23231
+ const abs = join18(worktreePath, relPath);
23232
+ await mkdir5(dirname11(abs), { recursive: true });
23233
+ await writeFile6(abs, content, "utf8");
23234
+ },
23235
+ async commitAll(worktreePath, message) {
23236
+ await git(["add", "-A"], worktreePath);
23237
+ await git(["commit", "-m", message], worktreePath);
23238
+ },
23239
+ async pushBranch(worktreePath, branch) {
23240
+ await git(["push", "-u", "origin", branch], worktreePath);
23241
+ },
23242
+ async removeWorktree(worktreePath) {
23243
+ await git(["worktree", "remove", worktreePath, "--force"], repoRoot);
23244
+ }
23245
+ };
23246
+ }
23247
+ function makeGhPrCreator() {
23248
+ return {
23249
+ async createDraftPr({ branch, baseBranch, title, body }) {
23250
+ const { stdout } = await execFileAsync4("gh", [
23251
+ "pr",
23252
+ "create",
23253
+ "--draft",
23254
+ "--head",
23255
+ branch,
23256
+ "--base",
23257
+ baseBranch,
23258
+ "--title",
23259
+ title,
23260
+ "--body",
23261
+ body
23262
+ ]);
23263
+ return stdout.trim();
23264
+ }
23265
+ };
23266
+ }
23267
+
23039
23268
  // src/mcp/tools/auto-remediation-deps.ts
23040
23269
  var NOT_READY = {
23041
23270
  shadowSelections: 0,
@@ -23049,16 +23278,22 @@ function buildAutoRemediationDeps(opts = {}) {
23049
23278
  // lease, so enforce must not proceed. (Audit never calls this.)
23050
23279
  async () => Promise.resolve(null)
23051
23280
  );
23281
+ const implement = opts.repoRoot !== void 0 && opts.repo !== void 0 ? makeProposalPrImplementAdapter({
23282
+ ops: makeGitWorktreeOps(opts.repoRoot),
23283
+ pr: makeGhPrCreator(),
23284
+ ...opts.baseBranch !== void 0 ? { baseBranch: opts.baseBranch } : {},
23285
+ logger: logger19
23286
+ }) : () => Promise.reject(
23287
+ new Error(
23288
+ "auto-remediation implement not wired \u2014 set repo + repoRoot to enable Option B (#3669)"
23289
+ )
23290
+ );
23052
23291
  return {
23053
23292
  research: (signal) => Promise.resolve(buildRemediationPlanFromSignal(signal)),
23054
23293
  vote: makeVoteAdapter(opts.voteRunner, logger19),
23055
23294
  acquireLease,
23056
23295
  readinessEvidence: opts.readiness ?? (() => Promise.resolve(NOT_READY)),
23057
- implement: () => Promise.reject(
23058
- new Error(
23059
- "auto-remediation implement adapter not wired yet (Option B, #3669) \u2014 enforce unavailable"
23060
- )
23061
- ),
23296
+ implement,
23062
23297
  audit: (event) => {
23063
23298
  logger19.info(`[auto-remediation] ${event.step}`, {
23064
23299
  ...event.signalKey !== void 0 ? { signalKey: event.signalKey } : {},
@@ -23195,92 +23430,6 @@ function evaluateEnforceReadiness(evidence, config = DEFAULT_ENFORCE_READINESS_C
23195
23430
  return { ready: blockers.length === 0, criteria, blockers };
23196
23431
  }
23197
23432
 
23198
- // src/mcp/tools/improvement-remediation-capability.ts
23199
- import { z as z8 } from "zod";
23200
- var PHASE_CAPABILITIES = {
23201
- research: /* @__PURE__ */ new Set(["untrusted-input", "secrets"]),
23202
- implement: /* @__PURE__ */ new Set(["repo-write", "secrets"])
23203
- };
23204
- var RuleOfTwoViolation = class extends Error {
23205
- constructor(message) {
23206
- super(message);
23207
- this.name = "RuleOfTwoViolation";
23208
- }
23209
- };
23210
- var CapabilityLedger = class {
23211
- phase;
23212
- /** Enter a phase, setting its declared capability set as active. */
23213
- enterPhase(phase) {
23214
- this.phase = phase;
23215
- }
23216
- /** The active phase, or undefined if none entered. */
23217
- currentPhase() {
23218
- return this.phase;
23219
- }
23220
- /**
23221
- * Assert that `capability` is permitted right now. Throws {@link RuleOfTwoViolation}
23222
- * (fail-closed) if no phase is active or the active phase doesn't grant it —
23223
- * which structurally prevents the forbidden three-leg conjunction, since no
23224
- * phase grants more than two legs.
23225
- */
23226
- assertCapability(capability) {
23227
- if (this.phase === void 0) {
23228
- throw new RuleOfTwoViolation(
23229
- `capability '${capability}' requested before any remediation phase was entered (fail-closed)`
23230
- );
23231
- }
23232
- const allowed = PHASE_CAPABILITIES[this.phase];
23233
- if (!allowed.has(capability)) {
23234
- throw new RuleOfTwoViolation(
23235
- `capability '${capability}' is not permitted in phase '${this.phase}' (allowed: ${[...allowed].join(", ")})`
23236
- );
23237
- }
23238
- }
23239
- };
23240
- var RemediationActionKindSchema = z8.enum([
23241
- "investigate",
23242
- "adjust-routing",
23243
- "add-test",
23244
- "refactor",
23245
- "update-docs",
23246
- "fix-bug"
23247
- ]);
23248
- var RemediationStepSchema = z8.object({
23249
- kind: RemediationActionKindSchema,
23250
- description: z8.string().min(1).max(500),
23251
- /** Optional path hint; bounded inert string, validated for traversal at use. */
23252
- targetPath: z8.string().min(1).max(300).optional()
23253
- }).strict();
23254
- var RemediationPlanSchema = z8.object({
23255
- /** Source signal key (mirrors ImprovementSignal.signalKey). */
23256
- signalKey: z8.string().min(1).max(200),
23257
- /** Signal category (mirrors SignalCategory). */
23258
- category: z8.enum(["routing", "tech-debt", "bug", "security", "consensus"]),
23259
- summary: z8.string().min(1).max(1e3),
23260
- steps: z8.array(RemediationStepSchema).min(1).max(20)
23261
- }).strict();
23262
- function parseRemediationPlan(raw) {
23263
- const result = RemediationPlanSchema.safeParse(raw);
23264
- if (!result.success) {
23265
- throw new RuleOfTwoViolation(
23266
- `RemediationPlan failed strict validation at the RESEARCH\u2192IMPLEMENT boundary: ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
23267
- );
23268
- }
23269
- return result.data;
23270
- }
23271
- function renderPlanAsResearch(plan) {
23272
- const steps = plan.steps.map((s, i) => {
23273
- const target = s.targetPath !== void 0 ? ` (target: ${s.targetPath})` : "";
23274
- return `${String(i + 1)}. [${s.kind}] ${s.description}${target}`;
23275
- }).join("\n");
23276
- return `Remediation plan for signal '${plan.signalKey}' (category: ${plan.category}).
23277
-
23278
- ${plan.summary}
23279
-
23280
- Steps:
23281
- ${steps}`;
23282
- }
23283
-
23284
23433
  // src/mcp/tools/remediation-circuit-breaker.ts
23285
23434
  var DEFAULT_CIRCUIT_BREAKER_CONFIG = { threshold: 3 };
23286
23435
  var RemediationCircuitBreaker = class {
@@ -23584,16 +23733,16 @@ async function handleAutoRemediateCommand(args) {
23584
23733
  // src/cli/migrate-command.ts
23585
23734
  import { cpSync, existsSync as existsSync22, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
23586
23735
  import { homedir } from "os";
23587
- import { join as join18 } from "path";
23736
+ import { join as join19 } from "path";
23588
23737
  var logger18 = createLogger({ component: "migrate-command" });
23589
- var HOMEDIR_DEFAULT_BASE = join18(homedir(), ".nexus-agents");
23738
+ var HOMEDIR_DEFAULT_BASE = join19(homedir(), ".nexus-agents");
23590
23739
  function countItems(dir) {
23591
23740
  if (!existsSync22(dir)) return 0;
23592
23741
  try {
23593
23742
  const entries = readdirSync3(dir, { withFileTypes: true });
23594
23743
  let n = 0;
23595
23744
  for (const e of entries) {
23596
- const p = join18(dir, e.name);
23745
+ const p = join19(dir, e.name);
23597
23746
  if (e.isDirectory()) {
23598
23747
  n += countItems(p);
23599
23748
  } else {
@@ -23648,8 +23797,8 @@ function checkEarlyExits(fromBase, toBase, dryRun) {
23648
23797
  return null;
23649
23798
  }
23650
23799
  function planAndExecuteEntry(name, fromBase, toBase, perRepoSet, dryRun) {
23651
- const source = join18(fromBase, name);
23652
- const target = join18(toBase, name);
23800
+ const source = join19(fromBase, name);
23801
+ const target = join19(toBase, name);
23653
23802
  if (!perRepoSet.has(name)) {
23654
23803
  return { subdir: name, status: "skipped-not-per-repo", source, target: "", itemsCopied: 0 };
23655
23804
  }
@@ -23669,7 +23818,7 @@ function planAndExecuteEntry(name, fromBase, toBase, perRepoSet, dryRun) {
23669
23818
  function resolveDefaultTarget(cwd) {
23670
23819
  const repoRoot = findRepoRoot(cwd);
23671
23820
  if (repoRoot === null) return null;
23672
- return join18(repoRoot, ".nexus-agents");
23821
+ return join19(repoRoot, ".nexus-agents");
23673
23822
  }
23674
23823
  function formatMigrationResult(result) {
23675
23824
  if (!result.success) {