nexus-agents 4.14.0 → 4.14.2

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 (151) hide show
  1. package/dist/{child-mcp-config-OULJI7VH.js → child-mcp-config-FNVCS5MQ.js} +5 -5
  2. package/dist/{chunk-4WVGF3SL.js → chunk-2RSNO5SP.js} +6 -6
  3. package/dist/{chunk-B6C3JCQB.js → chunk-6JEPV4XW.js} +2 -2
  4. package/dist/{chunk-VPTDFOAK.js → chunk-BLJY4YKP.js} +3 -3
  5. package/dist/{chunk-YHY5AZL2.js → chunk-CHRJKE7S.js} +2 -2
  6. package/dist/{chunk-LUBCO6ZN.js → chunk-CPRPZPX7.js} +2 -2
  7. package/dist/{chunk-IZUT5OHH.js → chunk-E536V52C.js} +2 -2
  8. package/dist/{chunk-IZUT5OHH.js.map → chunk-E536V52C.js.map} +1 -1
  9. package/dist/{chunk-56LJJOBN.js → chunk-ED47MBY5.js} +4 -4
  10. package/dist/{chunk-XWIFBBBH.js → chunk-EEJFMRHJ.js} +2 -2
  11. package/dist/{chunk-NMNTYAV2.js → chunk-FFMDYBGA.js} +3 -3
  12. package/dist/{chunk-V253VL7S.js → chunk-GEOALLHA.js} +4 -4
  13. package/dist/{chunk-F6SRPGDK.js → chunk-GFCCNGI7.js} +9 -9
  14. package/dist/{chunk-P45XNRHB.js → chunk-GT5B35X3.js} +2 -2
  15. package/dist/{chunk-ARTQO7XZ.js → chunk-H7LRUN27.js} +6 -6
  16. package/dist/{chunk-2HUC6NZN.js → chunk-HHLCAWAV.js} +2 -2
  17. package/dist/{chunk-HBF7O6CN.js → chunk-HJK5RZBX.js} +6 -6
  18. package/dist/{chunk-J6JEW4QZ.js → chunk-JD4HTIMG.js} +7 -7
  19. package/dist/{chunk-6454KSCM.js → chunk-JPA3GAWT.js} +2 -2
  20. package/dist/{chunk-XIIUIBSX.js → chunk-KGFFEQTN.js} +2 -2
  21. package/dist/{chunk-WATNLQS7.js → chunk-L6QLJ4VK.js} +2 -2
  22. package/dist/{chunk-EHMMTCCS.js → chunk-LLVEIPOZ.js} +8 -10
  23. package/dist/{chunk-EHMMTCCS.js.map → chunk-LLVEIPOZ.js.map} +1 -1
  24. package/dist/{chunk-232RDSCM.js → chunk-LWR2K2VM.js} +2 -2
  25. package/dist/{chunk-F6MUN523.js → chunk-MU7J7D5Q.js} +2 -2
  26. package/dist/{chunk-OQLFRJCW.js → chunk-NBMWONHE.js} +53 -53
  27. package/dist/chunk-NBMWONHE.js.map +1 -0
  28. package/dist/{chunk-VENIFAMX.js → chunk-NOROX25U.js} +4 -4
  29. package/dist/{chunk-7STKQ3IH.js → chunk-NX5VK5NW.js} +2 -2
  30. package/dist/{chunk-SNJNRDKZ.js → chunk-RQV5ZUXT.js} +2 -2
  31. package/dist/{chunk-KUGBMQAP.js → chunk-SQUEE5ZA.js} +4 -4
  32. package/dist/{chunk-W6IZVEIM.js → chunk-T5WQAYX2.js} +2 -2
  33. package/dist/{chunk-VZVGT4MN.js → chunk-UZRVS7MK.js} +5 -5
  34. package/dist/{chunk-PWSW5Z4L.js → chunk-VE3PAJ5M.js} +20 -10
  35. package/dist/chunk-VE3PAJ5M.js.map +1 -0
  36. package/dist/{chunk-TANMACVQ.js → chunk-VFGP57F5.js} +2 -2
  37. package/dist/{chunk-I6JMAM4J.js → chunk-VLDYF7EQ.js} +2 -2
  38. package/dist/{chunk-WPLXWTUK.js → chunk-W4Q5NZYZ.js} +5 -3
  39. package/dist/chunk-W4Q5NZYZ.js.map +1 -0
  40. package/dist/{chunk-TEWDRLCM.js → chunk-WCRXZJRW.js} +3426 -2208
  41. package/dist/chunk-WCRXZJRW.js.map +1 -0
  42. package/dist/{chunk-5PCTGUMF.js → chunk-WHOODKBB.js} +3 -3
  43. package/dist/chunk-WMSWVHFZ.js +1240 -0
  44. package/dist/chunk-WMSWVHFZ.js.map +1 -0
  45. package/dist/{chunk-XGCYJLTP.js → chunk-XT4J7QAO.js} +10 -10
  46. package/dist/{cli-circuit-breaker-4AVWA333.js → cli-circuit-breaker-A7GM4LXS.js} +5 -5
  47. package/dist/cli.js +2987 -1098
  48. package/dist/cli.js.map +1 -1
  49. package/dist/{composite-router-7OTZN7NS.js → composite-router-SVZA4OCQ.js} +4 -4
  50. package/dist/{consensus-vote-KFLDRUJJ.js → consensus-vote-7HGSTZKF.js} +20 -20
  51. package/dist/{context-retriever-2NZWXF6Z.js → context-retriever-QTASR42L.js} +11 -12
  52. package/dist/doctor-deep-NBDOSO4E.js +13 -0
  53. package/dist/{doctor-live-25LRXKBO.js → doctor-live-HZWIMIT6.js} +13 -13
  54. package/dist/{expert-bridge-2YLK3E6Q.js → expert-bridge-GY7RL4DF.js} +6 -6
  55. package/dist/factory-4FZ5FPZW.js +15 -0
  56. package/dist/factory-73NHOTVL.js +25 -0
  57. package/dist/{improvement-review-FFADRM4I.js → improvement-review-ZF3VHZEG.js} +7 -7
  58. package/dist/index.js +47 -48
  59. package/dist/index.js.map +1 -1
  60. package/dist/{init-opencode-OSZF74AL.js → init-opencode-EOCW7P4L.js} +8 -8
  61. package/dist/issue-triage-L6UDFHF4.js +17 -0
  62. package/dist/{learning-persistence-EGY4VKKO.js → learning-persistence-SR5F4DSY.js} +3 -3
  63. package/dist/{pr-reviewer-helpers-E3QXOVMT.js → pr-reviewer-helpers-KOE3X72Y.js} +6 -6
  64. package/dist/{registry-command-ZMKZ5CRZ.js → registry-command-7ZENMDK4.js} +4 -4
  65. package/dist/{repo-security-plan-BPET6KTP.js → repo-security-plan-PCHVMGHX.js} +5 -5
  66. package/dist/research-helpers-synthesize-KTVQVLII.js +14 -0
  67. package/dist/{routing-memory-3UWP6NOX.js → routing-memory-QTBLYGMC.js} +4 -4
  68. package/dist/{session-memory-N2ABITF7.js → session-memory-S7L4K5R5.js} +5 -5
  69. package/dist/setup-config-7SWEJXN4.js +11 -0
  70. package/dist/{setup-custom-api-PE2M5HWL.js → setup-custom-api-WYXYQMQF.js} +6 -6
  71. package/dist/setup-data-dir-EMXRXEFQ.js +28 -0
  72. package/dist/{tool-memory-CARCDHTN.js → tool-memory-PSQQ4NYD.js} +8 -8
  73. package/dist/{tool-memory-registry-adapters-QEC3YHB2.js → tool-memory-registry-adapters-UMPIJLHS.js} +3 -3
  74. package/dist/unified-registry-UEDZXMH3.js +26 -0
  75. package/dist/{weather-report-ZA7DBOC3.js → weather-report-CKEPMK5R.js} +4 -4
  76. package/package.json +1 -1
  77. package/dist/chunk-AH74OHVO.js +0 -1942
  78. package/dist/chunk-AH74OHVO.js.map +0 -1
  79. package/dist/chunk-NUBSJGQZ.js +0 -14
  80. package/dist/chunk-NUBSJGQZ.js.map +0 -1
  81. package/dist/chunk-OQLFRJCW.js.map +0 -1
  82. package/dist/chunk-PWSW5Z4L.js.map +0 -1
  83. package/dist/chunk-TEWDRLCM.js.map +0 -1
  84. package/dist/chunk-WPLXWTUK.js.map +0 -1
  85. package/dist/chunk-XOKWIL6I.js +0 -2455
  86. package/dist/chunk-XOKWIL6I.js.map +0 -1
  87. package/dist/doctor-deep-MC2FGSAF.js +0 -13
  88. package/dist/factory-G57M6CD4.js +0 -15
  89. package/dist/factory-T7KN4CKD.js +0 -25
  90. package/dist/issue-triage-KVYM5FQ5.js +0 -17
  91. package/dist/research-helpers-synthesize-M4SONOWL.js +0 -15
  92. package/dist/setup-command-LXMVWIKJ.js +0 -45
  93. package/dist/setup-config-PJNXP57G.js +0 -11
  94. package/dist/unified-registry-HVALHHPY.js +0 -26
  95. /package/dist/{child-mcp-config-OULJI7VH.js.map → child-mcp-config-FNVCS5MQ.js.map} +0 -0
  96. /package/dist/{chunk-4WVGF3SL.js.map → chunk-2RSNO5SP.js.map} +0 -0
  97. /package/dist/{chunk-B6C3JCQB.js.map → chunk-6JEPV4XW.js.map} +0 -0
  98. /package/dist/{chunk-VPTDFOAK.js.map → chunk-BLJY4YKP.js.map} +0 -0
  99. /package/dist/{chunk-YHY5AZL2.js.map → chunk-CHRJKE7S.js.map} +0 -0
  100. /package/dist/{chunk-LUBCO6ZN.js.map → chunk-CPRPZPX7.js.map} +0 -0
  101. /package/dist/{chunk-56LJJOBN.js.map → chunk-ED47MBY5.js.map} +0 -0
  102. /package/dist/{chunk-XWIFBBBH.js.map → chunk-EEJFMRHJ.js.map} +0 -0
  103. /package/dist/{chunk-NMNTYAV2.js.map → chunk-FFMDYBGA.js.map} +0 -0
  104. /package/dist/{chunk-V253VL7S.js.map → chunk-GEOALLHA.js.map} +0 -0
  105. /package/dist/{chunk-F6SRPGDK.js.map → chunk-GFCCNGI7.js.map} +0 -0
  106. /package/dist/{chunk-P45XNRHB.js.map → chunk-GT5B35X3.js.map} +0 -0
  107. /package/dist/{chunk-ARTQO7XZ.js.map → chunk-H7LRUN27.js.map} +0 -0
  108. /package/dist/{chunk-2HUC6NZN.js.map → chunk-HHLCAWAV.js.map} +0 -0
  109. /package/dist/{chunk-HBF7O6CN.js.map → chunk-HJK5RZBX.js.map} +0 -0
  110. /package/dist/{chunk-J6JEW4QZ.js.map → chunk-JD4HTIMG.js.map} +0 -0
  111. /package/dist/{chunk-6454KSCM.js.map → chunk-JPA3GAWT.js.map} +0 -0
  112. /package/dist/{chunk-XIIUIBSX.js.map → chunk-KGFFEQTN.js.map} +0 -0
  113. /package/dist/{chunk-WATNLQS7.js.map → chunk-L6QLJ4VK.js.map} +0 -0
  114. /package/dist/{chunk-232RDSCM.js.map → chunk-LWR2K2VM.js.map} +0 -0
  115. /package/dist/{chunk-F6MUN523.js.map → chunk-MU7J7D5Q.js.map} +0 -0
  116. /package/dist/{chunk-VENIFAMX.js.map → chunk-NOROX25U.js.map} +0 -0
  117. /package/dist/{chunk-7STKQ3IH.js.map → chunk-NX5VK5NW.js.map} +0 -0
  118. /package/dist/{chunk-SNJNRDKZ.js.map → chunk-RQV5ZUXT.js.map} +0 -0
  119. /package/dist/{chunk-KUGBMQAP.js.map → chunk-SQUEE5ZA.js.map} +0 -0
  120. /package/dist/{chunk-W6IZVEIM.js.map → chunk-T5WQAYX2.js.map} +0 -0
  121. /package/dist/{chunk-VZVGT4MN.js.map → chunk-UZRVS7MK.js.map} +0 -0
  122. /package/dist/{chunk-TANMACVQ.js.map → chunk-VFGP57F5.js.map} +0 -0
  123. /package/dist/{chunk-I6JMAM4J.js.map → chunk-VLDYF7EQ.js.map} +0 -0
  124. /package/dist/{chunk-5PCTGUMF.js.map → chunk-WHOODKBB.js.map} +0 -0
  125. /package/dist/{chunk-XGCYJLTP.js.map → chunk-XT4J7QAO.js.map} +0 -0
  126. /package/dist/{cli-circuit-breaker-4AVWA333.js.map → cli-circuit-breaker-A7GM4LXS.js.map} +0 -0
  127. /package/dist/{composite-router-7OTZN7NS.js.map → composite-router-SVZA4OCQ.js.map} +0 -0
  128. /package/dist/{consensus-vote-KFLDRUJJ.js.map → consensus-vote-7HGSTZKF.js.map} +0 -0
  129. /package/dist/{context-retriever-2NZWXF6Z.js.map → context-retriever-QTASR42L.js.map} +0 -0
  130. /package/dist/{doctor-deep-MC2FGSAF.js.map → doctor-deep-NBDOSO4E.js.map} +0 -0
  131. /package/dist/{doctor-live-25LRXKBO.js.map → doctor-live-HZWIMIT6.js.map} +0 -0
  132. /package/dist/{expert-bridge-2YLK3E6Q.js.map → expert-bridge-GY7RL4DF.js.map} +0 -0
  133. /package/dist/{factory-G57M6CD4.js.map → factory-4FZ5FPZW.js.map} +0 -0
  134. /package/dist/{factory-T7KN4CKD.js.map → factory-73NHOTVL.js.map} +0 -0
  135. /package/dist/{improvement-review-FFADRM4I.js.map → improvement-review-ZF3VHZEG.js.map} +0 -0
  136. /package/dist/{init-opencode-OSZF74AL.js.map → init-opencode-EOCW7P4L.js.map} +0 -0
  137. /package/dist/{issue-triage-KVYM5FQ5.js.map → issue-triage-L6UDFHF4.js.map} +0 -0
  138. /package/dist/{learning-persistence-EGY4VKKO.js.map → learning-persistence-SR5F4DSY.js.map} +0 -0
  139. /package/dist/{pr-reviewer-helpers-E3QXOVMT.js.map → pr-reviewer-helpers-KOE3X72Y.js.map} +0 -0
  140. /package/dist/{registry-command-ZMKZ5CRZ.js.map → registry-command-7ZENMDK4.js.map} +0 -0
  141. /package/dist/{repo-security-plan-BPET6KTP.js.map → repo-security-plan-PCHVMGHX.js.map} +0 -0
  142. /package/dist/{research-helpers-synthesize-M4SONOWL.js.map → research-helpers-synthesize-KTVQVLII.js.map} +0 -0
  143. /package/dist/{routing-memory-3UWP6NOX.js.map → routing-memory-QTBLYGMC.js.map} +0 -0
  144. /package/dist/{session-memory-N2ABITF7.js.map → session-memory-S7L4K5R5.js.map} +0 -0
  145. /package/dist/{setup-command-LXMVWIKJ.js.map → setup-config-7SWEJXN4.js.map} +0 -0
  146. /package/dist/{setup-custom-api-PE2M5HWL.js.map → setup-custom-api-WYXYQMQF.js.map} +0 -0
  147. /package/dist/{setup-config-PJNXP57G.js.map → setup-data-dir-EMXRXEFQ.js.map} +0 -0
  148. /package/dist/{tool-memory-CARCDHTN.js.map → tool-memory-PSQQ4NYD.js.map} +0 -0
  149. /package/dist/{tool-memory-registry-adapters-QEC3YHB2.js.map → tool-memory-registry-adapters-UMPIJLHS.js.map} +0 -0
  150. /package/dist/{unified-registry-HVALHHPY.js.map → unified-registry-UEDZXMH3.js.map} +0 -0
  151. /package/dist/{weather-report-ZA7DBOC3.js.map → weather-report-CKEPMK5R.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/setup-command.ts","../src/cli/setup-types.ts","../src/cli/setup-environment.ts","../src/cli/setup-mcp.ts","../src/cli/setup-rules.ts","../src/cli/setup-formatting.ts","../src/cli/setup-opencode.ts","../src/cli/setup-gemini.ts","../src/cli/setup-codex.ts","../src/cli/setup-wizard.ts","../src/cli/setup-permissions.ts","../src/cli/verify-command.ts"],"sourcesContent":["/* eslint-disable max-lines -- Cohesive setup command module (governance: 400-600 OK if cohesive) */\n/**\n * nexus-agents setup command\n *\n * Configures Claude CLI integration with nexus-agents MCP server.\n * Generates MCP configuration snippet and .rules/nexus-agents.md.\n *\n * @module cli/setup-command\n * (Source: Issue #363 - Auto-configure Claude CLI integration)\n * (Source: Issue #425 - Interactive setup wizard)\n */\n\nimport { existsSync } from 'node:fs';\nimport type { SetupOptions, SetupResult, SetupStep, EnvironmentInfo } from './setup-types.js';\nimport { SetupOptionsSchema } from './setup-types.js';\nimport { getTimeProvider, getErrorMessage } from '../core/index.js';\nimport {\n detectEnvironment,\n generateMcpSnippet,\n generateRulesContent,\n createRulesFile,\n getRulesFilePath,\n formatStatus,\n formatHeader,\n formatCodeBlock,\n isInteractive,\n configureMcpServer,\n // Hook configuration (Issue #416)\n configureHooks,\n generateHookSnippet,\n} from './setup-helpers.js';\nimport type { McpConfigResult, HookConfigResult } from './setup-helpers.js';\nimport { initDataDirectories } from './setup-data-dir.js';\nimport type { DataDirInitResult } from './setup-data-dir.js';\nimport { runConfigInitSync } from './setup-config.js';\nimport { detectOpenCodeCli, configureOpenCode } from './setup-opencode.js';\nimport { detectGeminiCli, configureGemini } from './setup-gemini.js';\nimport { detectCodexCli, configureCodex } from './setup-codex.js';\nimport { formatDetectionMessage, detectionRecoveryHint } from './cli-detection-error.js';\nimport { VERSION } from '../version.js';\nimport { runWizard } from './setup-wizard.js';\nimport { generatePermissionsSnippet, buildPermissionsBanner } from './setup-permissions.js';\n// #2137: post-setup health gate. Surfaces install-time issues that are easy\n// to miss (better-sqlite3 native build, missing API keys, unwritable data\n// dirs) inline at the end of setup, with copy-pasteable remediation.\nimport { runVerify } from './verify-command.js';\nimport { colors, symbols } from './ansi-output.js';\n// #2891: auto-gitignore + first-run layout hint at the end of setup.\nimport { ensureGitignored } from '../config/portable-mode.js';\nimport { findRepoRoot } from '../config/repo-root-detection.js';\n\n// ============================================================================\n// Output Helpers\n// ============================================================================\n\n/**\n * Writes a line to stdout.\n */\nfunction writeLine(text: string): void {\n process.stdout.write(text + '\\n');\n}\n\n/**\n * Writes an empty line.\n */\nfunction writeEmptyLine(): void {\n process.stdout.write('\\n');\n}\n\n/**\n * Prints MCP configuration result section.\n */\nfunction printMcpResult(mcpResult: McpConfigResult, snippet: string | undefined): void {\n writeLine(formatHeader('MCP Configuration'));\n writeLine('─'.repeat(40));\n if (mcpResult.success) {\n writeLine(mcpResult.message);\n writeLine('Run `/mcp` in Claude Code to verify.');\n } else {\n writeLine(`Failed: ${mcpResult.message}`);\n if (snippet !== undefined) {\n writeEmptyLine();\n writeLine('Manual fallback - run:');\n writeEmptyLine();\n writeLine(formatCodeBlock(`claude mcp add-json nexus-agents '${snippet}'`));\n }\n }\n writeEmptyLine();\n}\n\n/**\n * Prints rules file section.\n */\nfunction printRulesFile(rulesPath: string): void {\n writeLine(formatHeader('Rules File'));\n writeLine('─'.repeat(40));\n writeLine(`Created: ${rulesPath}`);\n writeLine('Claude will now have context about nexus-agents tools.');\n writeEmptyLine();\n}\n\n/**\n * Prints hooks configuration result section.\n * (Source: Issue #416)\n */\nfunction printHooksResult(hookResult: HookConfigResult, snippet: string | undefined): void {\n writeLine(formatHeader('Hooks Configuration'));\n writeLine('─'.repeat(40));\n if (hookResult.success) {\n writeLine(hookResult.message);\n writeLine('Hooks will track sessions, metrics, and validate tool use.');\n } else {\n writeLine(`Note: ${hookResult.message}`);\n if (snippet !== undefined) {\n writeEmptyLine();\n writeLine('Manual fallback - add to ~/.claude/settings.json:');\n writeEmptyLine();\n writeLine(formatCodeBlock(snippet));\n }\n }\n writeEmptyLine();\n}\n\n/**\n * Prints warnings section.\n */\nfunction printWarnings(warnings: readonly string[]): void {\n writeLine(formatHeader('Warnings'));\n writeLine('─'.repeat(40));\n for (const warning of warnings) {\n writeLine(`⚠ ${warning}`);\n }\n writeEmptyLine();\n}\n\n/**\n * Prints errors section.\n */\nfunction printErrors(errors: readonly string[]): void {\n writeLine(formatHeader('Errors'));\n writeLine('─'.repeat(40));\n for (const error of errors) {\n writeLine(`✗ ${error}`);\n }\n writeEmptyLine();\n}\n\n/**\n * Prints next steps section.\n */\nfunction printNextSteps(mcpConfigured: boolean, hasMcpSnippet: boolean): void {\n writeLine(formatHeader('Next Steps'));\n writeLine('─'.repeat(40));\n if (hasMcpSnippet && !mcpConfigured) {\n writeLine('1. Configure MCP manually (see above)');\n writeLine('2. Restart Claude Code');\n }\n writeLine('3. Run: nexus-agents doctor');\n writeLine('4. Try: nexus-agents orchestrate \"Hello World\"');\n writeEmptyLine();\n}\n\n/**\n * Prints steps with status indicators.\n */\nfunction printSteps(steps: readonly SetupStep[], verbose: boolean): void {\n for (const step of steps) {\n const status = formatStatus(step.status);\n const duration = step.durationMs !== undefined ? ` (${String(step.durationMs)}ms)` : '';\n writeLine(`${status} ${step.name}${verbose ? duration : ''}`);\n if (step.message !== undefined && (verbose || step.status === 'failed')) {\n writeLine(` ${step.message}`);\n }\n }\n writeEmptyLine();\n}\n\n/**\n * Prints result summary line.\n */\nfunction printSummary(success: boolean): void {\n const summary = success ? '✓ Setup completed successfully!' : '✗ Setup completed with errors';\n writeLine(success ? `\\x1b[32m${summary}\\x1b[0m` : `\\x1b[31m${summary}\\x1b[0m`);\n writeEmptyLine();\n}\n\n// ============================================================================\n// Setup Steps\n// ============================================================================\n\n/**\n * Runs the environment detection step.\n */\nfunction runDetectionStep(projectRoot: string): { env: EnvironmentInfo; step: SetupStep } {\n const time = getTimeProvider();\n const startTime = time.now();\n const env = detectEnvironment(projectRoot);\n\n return {\n env,\n step: {\n name: 'Environment Detection',\n status: 'success',\n message: `Platform: ${env.platform}, Claude CLI: ${env.claudeCli.installed ? (env.claudeCli.version ?? 'installed') : 'not found'}`,\n durationMs: time.now() - startTime,\n },\n };\n}\n\n/** Minimum Node.js major version required. */\nconst REQUIRED_NODE_MAJOR = 22;\n\n/**\n * Runs the prerequisite validation step.\n * Checks Node.js version and warns about missing package managers.\n */\nfunction runPrerequisiteStep(): { step: SetupStep; warnings: readonly string[] } {\n const time = getTimeProvider();\n const startTime = time.now();\n const warnings: string[] = [];\n\n const version = process.version;\n const major = Number(version.slice(1).split('.')[0]);\n const nodeOk = major >= REQUIRED_NODE_MAJOR;\n\n if (!nodeOk) {\n warnings.push(\n `Node.js ${version} detected — v${String(REQUIRED_NODE_MAJOR)}.x+ required. Some features may not work.`\n );\n }\n\n const status = nodeOk ? 'success' : 'warning';\n const message = nodeOk\n ? `Node.js ${version} (meets v${String(REQUIRED_NODE_MAJOR)}.x requirement)`\n : `Node.js ${version} — v${String(REQUIRED_NODE_MAJOR)}.x+ required`;\n\n return {\n step: {\n name: 'Prerequisite Check',\n status,\n message,\n durationMs: time.now() - startTime,\n },\n warnings,\n };\n}\n\n/** MCP step result type. */\ntype McpStepResult = {\n step: SetupStep;\n snippet: string | undefined;\n mcpResult: McpConfigResult | undefined;\n};\n\n/** Creates an MCP step result. */\nfunction makeMcpResult(\n status: SetupStep['status'],\n message: string,\n startTime: number,\n snippet?: string,\n mcpResult?: McpConfigResult\n): McpStepResult {\n return {\n step: {\n name: 'MCP Configuration',\n status,\n message,\n durationMs: getTimeProvider().now() - startTime,\n },\n snippet,\n mcpResult,\n };\n}\n\n/**\n * Runs the MCP configuration step.\n */\nfunction runMcpConfigStep(env: EnvironmentInfo, options: SetupOptions): McpStepResult {\n const startTime = getTimeProvider().now();\n\n if (options.skipMcp) {\n return makeMcpResult('skipped', 'Skipped (--skip-mcp)', startTime);\n }\n\n const useNpx = !env.claudeCli.installed;\n const snippet = generateMcpSnippet(useNpx);\n\n if (!env.claudeCli.installed) {\n const mcpResult: McpConfigResult = {\n success: false,\n alreadyConfigured: false,\n message: 'Claude CLI not installed',\n };\n return makeMcpResult(\n 'warning',\n 'Claude CLI not found - manual configuration required',\n startTime,\n snippet,\n mcpResult\n );\n }\n\n const mcpResult = configureMcpServer(useNpx, options.force, options.scope);\n const status = mcpResult.success\n ? mcpResult.alreadyConfigured\n ? 'skipped'\n : 'success'\n : 'failed';\n return makeMcpResult(\n status,\n mcpResult.message,\n startTime,\n mcpResult.success ? undefined : snippet,\n mcpResult\n );\n}\n\n/** Creates a rules step result. */\nfunction makeRulesResult(\n status: SetupStep['status'],\n message: string,\n startTime: number,\n rulesPath?: string\n): { step: SetupStep; rulesPath: string | undefined } {\n return {\n step: { name: 'Rules File', status, message, durationMs: getTimeProvider().now() - startTime },\n rulesPath,\n };\n}\n\n/** Creates a hooks step result. */\nfunction makeHooksResult(\n status: SetupStep['status'],\n message: string,\n startTime: number,\n hookResult?: HookConfigResult\n): { step: SetupStep; hookSnippet: string | undefined; hookResult: HookConfigResult | undefined } {\n return {\n step: {\n name: 'Hooks Configuration',\n status,\n message,\n durationMs: getTimeProvider().now() - startTime,\n },\n hookSnippet: hookResult?.success === false ? generateHookSnippet() : undefined,\n hookResult,\n };\n}\n\n/**\n * Runs the rules file generation step.\n */\nfunction runRulesStep(\n env: EnvironmentInfo,\n options: SetupOptions\n): { step: SetupStep; rulesPath: string | undefined } {\n const startTime = getTimeProvider().now();\n\n if (options.skipRules) {\n return makeRulesResult('skipped', 'Skipped (--skip-rules)', startTime);\n }\n\n const rulesPath = getRulesFilePath(env.projectInfo.root);\n if (existsSync(rulesPath) && !options.force) {\n return makeRulesResult(\n 'skipped',\n 'Rules file already exists (use --force to overwrite)',\n startTime\n );\n }\n\n try {\n const createdPath = createRulesFile(env.projectInfo.root, options.dryRun);\n const msg = options.dryRun ? `Would create: ${createdPath}` : `Created: ${createdPath}`;\n return makeRulesResult('success', msg, startTime, createdPath);\n } catch (error) {\n const msg = getErrorMessage(error);\n return makeRulesResult('failed', `Failed to create rules file: ${msg}`, startTime);\n }\n}\n\n/**\n * Runs the hooks configuration step.\n * (Source: Issue #416 - Setup command hook configuration)\n */\nfunction runHooksStep(\n env: EnvironmentInfo,\n options: SetupOptions\n): { step: SetupStep; hookSnippet: string | undefined; hookResult: HookConfigResult | undefined } {\n const startTime = getTimeProvider().now();\n\n if (options.skipHooks) {\n return makeHooksResult('skipped', 'Skipped (--skip-hooks)', startTime);\n }\n\n // If Claude CLI is not installed, we can't configure automatically\n if (!env.claudeCli.installed) {\n return makeHooksResult(\n 'warning',\n 'Claude CLI not found - manual hook configuration required',\n startTime,\n {\n success: false,\n alreadyConfigured: false,\n message: 'Claude CLI not installed',\n }\n );\n }\n\n // If dry-run, just report what would happen\n if (options.dryRun) {\n return makeHooksResult(\n 'success',\n 'Would configure nexus-agents hooks in Claude Code settings',\n startTime\n );\n }\n\n // Configure using Claude CLI\n const hookResult = configureHooks(options.force);\n const status = hookResult.success\n ? hookResult.alreadyConfigured\n ? 'skipped'\n : 'success'\n : 'failed';\n\n return makeHooksResult(status, hookResult.message, startTime, hookResult);\n}\n\n/**\n * Runs the OpenCode MCP configuration step (#1253).\n */\nfunction runOpenCodeStep(options: SetupOptions): SetupStep {\n const startTime = getTimeProvider().now();\n if (options.skipOpencode) {\n return {\n name: 'OpenCode MCP',\n status: 'skipped',\n message: 'Skipped (--skip-opencode)',\n durationMs: 0,\n };\n }\n const cliInfo = detectOpenCodeCli();\n if (!cliInfo.installed) {\n return {\n name: 'OpenCode MCP',\n status: 'skipped',\n message: `${formatDetectionMessage('OpenCode CLI', cliInfo.detectionError)}\\n → ${detectionRecoveryHint('opencode', cliInfo.detectionError)}`,\n durationMs: getTimeProvider().now() - startTime,\n };\n }\n const result = configureOpenCode(options.force, options.dryRun);\n return {\n name: 'OpenCode MCP',\n status: result.success ? (result.alreadyConfigured ? 'skipped' : 'success') : 'failed',\n message: result.message,\n durationMs: getTimeProvider().now() - startTime,\n };\n}\n\n/**\n * Runs the Gemini CLI MCP configuration step (#1259).\n */\nfunction runGeminiStep(options: SetupOptions): SetupStep {\n const startTime = getTimeProvider().now();\n if (options.skipGemini) {\n return {\n name: 'Gemini MCP',\n status: 'skipped',\n message: 'Skipped (--skip-gemini)',\n durationMs: 0,\n };\n }\n const cliInfo = detectGeminiCli();\n if (!cliInfo.installed) {\n return {\n name: 'Gemini MCP',\n status: 'skipped',\n message: `${formatDetectionMessage('Gemini CLI', cliInfo.detectionError)}\\n → ${detectionRecoveryHint('gemini', cliInfo.detectionError)}`,\n durationMs: getTimeProvider().now() - startTime,\n };\n }\n const result = configureGemini(options.force, options.dryRun, options.scope);\n return {\n name: 'Gemini MCP',\n status: result.success ? (result.alreadyConfigured ? 'skipped' : 'success') : 'failed',\n message: result.message,\n durationMs: getTimeProvider().now() - startTime,\n };\n}\n\n/**\n * Runs the data directory initialization step (#1249).\n */\nfunction runDataDirStep(options: SetupOptions): { step: SetupStep; result: DataDirInitResult } {\n const startTime = getTimeProvider().now();\n const dataDirResult = initDataDirectories(options.dryRun);\n return {\n step: {\n name: 'Data Directory',\n status: dataDirResult.success\n ? dataDirResult.created.length > 0\n ? 'success'\n : 'skipped'\n : 'failed',\n message: dataDirResult.success\n ? dataDirResult.created.length > 0\n ? `Created ${String(dataDirResult.created.length)} directories`\n : 'All directories already exist'\n : `Failed: ${dataDirResult.error ?? 'Unknown error'}`,\n durationMs: getTimeProvider().now() - startTime,\n },\n result: dataDirResult,\n };\n}\n\n/**\n * Runs the Codex CLI MCP configuration step (#1263).\n */\nfunction runCodexStep(options: SetupOptions): SetupStep {\n const startTime = getTimeProvider().now();\n if (options.skipCodex) {\n return {\n name: 'Codex MCP',\n status: 'skipped',\n message: 'Skipped (--skip-codex)',\n durationMs: 0,\n };\n }\n const cliInfo = detectCodexCli();\n if (!cliInfo.installed) {\n return {\n name: 'Codex MCP',\n status: 'skipped',\n message: `${formatDetectionMessage('Codex CLI', cliInfo.detectionError)}\\n → ${detectionRecoveryHint('codex', cliInfo.detectionError)}`,\n durationMs: getTimeProvider().now() - startTime,\n };\n }\n const result = configureCodex(options.force, options.dryRun);\n return {\n name: 'Codex MCP',\n status: result.success ? (result.alreadyConfigured ? 'skipped' : 'success') : 'failed',\n message: result.message,\n durationMs: getTimeProvider().now() - startTime,\n };\n}\n\n/**\n * Runs the config file generation step (#1252).\n */\nfunction runConfigStep(projectRoot: string, options: SetupOptions): SetupStep {\n const startTime = getTimeProvider().now();\n if (options.skipConfig) {\n return {\n name: 'Configuration',\n status: 'skipped',\n message: 'Skipped (--skip-config)',\n durationMs: 0,\n };\n }\n const result = runConfigInitSync(projectRoot, options.force, options.dryRun);\n return {\n name: 'Configuration',\n status: result.success ? (result.created ? 'success' : 'skipped') : 'failed',\n message: result.message,\n durationMs: getTimeProvider().now() - startTime,\n };\n}\n\n/** Checks a named step's status, returning a check or issue string. */\nfunction checkStepStatus(steps: readonly SetupStep[], name: string, label: string): string {\n const step = steps.find((s) => s.name === name);\n const ok = step?.status === 'success' || step?.status === 'skipped';\n return ok ? `${label} OK` : `${label} failed`;\n}\n\n/**\n * Runs post-setup validation step (#1271).\n * Checks that critical setup outcomes are in place.\n */\nfunction runValidationStep(steps: readonly SetupStep[]): SetupStep {\n const startTime = getTimeProvider().now();\n\n const mcpSteps = steps.filter((s) => s.name.includes('MCP'));\n const mcpFailed = mcpSteps.filter((s) => s.status === 'failed').length;\n const mcpCheck =\n mcpFailed > 0\n ? `${String(mcpFailed)} MCP config(s) failed`\n : `${String(mcpSteps.length)} MCP configs OK`;\n\n const results = [\n mcpCheck,\n checkStepStatus(steps, 'Data Directory', 'Data dirs'),\n checkStepStatus(steps, 'Configuration', 'Config'),\n ];\n const hasIssues = results.some((r) => r.includes('failed'));\n\n return {\n name: 'Validation',\n status: hasIssues ? 'warning' : 'success',\n message: `${results.join(', ')}. Run \\`nexus-agents doctor\\` for full health check`,\n durationMs: getTimeProvider().now() - startTime,\n };\n}\n\n// ============================================================================\n// Main Command Helpers\n// ============================================================================\n\n/**\n * Adds Claude CLI warnings if not installed.\n */\nfunction addClaudeCliWarnings(warnings: string[], installed: boolean): void {\n if (!installed) {\n warnings.push(\n 'Claude CLI not found. Install it with: npm install -g @anthropic-ai/claude-code'\n );\n warnings.push('The MCP snippet uses npx to run nexus-agents (works without global install).');\n }\n}\n\n/**\n * Collects errors from failed steps.\n */\nfunction collectErrors(steps: readonly SetupStep[]): string[] {\n return steps.filter((s) => s.status === 'failed').map((s) => s.message ?? `${s.name} failed`);\n}\n\n/** Result context for building final result. */\ninterface SetupResultContext {\n startTime: number;\n steps: SetupStep[];\n warnings: string[];\n mcpResult: McpConfigResult | undefined;\n snippet: string | undefined;\n hookResult: HookConfigResult | undefined;\n hookSnippet: string | undefined;\n rulesPath: string | undefined;\n dataDirResult?: DataDirInitResult;\n}\n\n/** Checks if a config result represents a new (non-existing) success. */\nfunction isNewSuccess(result: McpConfigResult | HookConfigResult | undefined): boolean {\n return result?.success === true && !result.alreadyConfigured;\n}\n\n/** Builds the final setup result from context. */\nfunction buildSetupResult(ctx: SetupResultContext): SetupResult {\n const errors = collectErrors(ctx.steps);\n return {\n success: errors.length === 0,\n steps: ctx.steps,\n warnings: ctx.warnings,\n errors,\n durationMs: getTimeProvider().now() - ctx.startTime,\n ...(isNewSuccess(ctx.mcpResult) && { mcpConfigured: true }),\n ...(ctx.snippet !== undefined && { mcpSnippet: ctx.snippet }),\n ...(isNewSuccess(ctx.hookResult) && { hooksConfigured: true }),\n ...(ctx.hookSnippet !== undefined && { hookSnippet: ctx.hookSnippet }),\n ...(ctx.rulesPath !== undefined && { rulesPath: ctx.rulesPath }),\n ...(ctx.dataDirResult !== undefined && {\n dataDirPath: ctx.dataDirResult.rootPath,\n dataDirsCreated: ctx.dataDirResult.created.length,\n }),\n };\n}\n\n// ============================================================================\n// Main Command\n// ============================================================================\n\n/**\n * Runs the setup command.\n */\nexport function runSetup(options: Partial<SetupOptions> = {}): SetupResult {\n const startTime = getTimeProvider().now();\n const parsedOptions = SetupOptionsSchema.parse(options);\n const projectRoot = process.cwd();\n\n const warnings: string[] = [];\n\n // Step 1: Environment Detection\n const { env, step: detectionStep } = runDetectionStep(projectRoot);\n addClaudeCliWarnings(warnings, env.claudeCli.installed);\n\n // Step 2: Prerequisite Validation\n const { step: prereqStep, warnings: prereqWarnings } = runPrerequisiteStep();\n warnings.push(...prereqWarnings);\n\n // Step 3: MCP Configuration\n const { step: mcpStep, snippet, mcpResult } = runMcpConfigStep(env, parsedOptions);\n\n // Step 4: Rules File\n const { step: rulesStep, rulesPath } = runRulesStep(env, parsedOptions);\n\n // Step 5: Hooks Configuration (Issue #416)\n const { step: hooksStep, hookSnippet, hookResult } = runHooksStep(env, parsedOptions);\n\n // Step 6: Data Directory Initialization (#1249)\n const { step: dataDirStep, result: dataDirResult } = runDataDirStep(parsedOptions);\n\n // Step 7: OpenCode MCP Configuration (#1253)\n const openCodeStep = runOpenCodeStep(parsedOptions);\n\n // Step 8: Gemini MCP Configuration (#1259)\n const geminiStep = runGeminiStep(parsedOptions);\n\n // Step 9: Codex MCP Configuration (#1263)\n const codexStep = runCodexStep(parsedOptions);\n\n const configStep = runConfigStep(projectRoot, parsedOptions); // Step 10\n const steps = [\n detectionStep,\n prereqStep,\n mcpStep,\n rulesStep,\n hooksStep,\n dataDirStep,\n openCodeStep,\n geminiStep,\n codexStep,\n configStep,\n ];\n steps.push(runValidationStep(steps)); // Step 11: Validation (#1271)\n\n return buildSetupResult({\n startTime,\n steps,\n warnings,\n mcpResult,\n snippet,\n hookResult,\n hookSnippet,\n rulesPath,\n dataDirResult,\n });\n}\n\n/** Prints optional detail sections (MCP, hooks, rules, data dir). */\nfunction printDetailSections(result: SetupResult): void {\n if (result.mcpSnippet !== undefined || result.mcpConfigured === true) {\n const mcpResult: McpConfigResult =\n result.mcpConfigured === true\n ? {\n success: true,\n alreadyConfigured: false,\n message: 'Added nexus-agents MCP server to Claude Code',\n }\n : { success: false, alreadyConfigured: false, message: 'Manual configuration required' };\n printMcpResult(mcpResult, result.mcpSnippet);\n }\n if (result.hookSnippet !== undefined || result.hooksConfigured === true) {\n const hookResult: HookConfigResult =\n result.hooksConfigured === true\n ? {\n success: true,\n alreadyConfigured: false,\n message: 'Configured nexus-agents hooks in Claude Code settings',\n }\n : { success: false, alreadyConfigured: false, message: 'Manual configuration required' };\n printHooksResult(hookResult, result.hookSnippet);\n }\n if (result.rulesPath !== undefined) printRulesFile(result.rulesPath);\n if (result.dataDirPath !== undefined) printDataDirSection(result);\n printPermissionsSuggestion();\n}\n\n/** Prints the Claude Code permissions suggestion (#1945). */\nfunction printPermissionsSuggestion(): void {\n const snippet = generatePermissionsSnippet('all');\n const banner = buildPermissionsBanner(snippet);\n writeLine(banner);\n}\n\n/** Prints the data directory section. */\nfunction printDataDirSection(result: SetupResult): void {\n writeLine(formatHeader('Data Directory'));\n writeLine('─'.repeat(40));\n const count = result.dataDirsCreated ?? 0;\n const msg =\n count > 0\n ? `Created ${String(count)} directories under ${result.dataDirPath ?? ''}`\n : `All directories already exist at ${result.dataDirPath ?? ''}`;\n writeLine(msg);\n writeEmptyLine();\n}\n\n/**\n * Prints the setup result.\n */\nexport function printSetupResult(result: SetupResult, verbose: boolean): void {\n writeEmptyLine();\n writeLine(formatHeader(`Nexus Agents Setup v${VERSION}`));\n writeLine('═'.repeat(40));\n writeEmptyLine();\n\n printSteps(result.steps, verbose);\n printDetailSections(result);\n\n if (result.warnings.length > 0) printWarnings(result.warnings);\n if (result.errors.length > 0) printErrors(result.errors);\n printNextSteps(result.mcpConfigured === true, result.mcpSnippet !== undefined);\n printSummary(result.success);\n}\n\n/**\n * Prints the post-setup \"Getting Started\" banner (#2138).\n *\n * Three numbered steps tailored to what setup actually configured. If Claude\n * Code's MCP wiring succeeded we point step 2 at the integrated harness\n * (`/mcp` in Claude); otherwise we suggest the standalone `orchestrate`\n * command. Always shown after a successful setup — printing 3 lines is not\n * worth gating on first-run detection.\n *\n * @param mcpConfigured - True when an MCP harness (Claude/etc.) was wired up.\n */\nfunction printGettingStartedBanner(mcpConfigured: boolean): void {\n writeEmptyLine();\n writeLine(formatHeader('Getting started'));\n writeLine('─'.repeat(40));\n\n writeLine(' 1. nexus-agents hello — guided tour (no API keys needed)');\n if (mcpConfigured) {\n writeLine(' 2. Use through Claude Code — type /mcp in Claude to list tools');\n } else {\n writeLine(' 2. nexus-agents orchestrate \"...\" — run your first task');\n }\n writeLine(' 3. nexus-agents workflow list — explore built-in workflows');\n\n writeEmptyLine();\n writeLine(` ${colors.dim}Docs: https://github.com/nexus-substrate/nexus-agents${colors.reset}`);\n writeLine(` ${colors.dim}Harness wiring: docs/guides/HARNESS_COMPATIBILITY.md${colors.reset}`);\n}\n\n/**\n * Ensures `.nexus-agents/` is gitignored in the current repo and prints a\n * first-run hint about the per-repo / cross-repo data layout (#2891).\n *\n * Runs once at the end of setup. The auto-gitignore also fires lazily on\n * the first `getNexusRepoDir()` call, but doing it here means the setup\n * output explicitly tells the user where state will live — they don't\n * have to run a workflow first to discover it. Skipped on a dry-run\n * (nothing was installed) and when not inside a git repo.\n */\nexport function ensureRepoGitignoredAndHint(dryRun: boolean): void {\n if (dryRun) return;\n const repoRoot = findRepoRoot(process.cwd());\n if (repoRoot === null) return;\n\n ensureGitignored(repoRoot, '.nexus-agents/');\n\n writeEmptyLine();\n writeLine(formatHeader('Data layout'));\n writeLine('─'.repeat(40));\n writeLine(\n ` Per-repo state (sessions, traces, runs, audit) → ${colors.dim}.nexus-agents/${colors.reset}`\n );\n writeLine(\n ` Cross-project state (routing memory, model registry) → ${colors.dim}~/.nexus-agents/${colors.reset}`\n );\n writeLine(\n ` ${colors.dim}.nexus-agents/ is gitignored. Run 'nexus-agents doctor' for the full layout.${colors.reset}`\n );\n}\n\n/**\n * Runs the post-setup health gate (#2137).\n *\n * After setup writes its files, this runs the verify checks and prints a\n * structured health summary inline. Returns `true` when no `severity: 'hard'`\n * checks failed — warnings still pass the gate.\n *\n * In `--dry-run` mode, the gate is skipped entirely (the user is previewing,\n * not actually installing).\n */\nasync function runPostSetupHealthGate(dryRun: boolean): Promise<boolean> {\n if (dryRun) return true;\n\n const result = await runVerify();\n const passed = result.checks.filter((c) => c.passed).length;\n const total = result.checks.length;\n\n writeEmptyLine();\n writeLine(formatHeader(`Health check (${String(passed)}/${String(total)} passed)`));\n writeLine('─'.repeat(40));\n\n for (const check of result.checks) {\n let symbol: string;\n if (check.passed) {\n symbol = `${colors.green}${symbols.check}${colors.reset}`;\n } else if (check.severity === 'warn') {\n symbol = `${colors.yellow}${symbols.warn}${colors.reset}`;\n } else {\n symbol = `${colors.red}${symbols.cross}${colors.reset}`;\n }\n writeLine(` ${symbol} ${check.name}: ${check.message}`);\n if (!check.passed && check.fix !== undefined) {\n writeLine(` ${colors.dim}→ Fix: ${check.fix}${colors.reset}`);\n }\n }\n\n writeEmptyLine();\n if (!result.noHardFailures) {\n writeLine(\n `${colors.red}${colors.bold}Action required: fix the blocking issues above before using nexus-agents.${colors.reset}`\n );\n } else if (!result.allPassed) {\n const warnCount = result.checks.filter((c) => !c.passed).length;\n writeLine(\n `${colors.yellow}${colors.bold}Setup complete with ${String(warnCount)} warning(s) — nexus-agents will run but some features are degraded.${colors.reset}`\n );\n } else {\n writeLine(\n `${colors.green}${colors.bold}All health checks passed. nexus-agents is ready.${colors.reset}`\n );\n }\n\n return result.noHardFailures;\n}\n\n/**\n * Setup command entry point (synchronous, non-interactive).\n *\n * @returns Exit code (0 = success, 1 = failure)\n */\nexport function setupCommand(options: Partial<SetupOptions> = {}): number {\n const parsedOptions = SetupOptionsSchema.parse(options);\n\n // Check for non-interactive mode in CI\n if (!isInteractive() && !parsedOptions.nonInteractive) {\n writeLine('Non-interactive environment detected.');\n writeLine('Run with --non-interactive or set CI=true.');\n return 1;\n }\n\n const result = runSetup(options);\n printSetupResult(result, parsedOptions.verbose);\n\n return result.success ? 0 : 1;\n}\n\n/** Extended options including interactive flag. */\nexport interface SetupCommandOptions extends Partial<SetupOptions> {\n interactive?: boolean;\n}\n\n/**\n * Setup command entry point with interactive wizard support.\n * (Source: Issue #425 - Interactive setup wizard)\n *\n * Also runs the post-setup health gate (#2137): after the configuration\n * steps complete, calls into `runVerify()` to surface install-time issues\n * that are easy to miss but break things at runtime (better-sqlite3 native\n * build, data dir writability, missing API keys). Health-gate warnings do\n * NOT fail setup — only `severity: 'hard'` failures do.\n *\n * @returns Exit code (0 = setup + no hard health failures, 1 = either failed)\n */\n/**\n * Runs the interactive wizard branch and returns its exit code.\n * Extracted from `setupCommandAsync` to keep cyclomatic complexity ≤10.\n */\nasync function runInteractiveSetup(options: SetupCommandOptions): Promise<number> {\n const wizardOptions = await runWizard();\n if (wizardOptions === undefined) return 1; // User cancelled.\n\n const mergedOptions = { ...options, ...wizardOptions };\n delete mergedOptions.interactive;\n\n const result = runSetup(mergedOptions);\n printSetupResult(result, mergedOptions.verbose ?? false);\n const healthOk = await runPostSetupHealthGate(mergedOptions.dryRun ?? false);\n if (result.success && !(mergedOptions.dryRun ?? false)) {\n // Inside the !dryRun branch — pass false explicitly.\n ensureRepoGitignoredAndHint(false);\n printGettingStartedBanner(result.mcpConfigured === true);\n }\n return result.success && healthOk ? 0 : 1;\n}\n\nexport async function setupCommandAsync(options: SetupCommandOptions = {}): Promise<number> {\n if (options.interactive === true) {\n return runInteractiveSetup(options);\n }\n\n // Sync setup, then run the health gate, then print the getting-started banner.\n const setupResult = runSetupAndPrint(options);\n const healthOk = await runPostSetupHealthGate(options.dryRun ?? false);\n if (setupResult.exitCode === 0 && !(options.dryRun ?? false)) {\n // Inside the !dryRun branch — pass false explicitly.\n ensureRepoGitignoredAndHint(false);\n printGettingStartedBanner(setupResult.mcpConfigured);\n }\n // Hard health failures override a successful setup; warnings don't.\n return setupResult.exitCode !== 0 || !healthOk ? 1 : 0;\n}\n\n/**\n * Runs setupCommand and captures both the exit code and the\n * `mcpConfigured` signal we need for the banner. Wraps the existing\n * synchronous path without changing its signature.\n */\nfunction runSetupAndPrint(options: SetupCommandOptions): {\n exitCode: number;\n mcpConfigured: boolean;\n} {\n const parsedOptions = SetupOptionsSchema.parse(options);\n if (!isInteractive() && !parsedOptions.nonInteractive) {\n writeLine('Non-interactive environment detected.');\n writeLine('Run with --non-interactive or set CI=true.');\n return { exitCode: 1, mcpConfigured: false };\n }\n const result = runSetup(options);\n printSetupResult(result, parsedOptions.verbose);\n return { exitCode: result.success ? 0 : 1, mcpConfigured: result.mcpConfigured === true };\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport { generateMcpSnippet, generateRulesContent, detectEnvironment };\nexport { runWizard } from './setup-wizard.js';\nexport type { SetupOptions, SetupResult };\n// SetupCommandOptions is already exported via interface definition above\nexport type { WizardAnswers, UsageMode } from './setup-wizard.js';\n","/**\n * nexus-agents setup command types\n *\n * Type definitions for the user onboarding automation system.\n *\n * @module cli/setup-types\n * (Source: Issue #363 - Auto-configure Claude CLI integration)\n */\n\nimport { z } from 'zod';\nimport type { CommandResult } from '../core/index.js';\n\n/**\n * Setup command options schema.\n */\nexport const SetupOptionsSchema = z.object({\n /** Skip interactive prompts */\n nonInteractive: z.boolean().default(false),\n /** Overwrite existing configurations */\n force: z.boolean().default(false),\n /** Skip MCP configuration */\n skipMcp: z.boolean().default(false),\n /** Skip CLAUDE.md/rules generation */\n skipRules: z.boolean().default(false),\n /** Skip hooks configuration (Issue #416) */\n skipHooks: z.boolean().default(false),\n /** Skip config file generation (#1252) */\n skipConfig: z.boolean().default(false),\n /** Skip OpenCode MCP configuration (#1253) */\n skipOpencode: z.boolean().default(false),\n /** Skip Gemini CLI MCP configuration (#1259) */\n skipGemini: z.boolean().default(false),\n /** Skip Codex CLI MCP configuration (#1263) */\n skipCodex: z.boolean().default(false),\n /** Show what would be done without making changes */\n dryRun: z.boolean().default(false),\n /** Show detailed output */\n verbose: z.boolean().default(false),\n /** Target scope for MCP config */\n scope: z.enum(['user', 'project']).default('user'),\n});\n\nexport type SetupOptions = z.infer<typeof SetupOptionsSchema>;\n\n/**\n * Claude CLI detection result.\n */\nexport interface ClaudeCliInfo {\n readonly installed: boolean;\n readonly version: string | undefined;\n readonly configPath: string;\n readonly mcpJsonPath: string;\n}\n\n/**\n * Existing MCP configuration info.\n */\nexport interface McpConfigInfo {\n readonly exists: boolean;\n readonly path: string;\n readonly hasNexusAgents: boolean;\n readonly servers: readonly string[];\n}\n\n/**\n * MCP server configuration entry.\n */\nexport interface McpServerEntry {\n readonly command: string;\n readonly args?: readonly string[];\n readonly env?: Record<string, string>;\n}\n\n/**\n * MCP JSON configuration format.\n */\nexport interface McpJsonConfig {\n readonly mcpServers?: Record<string, McpServerEntry>;\n}\n\n/**\n * Project type detection.\n */\nexport type ProjectType =\n | 'typescript'\n | 'javascript'\n | 'python'\n | 'rust'\n | 'go'\n | 'java'\n | 'unknown';\n\n/**\n * Project information.\n */\nexport interface ProjectInfo {\n readonly root: string;\n readonly hasPackageJson: boolean;\n readonly hasClaudeMd: boolean;\n readonly hasClaudeRules: boolean;\n readonly hasNexusConfig: boolean;\n readonly projectType: ProjectType;\n readonly packageName?: string;\n}\n\n/**\n * Complete environment information.\n */\nexport interface EnvironmentInfo {\n readonly platform: NodeJS.Platform;\n readonly homeDir: string;\n readonly claudeCli: ClaudeCliInfo;\n readonly existingMcpConfig: McpConfigInfo | undefined;\n readonly projectInfo: ProjectInfo;\n}\n\n/**\n * Setup step status.\n */\nexport interface SetupStep {\n readonly name: string;\n readonly status: 'pending' | 'success' | 'skipped' | 'failed' | 'warning';\n readonly message?: string;\n readonly durationMs?: number;\n}\n\n/**\n * Setup result summary.\n * Extends CommandResult base pattern (Issue #584).\n */\nexport interface SetupResult extends CommandResult {\n /** Setup steps executed */\n readonly steps: readonly SetupStep[];\n /** MCP configuration was successful via Claude CLI */\n readonly mcpConfigured?: boolean;\n /** Fallback MCP snippet for manual configuration */\n readonly mcpSnippet?: string;\n /** Hooks configuration was successful via Claude CLI (Issue #416) */\n readonly hooksConfigured?: boolean;\n /** Fallback hook snippet for manual configuration */\n readonly hookSnippet?: string;\n readonly rulesPath?: string;\n /** Data directory path if initialized (#1249) */\n readonly dataDirPath?: string;\n /** Number of data directories created (#1249) */\n readonly dataDirsCreated?: number;\n readonly warnings: readonly string[];\n readonly errors: readonly string[];\n readonly durationMs: number;\n}\n\n/**\n * Backup information for rollback.\n */\nexport interface BackupInfo {\n readonly type: 'file';\n readonly originalPath: string;\n readonly backupPath: string;\n readonly content: string;\n}\n\n/**\n * Rollback item types.\n */\nexport type RollbackItem =\n | { type: 'file-backup'; backup: BackupInfo }\n | { type: 'file-created'; path: string }\n | { type: 'file-modified'; backup: BackupInfo };\n","/**\n * nexus-agents setup environment detection\n *\n * Environment detection helpers for Claude CLI and project setup.\n *\n * @module cli/setup-environment\n * (Source: Issue #363 - Auto-configure Claude CLI integration)\n */\n\nimport { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, basename } from 'node:path';\nimport { CLI_SUBPROCESS_TIMEOUTS } from '../config/timeouts.js';\nimport type {\n ClaudeCliInfo,\n McpConfigInfo,\n ProjectInfo,\n ProjectType,\n EnvironmentInfo,\n} from './setup-types.js';\n\n/**\n * Parses Claude CLI version from output.\n */\nfunction parseClaudeVersion(output: string): string | undefined {\n const match = output.match(/(\\d+\\.\\d+\\.\\d+)/);\n return match?.[1];\n}\n\n/**\n * Detects if Claude CLI is installed and available.\n * Uses a 3-second timeout to avoid hanging in slow environments.\n */\nexport function detectClaudeCli(): ClaudeCliInfo {\n const configPath = join(homedir(), '.claude');\n const mcpJsonPath = join(homedir(), '.claude.json');\n\n try {\n const result = execSync('claude --version', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n timeout: CLI_SUBPROCESS_TIMEOUTS.envSetupMs,\n });\n const version = parseClaudeVersion(result);\n\n return {\n installed: true,\n version,\n configPath,\n mcpJsonPath,\n };\n } catch {\n return {\n installed: false,\n version: undefined,\n configPath,\n mcpJsonPath,\n };\n }\n}\n\n/** Extracts MCP servers from direct mcpServers key (project-scoped .mcp.json). */\nfunction extractDirectServers(config: Record<string, unknown>): string[] | undefined {\n const mcpServers = config['mcpServers'] as Record<string, unknown> | undefined;\n if (mcpServers === undefined) return undefined;\n return Object.keys(mcpServers);\n}\n\n/** Extracts MCP servers from projects key (~/.claude.json format). */\nfunction extractProjectServers(config: Record<string, unknown>): string[] | undefined {\n const projects = config['projects'] as Record<string, Record<string, unknown>> | undefined;\n if (projects === undefined) return undefined;\n const allServers = new Set<string>();\n for (const proj of Object.values(projects)) {\n const mcpServers = proj['mcpServers'] as Record<string, unknown> | undefined;\n if (mcpServers !== undefined) Object.keys(mcpServers).forEach((n) => allServers.add(n));\n }\n return [...allServers];\n}\n\n/** Builds McpConfigInfo from a server list. */\nfunction buildMcpInfo(path: string, servers: string[]): McpConfigInfo {\n return { exists: true, path, hasNexusAgents: servers.includes('nexus-agents'), servers };\n}\n\n/**\n * Detects existing MCP configuration.\n *\n * Handles two formats:\n * - `.mcp.json` (project-scoped): `{ mcpServers: { ... } }`\n * - `~/.claude.json` (user-scoped): `{ projects: { [path]: { mcpServers: { ... } } } }`\n */\nexport function detectMcpConfig(mcpJsonPath: string): McpConfigInfo | undefined {\n if (!existsSync(mcpJsonPath)) return undefined;\n try {\n const config = JSON.parse(readFileSync(mcpJsonPath, 'utf-8')) as Record<string, unknown>;\n const direct = extractDirectServers(config);\n if (direct !== undefined) return buildMcpInfo(mcpJsonPath, direct);\n const projected = extractProjectServers(config);\n if (projected !== undefined) return buildMcpInfo(mcpJsonPath, projected);\n return buildMcpInfo(mcpJsonPath, []);\n } catch {\n return buildMcpInfo(mcpJsonPath, []);\n }\n}\n\n/** Checks for TypeScript in package.json devDependencies. */\nfunction hasTypeScriptInPackageJson(root: string): boolean {\n try {\n const content = readFileSync(join(root, 'package.json'), 'utf-8');\n const parsed: unknown = JSON.parse(content);\n const pkg = parsed as Record<string, unknown>;\n const devDeps = pkg['devDependencies'] as Record<string, string> | undefined;\n return devDeps?.['typescript'] !== undefined;\n } catch {\n return false;\n }\n}\n\n/** Mapping of config files to project types. */\nconst CONFIG_FILE_TYPES: readonly [string, ProjectType][] = [\n ['tsconfig.json', 'typescript'],\n ['Cargo.toml', 'rust'],\n ['go.mod', 'go'],\n ['pyproject.toml', 'python'],\n ['setup.py', 'python'],\n ['pom.xml', 'java'],\n ['build.gradle', 'java'],\n];\n\n/**\n * Detects project type based on configuration files.\n */\nexport function detectProjectType(root: string): ProjectType {\n // Check config files\n for (const [file, type] of CONFIG_FILE_TYPES) {\n if (existsSync(join(root, file))) return type;\n }\n\n // Check package.json\n if (existsSync(join(root, 'package.json'))) {\n return hasTypeScriptInPackageJson(root) ? 'typescript' : 'javascript';\n }\n\n return 'unknown';\n}\n\n/**\n * Detects project information.\n */\nexport function detectProjectInfo(root: string): ProjectInfo {\n let packageName: string | undefined;\n\n if (existsSync(join(root, 'package.json'))) {\n try {\n const content = readFileSync(join(root, 'package.json'), 'utf-8');\n const pkg = JSON.parse(content) as Record<string, unknown>;\n const nameValue = pkg['name'];\n packageName = typeof nameValue === 'string' ? nameValue : undefined;\n } catch (parseErr: unknown) {\n // Non-critical: package.json parse failure, fall through to basename\n void parseErr;\n }\n }\n\n return {\n root,\n hasPackageJson: existsSync(join(root, 'package.json')),\n hasClaudeMd: existsSync(join(root, 'CLAUDE.md')),\n // Rules relocated in #2121: canonical is `.rules/`. Still accept the\n // legacy `.claude/rules/` path so detection doesn't break on repos that\n // haven't migrated yet.\n hasClaudeRules: existsSync(join(root, '.rules')) || existsSync(join(root, '.claude', 'rules')),\n // Check dotdir-scoped location first (epic #2872), then legacy root.\n hasNexusConfig:\n existsSync(join(root, '.nexus-agents', 'nexus-agents.yaml')) ||\n existsSync(join(root, 'nexus-agents.yaml')),\n projectType: detectProjectType(root),\n packageName: packageName ?? basename(root),\n };\n}\n\n/**\n * Detects complete environment information.\n */\nexport function detectEnvironment(projectRoot: string): EnvironmentInfo {\n const claudeCli = detectClaudeCli();\n const existingMcpConfig = detectMcpConfig(claudeCli.mcpJsonPath);\n const projectInfo = detectProjectInfo(projectRoot);\n\n return {\n platform: process.platform,\n homeDir: homedir(),\n claudeCli,\n existingMcpConfig,\n projectInfo,\n };\n}\n","/**\n * nexus-agents setup MCP configuration\n *\n * MCP configuration helpers using Claude CLI's `claude mcp` commands.\n *\n * @module cli/setup-mcp\n * (Source: Issue #363 - Auto-configure Claude CLI integration)\n */\n\nimport { execSync, execFileSync } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport type { McpJsonConfig, McpServerEntry } from './setup-types.js';\nimport { getErrorMessage } from '../core/index.js';\n\n/** MCP entry for nexus-agents */\nexport const NEXUS_AGENTS_MCP_ENTRY: McpServerEntry = {\n command: 'nexus-agents',\n args: ['--mode=server'],\n};\n\n/** MCP entry with npx for users who install globally */\nexport const NEXUS_AGENTS_MCP_NPX_ENTRY: McpServerEntry = {\n command: 'npx',\n args: ['-y', 'nexus-agents@latest', '--mode=server'],\n};\n\n/**\n * Result of MCP configuration attempt.\n */\nexport interface McpConfigResult {\n success: boolean;\n alreadyConfigured: boolean;\n message: string;\n}\n\n/**\n * Checks if nexus-agents MCP server is already configured in Claude CLI.\n */\nexport function isMcpServerConfigured(): boolean {\n try {\n const result = execSync('claude mcp get nexus-agents', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return result.includes('nexus-agents');\n } catch {\n return false;\n }\n}\n\n/**\n * Maps scope option to Claude CLI `-s` flag value.\n */\nfunction scopeToFlag(scope: 'user' | 'project'): string {\n return scope === 'project' ? 'local' : 'user';\n}\n\n/**\n * Removes existing MCP server configuration if present.\n */\nfunction removeExistingMcpServer(scope: 'user' | 'project' = 'user'): void {\n try {\n execSync(`claude mcp remove nexus-agents -s ${scopeToFlag(scope)}`, {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch {\n // Ignore removal errors\n }\n}\n\n/**\n * Adds MCP server to Claude CLI.\n */\nfunction addMcpServer(useNpx: boolean, scope: 'user' | 'project' = 'user'): McpConfigResult {\n const entry = useNpx ? NEXUS_AGENTS_MCP_NPX_ENTRY : NEXUS_AGENTS_MCP_ENTRY;\n const jsonConfig = JSON.stringify(entry);\n const scopeLabel = scope === 'project' ? 'project' : 'global';\n\n try {\n execFileSync(\n 'claude',\n ['mcp', 'add-json', 'nexus-agents', jsonConfig, '-s', scopeToFlag(scope)],\n {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n }\n );\n return {\n success: true,\n alreadyConfigured: false,\n message: `Added nexus-agents MCP server to Claude Code (${scopeLabel})`,\n };\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n return {\n success: false,\n alreadyConfigured: false,\n message: `Failed to add MCP server: ${errorMsg}`,\n };\n }\n}\n\n/**\n * Configures nexus-agents MCP server using Claude CLI.\n *\n * Uses `claude mcp add-json` to register the server.\n * @param useNpx - Register via `npx -y nexus-agents` instead of a global binary\n * @param force - Overwrite an existing nexus-agents MCP server entry\n * @param scope - 'user' for global (~/.claude/mcp.json), 'project' for local (.mcp.json)\n */\nexport function configureMcpServer(\n useNpx: boolean = false,\n force: boolean = false,\n scope: 'user' | 'project' = 'user'\n): McpConfigResult {\n const isConfigured = isMcpServerConfigured();\n\n // Check if already configured\n if (!force && isConfigured) {\n return {\n success: true,\n alreadyConfigured: true,\n message: 'nexus-agents MCP server already configured (use --force to reconfigure)',\n };\n }\n\n // Remove existing if forcing\n if (force && isConfigured) {\n removeExistingMcpServer(scope);\n }\n\n return addMcpServer(useNpx, scope);\n}\n\n/**\n * Generates MCP configuration snippet for manual setup (fallback).\n */\nexport function generateMcpSnippet(useNpx: boolean = false): string {\n const entry = useNpx ? NEXUS_AGENTS_MCP_NPX_ENTRY : NEXUS_AGENTS_MCP_ENTRY;\n const config: McpJsonConfig = {\n mcpServers: {\n 'nexus-agents': entry,\n },\n };\n\n return JSON.stringify(config, null, 2);\n}\n\n/**\n * Generates the full MCP config path based on scope.\n *\n * Claude Code uses:\n * - Project scope: `.mcp.json` in project root\n * - User scope: `~/.claude.json` (stores MCP config under `projects` key)\n */\nexport function getMcpJsonPath(scope: 'user' | 'project', projectRoot: string): string {\n if (scope === 'project') {\n return join(projectRoot, '.mcp.json');\n }\n return join(homedir(), '.claude.json');\n}\n\n// ============================================================================\n// Hook Configuration (Issue #411, #416)\n// ============================================================================\n\n/**\n * Generates hook configuration for Claude CLI settings.json.\n * (Source: Issue #411 - Claude CLI Hook Integration)\n */\nexport function generateHookConfig(): HookSettingsConfig {\n return {\n hooks: {\n SessionStart: [\n {\n hooks: [\n {\n type: 'command',\n command: 'nexus-agents hooks session-start',\n },\n ],\n },\n ],\n PreToolUse: [\n {\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: 'nexus-agents hooks pre-tool --tool Bash --validate',\n },\n ],\n },\n ],\n PostToolUse: [\n {\n matcher: '*',\n hooks: [\n {\n type: 'command',\n command: 'nexus-agents hooks post-tool --track-metrics',\n },\n ],\n },\n ],\n Stop: [\n {\n hooks: [\n {\n type: 'command',\n command: 'nexus-agents hooks stop --check-tasks',\n },\n ],\n },\n ],\n },\n };\n}\n\n/**\n * Hook command entry structure.\n */\ninterface HookCommandEntry {\n type: 'command';\n command: string;\n}\n\n/**\n * Hook matcher entry structure.\n */\ninterface HookMatcherEntry {\n matcher?: string;\n hooks: HookCommandEntry[];\n}\n\n/**\n * Hook settings configuration structure.\n */\nexport interface HookSettingsConfig {\n hooks: {\n SessionStart?: HookMatcherEntry[];\n SessionEnd?: HookMatcherEntry[];\n PreToolUse?: HookMatcherEntry[];\n PostToolUse?: HookMatcherEntry[];\n Stop?: HookMatcherEntry[];\n };\n}\n\n/**\n * Result of hook configuration attempt.\n */\nexport interface HookConfigResult {\n success: boolean;\n alreadyConfigured: boolean;\n message: string;\n}\n\n/**\n * Checks if hooks are already configured in Claude CLI settings.\n */\nexport function areHooksConfigured(): boolean {\n try {\n const result = execSync('claude config get hooks', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n // If we get output that includes nexus-agents, hooks are configured\n return result.includes('nexus-agents');\n } catch {\n return false;\n }\n}\n\n/**\n * Detailed result of reading existing hooks. Distinguishes \"no hooks set\" from\n * \"the claude CLI returned content we couldn't parse\" — they look the same to\n * `getExistingHooks()` (both undefined) but mean very different things to the\n * caller. Closes #2975: collapsing both into undefined caused `configureHooks`\n * to silently overwrite the user's existing hooks when the claude CLI's JSON\n * shape drifted (regressed #420 on the parse-failure path).\n */\nexport type ReadHooksResult =\n | { kind: 'absent' }\n | { kind: 'present'; hooks: HookSettingsConfig['hooks'] }\n | { kind: 'unreadable'; reason: string }\n | { kind: 'parse_failed'; reason: string; raw: string };\n\n/**\n * Reads existing hooks from Claude CLI settings with full result detail.\n * Callers should branch on `kind` before deciding whether to merge or abort.\n */\nexport function readExistingHooks(): ReadHooksResult {\n let raw: string;\n try {\n raw = execSync('claude config get hooks', {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n } catch (error) {\n return { kind: 'unreadable', reason: getErrorMessage(error) };\n }\n const trimmed = raw.trim();\n if (!trimmed || trimmed === 'null' || trimmed === 'undefined') {\n return { kind: 'absent' };\n }\n try {\n return { kind: 'present', hooks: JSON.parse(trimmed) as HookSettingsConfig['hooks'] };\n } catch (error) {\n return { kind: 'parse_failed', reason: getErrorMessage(error), raw: trimmed };\n }\n}\n\n/**\n * Reads existing hooks from Claude CLI settings.\n *\n * Returns parsed hooks object or undefined if no hooks exist, the CLI call\n * fails, or the response cannot be parsed. Loses the distinction between\n * those cases — for the merge-vs-abort decision in `configureHooks` use\n * `readExistingHooks()` directly. Kept for backward compatibility.\n */\nexport function getExistingHooks(): HookSettingsConfig['hooks'] | undefined {\n const result = readExistingHooks();\n return result.kind === 'present' ? result.hooks : undefined;\n}\n\n/**\n * Merges two hook arrays, combining entries without duplicating nexus-agents hooks.\n */\nfunction mergeHookArrays(\n existing: HookMatcherEntry[] | undefined,\n newHooks: HookMatcherEntry[]\n): HookMatcherEntry[] {\n if (!existing || existing.length === 0) {\n return newHooks;\n }\n\n // Filter out any existing nexus-agents hooks to avoid duplicates\n const filteredExisting = existing.filter((entry) => {\n return !entry.hooks.some((h) => h.command.startsWith('nexus-agents'));\n });\n\n // Combine existing (non-nexus) hooks with new nexus-agents hooks\n return [...filteredExisting, ...newHooks];\n}\n\n/**\n * Merges nexus-agents hooks with existing hooks configuration.\n * Preserves existing user hooks while adding/updating nexus-agents hooks.\n */\nexport function mergeHookConfigs(\n existing: HookSettingsConfig['hooks'] | undefined,\n newConfig: HookSettingsConfig['hooks']\n): HookSettingsConfig['hooks'] {\n if (!existing) {\n return newConfig;\n }\n\n const hookTypes = ['SessionStart', 'SessionEnd', 'PreToolUse', 'PostToolUse', 'Stop'] as const;\n\n const merged: HookSettingsConfig['hooks'] = {};\n\n for (const hookType of hookTypes) {\n const existingHooks = existing[hookType];\n const newHooks = newConfig[hookType];\n\n if (newHooks) {\n merged[hookType] = mergeHookArrays(existingHooks, newHooks);\n } else if (existingHooks) {\n merged[hookType] = existingHooks;\n }\n }\n\n return merged;\n}\n\n/**\n * Configures hooks in Claude CLI settings.\n * Uses `claude config set hooks` to register hook commands.\n * Merges with existing hooks instead of overwriting them (Issue #420).\n */\nexport function configureHooks(force: boolean = false): HookConfigResult {\n const isConfigured = areHooksConfigured();\n\n if (!force && isConfigured) {\n return {\n success: true,\n alreadyConfigured: true,\n message: 'Hooks already configured (use --force to reconfigure)',\n };\n }\n\n const nexusHookConfig = generateHookConfig();\n\n // Read existing hooks first to merge (Issue #420). Closes #2975: on\n // parse_failed we MUST NOT proceed — silently overwriting an unparseable\n // response is exactly how user hooks got wiped after claude-cli JSON-shape\n // drifts. Surface the problem so the operator can fix the underlying\n // condition (or pass --force after backing up their hooks).\n const existing = readExistingHooks();\n if (existing.kind === 'parse_failed') {\n return {\n success: false,\n alreadyConfigured: false,\n message:\n 'Refusing to configure hooks: existing hooks could not be parsed. ' +\n 'Overwriting would wipe your current settings. ' +\n `Inspect with \\`claude config get hooks\\` and resolve manually. (parse error: ${existing.reason})`,\n };\n }\n const existingHooks = existing.kind === 'present' ? existing.hooks : undefined;\n\n try {\n const mergedHooks = mergeHookConfigs(existingHooks, nexusHookConfig.hooks);\n\n // Use claude config set with merged hooks\n const configJson = JSON.stringify(mergedHooks);\n execFileSync('claude', ['config', 'set', 'hooks', configJson], {\n encoding: 'utf-8',\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n return {\n success: true,\n alreadyConfigured: false,\n message: existingHooks\n ? 'Merged nexus-agents hooks with existing hooks in Claude Code settings'\n : 'Configured nexus-agents hooks in Claude Code settings',\n };\n } catch (error) {\n const errorMsg = getErrorMessage(error);\n return {\n success: false,\n alreadyConfigured: false,\n message: `Failed to configure hooks: ${errorMsg}`,\n };\n }\n}\n\n/**\n * Generates hook configuration snippet for manual setup.\n */\nexport function generateHookSnippet(): string {\n const config = generateHookConfig();\n return JSON.stringify(config, null, 2);\n}\n","/**\n * nexus-agents setup rules generation\n *\n * Rules file and backup management helpers.\n *\n * @module cli/setup-rules\n * (Source: Issue #363 - Auto-configure Claude CLI integration)\n */\n\nimport { readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { VERSION } from '../version.js';\nimport { getTimeProvider } from '../core/index.js';\nimport type { BackupInfo } from './setup-types.js';\n\n/**\n * Generates the nexus-agents rules file content.\n */\nexport function generateRulesContent(): string {\n return `# Nexus-Agents Integration\n\nThis project uses [nexus-agents](https://github.com/nexus-substrate/nexus-agents) v${VERSION} for multi-agent orchestration.\n\n## MCP Tools Available\n\nWhen running with MCP server mode, these tools are available:\n\n| Tool | Description |\n| ---- | ----------- |\n| \\`orchestrate\\` | Task orchestration with Orchestrator coordination |\n| \\`create_expert\\` | Dynamic expert agent creation |\n| \\`run_workflow\\` | Execute workflow templates |\n| \\`delegate_to_model\\` | Route task to optimal model |\n| \\`list_experts\\` | List available expert types (discoverability) |\n| \\`list_workflows\\` | List available workflow templates |\n\n## Quick Commands\n\n\\`\\`\\`bash\n# Orchestrate a task with specialized experts\nnexus-agents orchestrate \"Implement feature X with tests\"\n\n# List available experts\nnexus-agents expert list\n\n# Run a workflow\nnexus-agents workflow list\nnexus-agents workflow run code-review --input='{\"url\": \"...\"}'\n\n# Check system health\nnexus-agents doctor\n\n# Generate config\nnexus-agents config init\n\\`\\`\\`\n\n## Usage Examples\n\n**Orchestrate a code review:**\n\\`\\`\\`\nUse nexus-agents to review this PR: https://github.com/owner/repo/pull/123\n\\`\\`\\`\n\n**Create a specialized expert:**\n\\`\\`\\`\nCreate a security expert to audit this codebase for vulnerabilities\n\\`\\`\\`\n\n**Run a workflow:**\n\\`\\`\\`\nRun the code-review workflow with the current changes\n\\`\\`\\`\n\n## Configuration\n\n- Config file: \\`./nexus-agents.yaml\\`\n- Generate config: \\`nexus-agents config init\\`\n- Check health: \\`nexus-agents doctor\\`\n\n---\n*Generated by nexus-agents setup v${VERSION}*\n`;\n}\n\n/**\n * Gets the rules file path.\n *\n * As of #2121 rules live at `<root>/.rules/` (harness-neutral location,\n * was `.claude/rules/`). Claude Code finds them via CLAUDE.md references,\n * so the move doesn't break its auto-load. Non-Claude harnesses (OpenCode,\n * Codex, Cursor, etc.) can point at the same directory.\n */\nexport function getRulesFilePath(projectRoot: string): string {\n return join(projectRoot, '.rules', 'nexus-agents.md');\n}\n\n/**\n * Creates the rules file.\n */\nexport function createRulesFile(projectRoot: string, dryRun: boolean): string {\n const rulesPath = getRulesFilePath(projectRoot);\n const content = generateRulesContent();\n\n if (!dryRun) {\n const rulesDir = dirname(rulesPath);\n mkdirSync(rulesDir, { recursive: true });\n writeFileSync(rulesPath, content, 'utf-8');\n }\n\n return rulesPath;\n}\n\n/**\n * Creates a backup of a file.\n */\nexport function backupFile(filePath: string): BackupInfo {\n const content = readFileSync(filePath, 'utf-8');\n const backupPath = `${filePath}.backup.${String(getTimeProvider().now())}`;\n writeFileSync(backupPath, content, 'utf-8');\n\n return {\n type: 'file',\n originalPath: filePath,\n backupPath,\n content,\n };\n}\n\n/**\n * Restores a file from backup.\n */\nexport function restoreBackup(backup: BackupInfo): void {\n writeFileSync(backup.originalPath, backup.content, 'utf-8');\n}\n","/**\n * nexus-agents setup output formatting\n *\n * Output formatting helpers for setup command.\n * Re-exports from consolidated ansi-output.ts for backward compatibility.\n *\n * @module cli/setup-formatting\n * (Source: Issue #363 - Auto-configure Claude CLI integration)\n */\n\n// Re-export consolidated formatters from ansi-output.ts\nexport { formatStatus, formatHeader, formatCodeBlock, colors, symbols } from './ansi-output.js';\n\n/**\n * Checks if running in interactive mode.\n */\nexport function isInteractive(): boolean {\n if (!process.stdout.isTTY) return false;\n if (process.env['CI'] === 'true') return false;\n if (process.env['CONTINUOUS_INTEGRATION'] !== undefined) return false;\n return true;\n}\n","/**\n * OpenCode MCP auto-configuration for setup command.\n *\n * Detects OpenCode CLI and generates opencode.json/.jsonc with nexus-agents MCP server.\n * Supports JSONC (JSON with Comments) via `jsonc-parser` for comment-preserving writes.\n *\n * @module cli/setup-opencode\n * (Source: Issue #1253 - OpenCode MCP auto-configuration, #1255 - JSONC support)\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { homedir } from 'node:os';\nimport { parse as jsoncParse, modify, applyEdits } from 'jsonc-parser';\nimport { getErrorMessage } from '../core/index.js';\nimport { createLogger } from '../core/index.js';\nimport { detectCliBinary, type CliDetectionInfo } from './setup-cli-detection.js';\n\nconst logger = createLogger({ component: 'setup-opencode' });\n\n/**\n * OpenCode detection result.\n *\n * Type alias of {@link CliDetectionInfo} — see #2155.\n */\nexport type OpenCodeCliInfo = CliDetectionInfo;\n\n/** OpenCode MCP configuration result. */\nexport interface OpenCodeConfigResult {\n readonly success: boolean;\n readonly alreadyConfigured: boolean;\n readonly message: string;\n readonly configPath: string;\n}\n\n/** Resolved config file info. */\nexport interface ResolvedConfig {\n readonly path: string;\n readonly isJsonc: boolean;\n readonly exists: boolean;\n}\n\n/** Detects OpenCode CLI installation. Delegates to {@link detectCliBinary}. */\nexport function detectOpenCodeCli(): OpenCodeCliInfo {\n return detectCliBinary('opencode');\n}\n\n/**\n * Resolves the OpenCode config directory path.\n * OpenCode uses ~/.config/opencode/ on Linux/macOS.\n */\nfunction getOpenCodeConfigDir(): string {\n return join(homedir(), '.config', 'opencode');\n}\n\n/**\n * Resolves the path for nexus-agents CLI command.\n * Uses `npx nexus-agents` for portability.\n */\nfunction getNexusCommand(): readonly string[] {\n return ['npx', 'nexus-agents', '--mode=server'];\n}\n\nconst NEXUS_MCP_ENTRY = {\n type: 'local',\n command: getNexusCommand(),\n enabled: true,\n};\n\n/**\n * Resolves the OpenCode config file in a directory.\n * Prefers .jsonc over .json (matching OpenCode's own priority).\n */\nexport function resolveOpenCodeConfig(dir: string): ResolvedConfig {\n const jsoncPath = join(dir, 'opencode.jsonc');\n if (existsSync(jsoncPath)) {\n return { path: jsoncPath, isJsonc: true, exists: true };\n }\n const jsonPath = join(dir, 'opencode.json');\n if (existsSync(jsonPath)) {\n return { path: jsonPath, isJsonc: false, exists: true };\n }\n return { path: jsonPath, isJsonc: false, exists: false };\n}\n\n/** Checks if nexus-agents is already configured in an existing config. */\nfunction isAlreadyConfigured(resolved: ResolvedConfig): boolean {\n if (!resolved.exists) return false;\n try {\n const raw = readFileSync(resolved.path, 'utf-8');\n const config = jsoncParse(raw) as Record<string, unknown> | null;\n const mcp = config?.['mcp'] as Record<string, unknown> | undefined;\n return mcp?.['nexus-agents'] !== undefined;\n } catch {\n logger.debug('Failed to parse existing OpenCode config, will overwrite');\n return false;\n }\n}\n\n/** Writes nexus-agents MCP entry using comment-preserving edits for JSONC. */\nfunction writeJsoncConfig(configPath: string, raw: string): void {\n let result = raw;\n result = applyEdits(result, modify(result, ['mcp', 'nexus-agents'], NEXUS_MCP_ENTRY, {}));\n result = applyEdits(result, modify(result, ['$schema'], 'https://opencode.ai/config.json', {}));\n writeFileSync(configPath, result, 'utf-8');\n}\n\n/** Writes nexus-agents MCP entry as plain JSON for new configs. */\nfunction writeJsonConfig(configPath: string): void {\n const config = {\n $schema: 'https://opencode.ai/config.json',\n mcp: { 'nexus-agents': NEXUS_MCP_ENTRY },\n };\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\n/** Writes the merged config with nexus-agents MCP entry. */\nfunction writeOpenCodeConfig(configDir: string, resolved: ResolvedConfig): void {\n if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });\n\n if (resolved.exists) {\n const raw = readFileSync(resolved.path, 'utf-8');\n writeJsoncConfig(resolved.path, raw);\n } else {\n writeJsonConfig(resolved.path);\n }\n}\n\n/** Options for configureOpenCode. */\nexport interface ConfigureOpenCodeOptions {\n readonly force: boolean;\n readonly dryRun: boolean;\n readonly projectRoot?: string;\n}\n\n/** Validates projectRoot to prevent path traversal (CWE-22). */\nfunction validateProjectRoot(projectRoot: string): string {\n const resolved = resolve(projectRoot);\n if (!existsSync(resolved)) {\n throw new Error(`Project root does not exist: ${resolved}`);\n }\n return resolved;\n}\n\n/**\n * Configures OpenCode with nexus-agents MCP server.\n * Supports both global (~/.config/opencode/) and project-local configs.\n */\nexport function configureOpenCode(\n force: boolean,\n dryRun: boolean,\n options?: ConfigureOpenCodeOptions\n): OpenCodeConfigResult {\n try {\n return configureOpenCodeInner(force, dryRun, options);\n } catch (error: unknown) {\n const fallbackPath = join(getOpenCodeConfigDir(), 'opencode.json');\n return {\n success: false,\n alreadyConfigured: false,\n message: `Failed to configure OpenCode: ${getErrorMessage(error)}`,\n configPath: fallbackPath,\n };\n }\n}\n\nfunction configureOpenCodeInner(\n force: boolean,\n dryRun: boolean,\n options?: ConfigureOpenCodeOptions\n): OpenCodeConfigResult {\n const configDir =\n options?.projectRoot !== undefined\n ? validateProjectRoot(options.projectRoot)\n : getOpenCodeConfigDir();\n const resolved = resolveOpenCodeConfig(configDir);\n\n if (isAlreadyConfigured(resolved) && !force) {\n return {\n success: true,\n alreadyConfigured: true,\n message: 'nexus-agents already configured in OpenCode',\n configPath: resolved.path,\n };\n }\n if (dryRun) {\n return {\n success: true,\n alreadyConfigured: false,\n message: `Would configure nexus-agents MCP in ${resolved.path}`,\n configPath: resolved.path,\n };\n }\n writeOpenCodeConfig(configDir, resolved);\n return {\n success: true,\n alreadyConfigured: false,\n message: 'Configured nexus-agents MCP in OpenCode',\n configPath: resolved.path,\n };\n}\n","/**\n * Gemini CLI MCP auto-configuration for setup command.\n *\n * Detects Gemini CLI and configures ~/.gemini/settings.json with nexus-agents MCP server.\n *\n * @module cli/setup-gemini\n * (Source: Issue #1259 - Gemini CLI MCP auto-configuration)\n */\n\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { getErrorMessage } from '../core/index.js';\nimport { createLogger } from '../core/index.js';\nimport { detectCliBinary, type CliDetectionInfo } from './setup-cli-detection.js';\n\nconst logger = createLogger({ component: 'setup-gemini' });\n\n/**\n * Gemini CLI detection result.\n *\n * Type alias of {@link CliDetectionInfo} — see #2155.\n */\nexport type GeminiCliInfo = CliDetectionInfo;\n\n/** Gemini MCP configuration result. */\nexport interface GeminiConfigResult {\n readonly success: boolean;\n readonly alreadyConfigured: boolean;\n readonly message: string;\n readonly configPath: string;\n}\n\n/** Gemini MCP server entry format. */\ninterface GeminiMcpEntry {\n readonly command: string;\n readonly args: readonly string[];\n readonly timeout: number;\n}\n\n/** Detects Gemini CLI installation. Delegates to {@link detectCliBinary}. */\nexport function detectGeminiCli(): GeminiCliInfo {\n return detectCliBinary('gemini');\n}\n\n/** Resolves the Gemini config directory path based on scope. */\nfunction getGeminiConfigDir(scope: 'user' | 'project', projectRoot?: string): string {\n if (scope === 'project' && projectRoot !== undefined) {\n return join(projectRoot, '.gemini');\n }\n return join(homedir(), '.gemini');\n}\n\n/** Returns the nexus-agents MCP entry for Gemini settings.json. */\nfunction getNexusMcpEntry(): GeminiMcpEntry {\n return {\n command: 'npx',\n args: ['nexus-agents', '--mode=server'],\n timeout: 30000,\n };\n}\n\n/** Checks if nexus-agents is already configured in settings.json. */\nfunction isAlreadyConfigured(configPath: string): boolean {\n if (!existsSync(configPath)) return false;\n try {\n const config = JSON.parse(readFileSync(configPath, 'utf-8')) as Record<string, unknown>;\n const servers = config['mcpServers'] as Record<string, unknown> | undefined;\n return servers?.['nexus-agents'] !== undefined;\n } catch {\n logger.debug('Failed to parse existing Gemini settings.json, will overwrite');\n return false;\n }\n}\n\n/** Reads existing config or returns empty object. */\nfunction readExistingConfig(configPath: string): Record<string, unknown> {\n if (!existsSync(configPath)) return {};\n try {\n return JSON.parse(readFileSync(configPath, 'utf-8')) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\n/** Writes the merged config with nexus-agents MCP entry. */\nfunction writeGeminiConfig(configDir: string, configPath: string): void {\n if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });\n const config = readExistingConfig(configPath);\n const servers = (config['mcpServers'] ?? {}) as Record<string, unknown>;\n servers['nexus-agents'] = getNexusMcpEntry();\n config['mcpServers'] = servers;\n writeFileSync(configPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\n/**\n * Configures Gemini CLI with nexus-agents MCP server.\n *\n * @param force - Force reconfiguration even if already configured\n * @param dryRun - Preview changes without writing\n * @param scope - 'user' for global (~/.gemini/), 'project' for project-local (.gemini/)\n * @param projectRoot - Project root directory (required for project scope)\n */\nexport function configureGemini(\n force: boolean,\n dryRun: boolean,\n scope: 'user' | 'project' = 'user',\n projectRoot?: string\n): GeminiConfigResult {\n const configDir = getGeminiConfigDir(scope, projectRoot);\n const configPath = join(configDir, 'settings.json');\n\n if (isAlreadyConfigured(configPath) && !force) {\n return {\n success: true,\n alreadyConfigured: true,\n message: 'nexus-agents already configured in Gemini CLI',\n configPath,\n };\n }\n if (dryRun) {\n return {\n success: true,\n alreadyConfigured: false,\n message: `Would configure nexus-agents MCP in ${configPath}`,\n configPath,\n };\n }\n try {\n writeGeminiConfig(configDir, configPath);\n return {\n success: true,\n alreadyConfigured: false,\n message: 'Configured nexus-agents MCP in Gemini CLI',\n configPath,\n };\n } catch (error: unknown) {\n return {\n success: false,\n alreadyConfigured: false,\n message: `Failed to configure Gemini: ${getErrorMessage(error)}`,\n configPath,\n };\n }\n}\n","/**\n * Codex CLI MCP auto-configuration for setup command.\n *\n * Detects Codex CLI and registers nexus-agents MCP server\n * using `codex mcp add` CLI command.\n *\n * @module cli/setup-codex\n * (Source: Issue #1263 - Codex CLI MCP auto-configuration)\n */\n\nimport { execFileSync } from 'node:child_process';\nimport { getErrorMessage } from '../core/index.js';\nimport { createLogger } from '../core/index.js';\nimport { detectCliBinary, type CliDetectionInfo } from './setup-cli-detection.js';\n\nconst logger = createLogger({ component: 'setup-codex' });\n\n/**\n * Codex CLI detection result.\n *\n * Type alias of {@link CliDetectionInfo} — kept as a named type for the\n * public API surface (re-exported from `cli/index.ts`). Identical shape\n * across all three CLI setups (#2155).\n */\nexport type CodexCliInfo = CliDetectionInfo;\n\n/** Codex MCP configuration result. */\nexport interface CodexConfigResult {\n readonly success: boolean;\n readonly alreadyConfigured: boolean;\n readonly message: string;\n}\n\n/** Detects Codex CLI installation. Delegates to {@link detectCliBinary}. */\nexport function detectCodexCli(): CodexCliInfo {\n return detectCliBinary('codex');\n}\n\n/**\n * Checks if nexus-agents is already configured in Codex MCP.\n */\nfunction isAlreadyConfigured(): boolean {\n try {\n const output = execFileSync('codex', ['mcp', 'list'], {\n timeout: 5000,\n stdio: 'pipe',\n encoding: 'utf-8',\n });\n return output.includes('nexus-agents');\n } catch {\n return false;\n }\n}\n\n/**\n * Removes existing nexus-agents MCP configuration from Codex.\n */\nfunction removeExisting(): void {\n try {\n execFileSync('codex', ['mcp', 'remove', 'nexus-agents'], {\n timeout: 5000,\n stdio: 'pipe',\n });\n } catch {\n logger.debug('Failed to remove existing Codex MCP config (may not exist)');\n }\n}\n\n/**\n * Adds nexus-agents MCP server to Codex CLI.\n *\n * Uses: codex mcp add nexus-agents -- npx nexus-agents --mode=server\n */\nfunction addMcpServer(): CodexConfigResult {\n try {\n execFileSync(\n 'codex',\n ['mcp', 'add', 'nexus-agents', '--', 'npx', 'nexus-agents', '--mode=server'],\n {\n timeout: 10000,\n stdio: 'pipe',\n }\n );\n return {\n success: true,\n alreadyConfigured: false,\n message: 'Configured nexus-agents MCP in Codex CLI',\n };\n } catch (error: unknown) {\n return {\n success: false,\n alreadyConfigured: false,\n message: `Failed to configure Codex: ${getErrorMessage(error)}`,\n };\n }\n}\n\n/**\n * Configures Codex CLI with nexus-agents MCP server.\n */\nexport function configureCodex(force: boolean, dryRun: boolean): CodexConfigResult {\n if (isAlreadyConfigured() && !force) {\n return {\n success: true,\n alreadyConfigured: true,\n message: 'nexus-agents already configured in Codex CLI',\n };\n }\n\n if (dryRun) {\n return {\n success: true,\n alreadyConfigured: false,\n message: 'Would configure nexus-agents MCP in Codex CLI',\n };\n }\n\n if (force && isAlreadyConfigured()) {\n removeExisting();\n }\n\n return addMcpServer();\n}\n","/**\n * nexus-agents setup interactive wizard\n *\n * Guided setup experience for configuring nexus-agents with Claude CLI.\n * Uses Node.js readline for terminal prompts without external dependencies.\n *\n * @module cli/setup-wizard\n * (Source: Issue #425 - Interactive setup wizard)\n */\n\nimport { createInterface, type Interface } from 'node:readline';\nimport { formatHeader, formatStatus, isInteractive } from './setup-formatting.js';\nimport type { SetupOptions } from './setup-types.js';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Usage mode selection. */\nexport type UsageMode = 'claude-desktop' | 'claude-cli' | 'standalone' | 'all';\n\n/** Wizard answers collected from user. */\nexport interface WizardAnswers {\n usageMode: UsageMode;\n hasApiKeys: boolean;\n configDirectory: string;\n confirmProceed: boolean;\n}\n\n/** Wizard state during execution. */\ninterface WizardState {\n currentStep: number;\n totalSteps: number;\n answers: Partial<WizardAnswers>;\n}\n\n// ============================================================================\n// Readline Helpers\n// ============================================================================\n\n/**\n * Creates a readline interface for prompting.\n */\nfunction createReadline(): Interface {\n return createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n}\n\n/**\n * Prompts for input and returns the answer.\n */\nasync function promptInput(rl: Interface, question: string): Promise<string> {\n return new Promise((resolve) => {\n rl.question(question, (answer) => {\n resolve(answer.trim());\n });\n });\n}\n\n/**\n * Prompts for a yes/no confirmation.\n */\nasync function promptConfirm(\n rl: Interface,\n question: string,\n defaultValue = true\n): Promise<boolean> {\n const defaultHint = defaultValue ? '[Y/n]' : '[y/N]';\n const answer = await promptInput(rl, `${question} ${defaultHint}: `);\n\n if (answer === '') return defaultValue;\n return answer.toLowerCase().startsWith('y');\n}\n\n/**\n * Prints options for selection prompt.\n */\nfunction printOptions(\n options: readonly { value: string; label: string }[],\n defaultIndex: number\n): void {\n for (let i = 0; i < options.length; i++) {\n const option = options[i];\n const marker = i === defaultIndex ? '>' : ' ';\n const label = option?.label ?? '';\n writeLine(` ${marker} ${String(i + 1)}. ${label}`);\n }\n}\n\n/**\n * Gets value from options at index with fallback.\n */\nfunction getOptionValue(\n options: readonly { value: string; label: string }[],\n index: number,\n fallbackIndex: number\n): string {\n const option = options[index];\n if (option !== undefined) {\n return option.value;\n }\n const fallback = options[fallbackIndex];\n return fallback?.value ?? '';\n}\n\n/**\n * Prompts for a selection from a list of options.\n */\nasync function promptSelect(\n rl: Interface,\n question: string,\n options: readonly { value: string; label: string }[],\n defaultIndex = 0\n): Promise<string> {\n writeLine('\\n' + question);\n printOptions(options, defaultIndex);\n\n const answer = await promptInput(rl, `\\nChoice [1-${String(options.length)}]: `);\n\n if (answer === '') {\n return getOptionValue(options, defaultIndex, 0);\n }\n\n const index = parseInt(answer, 10) - 1;\n if (index >= 0 && index < options.length) {\n return getOptionValue(options, index, 0);\n }\n\n return getOptionValue(options, defaultIndex, 0);\n}\n\n// ============================================================================\n// Output Helpers\n// ============================================================================\n\n/** Writes a line to stdout. */\nfunction writeLine(text: string): void {\n process.stdout.write(text + '\\n');\n}\n\n/** Writes an empty line. */\nfunction writeEmptyLine(): void {\n process.stdout.write('\\n');\n}\n\n/** Prints the wizard header. */\nfunction printWizardHeader(): void {\n writeEmptyLine();\n writeLine(formatHeader('Nexus Agents Setup Wizard'));\n writeLine('='.repeat(40));\n writeLine('This wizard will help you configure nexus-agents.');\n writeEmptyLine();\n}\n\n/** Prints step progress. */\nfunction printStepProgress(state: WizardState, stepName: string): void {\n writeLine(\n `\\n${formatHeader(`Step ${String(state.currentStep)}/${String(state.totalSteps)}: ${stepName}`)}`\n );\n writeLine('-'.repeat(40));\n}\n\n/** Prints completion message. */\nfunction printCompletion(): void {\n writeEmptyLine();\n writeLine(formatStatus('success') + ' Wizard completed! Running setup...');\n writeEmptyLine();\n}\n\n// ============================================================================\n// Wizard Steps\n// ============================================================================\n\n/** Usage mode options. */\nconst USAGE_MODE_OPTIONS: readonly { value: UsageMode; label: string }[] = [\n { value: 'claude-cli', label: 'Claude CLI (terminal-based development)' },\n { value: 'claude-desktop', label: 'Claude Desktop (GUI application)' },\n { value: 'standalone', label: 'Standalone CLI (without Claude integration)' },\n { value: 'all', label: 'All of the above' },\n];\n\n/**\n * Step 1: Ask how the user plans to use nexus-agents.\n */\nasync function askUsageMode(rl: Interface, state: WizardState): Promise<UsageMode> {\n printStepProgress(state, 'Usage Mode');\n writeLine('How will you use nexus-agents?');\n\n const answer = await promptSelect(rl, '', USAGE_MODE_OPTIONS, 0);\n\n return answer as UsageMode;\n}\n\n/**\n * Step 2: Ask about API key configuration.\n */\nasync function askApiKeys(rl: Interface, state: WizardState): Promise<boolean> {\n printStepProgress(state, 'API Keys');\n writeLine('nexus-agents works best with API keys configured.');\n writeLine('Supported providers: Anthropic (Claude), OpenAI, Google (Gemini)');\n writeEmptyLine();\n\n const hasKeys = await promptConfirm(rl, 'Do you have at least one API key configured?', false);\n\n if (!hasKeys) {\n writeEmptyLine();\n writeLine('No worries! You can configure API keys later:');\n writeLine(' - ANTHROPIC_API_KEY for Claude');\n writeLine(' - OPENAI_API_KEY for OpenAI/Codex');\n writeLine(' - GOOGLE_AI_API_KEY for Gemini');\n writeLine('\\nRun `nexus-agents doctor` to check your configuration.');\n }\n\n return hasKeys;\n}\n\n/**\n * Step 3: Ask about configuration directory.\n */\nasync function askConfigDirectory(rl: Interface, state: WizardState): Promise<string> {\n printStepProgress(state, 'Configuration');\n writeLine('Where should nexus-agents store its configuration?');\n writeEmptyLine();\n\n const defaultDir = process.cwd();\n const answer = await promptInput(rl, `Directory [${defaultDir}]: `);\n\n return answer || defaultDir;\n}\n\n/**\n * Step 4: Confirm and proceed.\n */\nasync function askConfirmation(\n rl: Interface,\n state: WizardState,\n answers: Partial<WizardAnswers>\n): Promise<boolean> {\n printStepProgress(state, 'Confirmation');\n writeLine('Setup will configure the following:');\n writeEmptyLine();\n\n const modeLabel =\n USAGE_MODE_OPTIONS.find((o) => o.value === answers.usageMode)?.label ?? 'Unknown';\n const apiKeyStatus = answers.hasApiKeys === true ? 'Configured' : 'Not yet configured';\n const configDir = answers.configDirectory ?? process.cwd();\n writeLine(` Usage mode: ${modeLabel}`);\n writeLine(` API keys: ${apiKeyStatus}`);\n writeLine(` Config directory: ${configDir}`);\n writeEmptyLine();\n\n const skipMcp = answers.usageMode === 'standalone';\n if (!skipMcp) {\n writeLine('Will configure:');\n writeLine(' - MCP server for Claude integration');\n writeLine(' - Rules file (.rules/nexus-agents.md)');\n writeLine(' - Hooks for session tracking');\n } else {\n writeLine('Will configure:');\n writeLine(' - Rules file (.rules/nexus-agents.md)');\n writeLine(' - (Skipping MCP/hooks - not needed for standalone mode)');\n }\n\n writeEmptyLine();\n return promptConfirm(rl, 'Proceed with setup?', true);\n}\n\n// ============================================================================\n// Wizard Runner\n// ============================================================================\n\n/**\n * Runs the interactive setup wizard.\n *\n * @returns Setup options based on wizard answers, or undefined if cancelled\n */\nexport async function runWizard(): Promise<Partial<SetupOptions> | undefined> {\n // Check if interactive mode is available\n if (!isInteractive()) {\n writeLine('Interactive mode not available (TTY required).');\n writeLine('Use --non-interactive flag for automated setup.');\n return undefined;\n }\n\n const rl = createReadline();\n\n try {\n printWizardHeader();\n\n const state: WizardState = {\n currentStep: 1,\n totalSteps: 4,\n answers: {},\n };\n\n // Step 1: Usage mode\n state.answers.usageMode = await askUsageMode(rl, state);\n state.currentStep++;\n\n // Step 2: API keys\n state.answers.hasApiKeys = await askApiKeys(rl, state);\n state.currentStep++;\n\n // Step 3: Config directory\n state.answers.configDirectory = await askConfigDirectory(rl, state);\n state.currentStep++;\n\n // Step 4: Confirmation\n state.answers.confirmProceed = await askConfirmation(rl, state, state.answers);\n state.currentStep++;\n\n if (!state.answers.confirmProceed) {\n writeEmptyLine();\n writeLine('Setup cancelled.');\n return undefined;\n }\n\n printCompletion();\n\n // Convert wizard answers to setup options\n return convertAnswersToOptions(state.answers as WizardAnswers);\n } finally {\n rl.close();\n }\n}\n\n/**\n * Converts wizard answers to setup options.\n */\nfunction convertAnswersToOptions(answers: WizardAnswers): Partial<SetupOptions> {\n const isStandalone = answers.usageMode === 'standalone';\n\n return {\n // Skip MCP and hooks for standalone mode\n skipMcp: isStandalone,\n skipHooks: isStandalone,\n // Always create rules file\n skipRules: false,\n // Use user scope for Claude CLI/Desktop, project for standalone\n scope: isStandalone ? 'project' : 'user',\n // Non-interactive since we collected all info\n nonInteractive: true,\n // Show verbose output to explain what's happening\n verbose: true,\n };\n}\n\n// ============================================================================\n// Exports\n// ============================================================================\n\nexport { isInteractive };\n","/**\n * Claude Code permissions snippet generator (#1945).\n *\n * In \"don't ask\" permission mode, Claude Code hard-rejects MCP tool calls\n * that aren't in the allowlist. For nexus-agents to be usable for agentic\n * dogfooding (orchestrate, create_expert, consensus_vote), the tools must\n * be pre-approved in ~/.claude/settings.json.\n *\n * This module generates a recommended snippet for users to paste into\n * their settings.json. We do NOT auto-write permissions — that would be\n * a security footgun.\n *\n * @module cli/setup-permissions\n */\n\n/** MCP tool names that are safe to pre-approve (read-only or idempotent). */\nconst SAFE_READONLY_TOOLS = [\n 'list_experts',\n 'list_workflows',\n 'weather_report',\n 'memory_query',\n 'memory_stats',\n 'query_trace',\n 'research_query',\n 'research_analyze',\n 'research_discover',\n 'research_synthesize',\n 'repo_analyze',\n 'repo_security_plan',\n 'extract_symbols',\n 'search_codebase',\n] as const;\n\n/** MCP tools that execute tasks but are commonly used for dogfooding. */\nconst SAFE_EXECUTION_TOOLS = [\n 'orchestrate',\n 'create_expert',\n 'execute_expert',\n 'consensus_vote',\n 'delegate_to_model',\n 'run_workflow',\n 'issue_triage',\n 'registry_import',\n] as const;\n\nexport type PermissionLevel = 'readonly' | 'all';\n\n/**\n * Generate a JSON snippet of recommended permissions for Claude Code's\n * `~/.claude/settings.json` to pre-approve nexus-agents MCP tools.\n *\n * Users paste this into the `permissions.allow` array.\n */\nexport function generatePermissionsSnippet(level: PermissionLevel = 'all'): string {\n const tools =\n level === 'readonly'\n ? [...SAFE_READONLY_TOOLS]\n : [...SAFE_READONLY_TOOLS, ...SAFE_EXECUTION_TOOLS];\n\n const permissions = tools.map((t) => `mcp__nexus-agents__${t}`).sort();\n return JSON.stringify(permissions, null, 2);\n}\n\n/**\n * Return a human-readable banner explaining the permissions snippet,\n * for inclusion in `nexus-agents setup` output.\n */\nexport function buildPermissionsBanner(snippet: string): string {\n return [\n '',\n '--- Claude Code Permissions (optional) ---',\n '',\n \"To use nexus-agents MCP tools in 'don't ask' mode (autonomous/headless\",\n 'Claude Code sessions), add these entries to the `permissions.allow` array',\n 'in your `~/.claude/settings.json`:',\n '',\n snippet,\n '',\n 'Without these, each MCP tool call will prompt for approval in interactive',\n 'mode, or be rejected outright in `dangerously-skip-permissions` mode.',\n '',\n 'Reference: https://github.com/nexus-substrate/nexus-agents/issues/1945',\n '',\n ].join('\\n');\n}\n","/**\n * nexus-agents/cli - Verify Command\n *\n * Quick verification that installation works correctly.\n * No API keys required - runs offline checks only.\n *\n * @module cli/verify-command\n * (Source: Issue #253 - Quick verification step after installation)\n */\n\nimport { VERSION } from '../version.js';\nimport { getTimeProvider } from '../core/index.js';\nimport { defaultConfig, loadConfig } from '../config/index.js';\nimport { BUILT_IN_EXPERTS } from '../agents/experts/expert-config.js';\nimport { colors, symbols } from './ansi-output.js';\nimport { checkSqlite, checkDataDirectory, checkApiKeys } from './doctor.js';\nimport { probeAllClis } from './cli-auth-probe.js';\n\n/**\n * Verify command options.\n */\nexport interface VerifyOptions {\n readonly verbose: boolean;\n}\n\n/**\n * Severity of a failed check.\n *\n * - `hard`: functionality is broken (e.g. Node version too low, core exports\n * missing). Exit code 1.\n * - `warn`: functionality is degraded but usable (e.g. better-sqlite3 missing\n * → only some memory backends unavailable; no CLI adapters detected →\n * orchestrator still works via API keys). Exit code 0.\n *\n * Unused on passing checks.\n */\nexport type VerifySeverity = 'hard' | 'warn';\n\n/**\n * Single verification check result.\n */\nexport interface VerifyCheck {\n readonly name: string;\n readonly passed: boolean;\n readonly message: string;\n readonly fix?: string;\n /**\n * Severity for failed checks. Defaults to `hard` when omitted. Passing\n * checks ignore this field.\n */\n readonly severity?: VerifySeverity;\n}\n\n/**\n * Complete verification result.\n */\nexport interface VerifyResult {\n readonly version: string;\n readonly nodeVersion: string;\n readonly checks: readonly VerifyCheck[];\n /** True when every check passed. */\n readonly allPassed: boolean;\n /**\n * True when no check failed with `severity: 'hard'`. Drives the exit code:\n * warnings alone do not fail verification (exit 0 with warnings printed).\n */\n readonly noHardFailures: boolean;\n readonly durationMs: number;\n}\n\n/**\n * Checks if Node.js version is supported.\n */\nfunction checkNodeVersion(): VerifyCheck {\n const version = process.version;\n const major = parseInt(version.slice(1).split('.')[0] ?? '0', 10);\n\n if (major >= 22) {\n return {\n name: 'Node.js Version',\n passed: true,\n message: `${version} (LTS)`,\n };\n }\n\n if (major >= 18) {\n return {\n name: 'Node.js Version',\n passed: true,\n message: `${version} (supported, recommend 22.x LTS)`,\n };\n }\n\n return {\n name: 'Node.js Version',\n passed: false,\n message: `${version} (unsupported)`,\n fix: 'Install Node.js 22.x LTS from https://nodejs.org',\n };\n}\n\n/**\n * Checks if package exports are accessible.\n */\nfunction checkPackageExports(): VerifyCheck {\n try {\n // Check if we can access core exports\n const hasVersion = typeof VERSION === 'string' && VERSION.length > 0;\n const hasConfig = typeof defaultConfig === 'object';\n const hasBuiltInExperts = typeof BUILT_IN_EXPERTS === 'object';\n\n if (hasVersion && hasConfig && hasBuiltInExperts) {\n return {\n name: 'Package Exports',\n passed: true,\n message: 'All core modules accessible',\n };\n }\n\n return {\n name: 'Package Exports',\n passed: false,\n message: 'Some modules failed to load',\n fix: 'Try reinstalling: npm install -g nexus-agents',\n };\n } catch {\n return {\n name: 'Package Exports',\n passed: false,\n message: 'Failed to load core modules',\n fix: 'Try reinstalling: npm install -g nexus-agents',\n };\n }\n}\n\n/**\n * Checks that the project's configuration file loads and validates.\n *\n * #4844: this used to read two properties off the compiled-in `defaultConfig`\n * constant. That cannot throw once the module has imported, so the failure\n * branch #4181 added was unreachable and both live branches returned a pass —\n * a user with a malformed `nexus-agents.yaml` saw Configuration succeed. It\n * now loads the real file, which is what #4181's own remediation text\n * described (\"if a local config override exists, check it for syntax errors\").\n */\nfunction checkConfigLoading(): VerifyCheck {\n const result = loadConfig();\n\n if (!result.ok) {\n // Degraded, not a hard gate: the CLI still runs on defaults (exit 0).\n return {\n name: 'Configuration',\n passed: false,\n severity: 'warn',\n message: `Failed to load nexus-agents.yaml: ${result.error.message}`,\n fix: 'Fix the syntax errors in nexus-agents.yaml, or remove it to fall back to defaults',\n };\n }\n\n const { configPath, warnings } = result.value;\n\n // No config file is the common case and is genuinely healthy — but the\n // message must not imply a file was validated when none was read.\n if (configPath === undefined) {\n return {\n name: 'Configuration',\n passed: true,\n message: 'No config file found — using defaults',\n };\n }\n\n const suffix = warnings.length > 0 ? ` (${String(warnings.length)} warning(s))` : '';\n return {\n name: 'Configuration',\n passed: true,\n message: `Loaded ${configPath}${suffix}`,\n };\n}\n\n/**\n * Checks expert system availability.\n */\nfunction checkExpertSystem(): VerifyCheck {\n const expertTypes = Object.keys(BUILT_IN_EXPERTS);\n const count = expertTypes.length;\n\n if (count >= 5) {\n return {\n name: 'Expert System',\n passed: true,\n message: `${String(count)} expert types available`,\n };\n }\n\n return {\n name: 'Expert System',\n passed: false,\n message: 'Expert types not loaded',\n fix: 'Try reinstalling: npm install -g nexus-agents',\n };\n}\n\n/**\n * Checks that better-sqlite3 loads. Memory backends (agentic, adaptive, typed,\n * mobimem, decay) are unavailable if it's missing — functional degradation,\n * not a hard failure. Rebuilding the native module or reinstalling usually\n * fixes it.\n */\nasync function checkSqliteAvailability(): Promise<VerifyCheck> {\n const result = await checkSqlite();\n if (result.available) {\n return {\n name: 'SQLite Storage',\n passed: true,\n message: 'better-sqlite3 loaded (memory backends available)',\n };\n }\n return {\n name: 'SQLite Storage',\n passed: false,\n severity: 'warn',\n message: result.error ?? 'better-sqlite3 not available',\n fix: 'Run \"pnpm rebuild better-sqlite3\" or reinstall nexus-agents',\n };\n}\n\n/**\n * Checks that the nexus-agents data directories (per-repo + cross-repo\n * roots per epic #2872) exist and are writable.\n * `cli-commands.ts::dispatchCommand` initializes them lazily (#1398), so\n * missing dirs are a hard failure (persistence will silently drop writes).\n */\nfunction checkDataDirs(): VerifyCheck {\n const result = checkDataDirectory();\n const unwritable = result.subdirectories.filter((s) => !s.exists || !s.writable);\n if (result.rootExists && unwritable.length === 0) {\n return {\n name: 'Data Directories',\n passed: true,\n message: `${result.rootPath} (all subdirectories writable)`,\n };\n }\n if (!result.rootExists) {\n return {\n name: 'Data Directories',\n passed: false,\n severity: 'warn',\n message: `${result.rootPath} does not exist`,\n fix: 'Run any nexus-agents command — directories auto-initialize on first run',\n };\n }\n return {\n name: 'Data Directories',\n passed: false,\n severity: 'warn',\n message: `${String(unwritable.length)} subdirectory(ies) unwritable: ${unwritable\n .map((s) => s.name)\n .join(', ')}`,\n fix: `Check filesystem permissions on ${result.rootPath}`,\n };\n}\n\n/**\n * Checks that at least one execution path is configured — either an API key\n * (direct adapter) or a CLI that's actually authenticated. Without either,\n * the orchestrator has nothing to dispatch to.\n *\n * #2437: previously only checked env vars, so verify reported \"No API keys\n * configured / degraded\" while doctor (post-#2448) correctly reported the\n * CLI as authed. Both were accurate but disagreed in tone, confusing\n * operators. Now verify uses the same auth probe doctor uses, so they\n * align on the available-paths question.\n */\nasync function checkAdapterAvailability(): Promise<VerifyCheck> {\n const keys = checkApiKeys();\n const configuredKeys = keys.filter((k) => k.configured);\n const authedClis = (await probeAllClis()).filter((p) => p.state === 'authenticated');\n\n if (configuredKeys.length > 0 || authedClis.length > 0) {\n const parts: string[] = [];\n if (configuredKeys.length > 0) {\n parts.push(\n `${String(configuredKeys.length)} API key(s): ${configuredKeys.map((k) => k.name).join(', ')}`\n );\n }\n if (authedClis.length > 0) {\n parts.push(\n `${String(authedClis.length)} authed CLI(s): ${authedClis.map((p) => p.cli).join(', ')}`\n );\n }\n return {\n name: 'Adapter Availability',\n passed: true,\n message: parts.join('; '),\n };\n }\n\n return {\n name: 'Adapter Availability',\n passed: false,\n severity: 'warn',\n message: 'No API keys and no authed CLIs detected',\n fix: 'Run \"nexus-agents login\" to see per-CLI status, then \"claude /login\" / \"codex login\" / etc., or set ANTHROPIC_API_KEY / OPENAI_API_KEY / GOOGLE_AI_API_KEY',\n };\n}\n\n/**\n * Runs all verification checks.\n */\nexport async function runVerify(): Promise<VerifyResult> {\n const time = getTimeProvider();\n const startTime = time.now();\n\n const checks: VerifyCheck[] = [\n checkNodeVersion(),\n checkPackageExports(),\n checkConfigLoading(),\n checkExpertSystem(),\n await checkSqliteAvailability(),\n checkDataDirs(),\n await checkAdapterAvailability(),\n ];\n\n const allPassed = checks.every((c) => c.passed);\n const noHardFailures = checks.every((c) => c.passed || c.severity === 'warn');\n const durationMs = time.now() - startTime;\n\n return {\n version: VERSION,\n nodeVersion: process.version,\n checks,\n allPassed,\n noHardFailures,\n durationMs,\n };\n}\n\n/**\n * Formats a single check result.\n *\n * Failed-but-warn checks render as yellow warnings (degraded). Failed hard\n * checks render as red crosses.\n */\nfunction formatCheck(check: VerifyCheck): string {\n let symbol: string;\n if (check.passed) {\n symbol = `${colors.green}${symbols.check}${colors.reset}`;\n } else if (check.severity === 'warn') {\n symbol = `${colors.yellow}${symbols.warn}${colors.reset}`;\n } else {\n symbol = `${colors.red}${symbols.cross}${colors.reset}`;\n }\n\n let line = ` ${symbol} ${check.name}: ${check.message}`;\n\n if (!check.passed && check.fix !== undefined) {\n line += `\\n ${colors.dim}Fix: ${check.fix}${colors.reset}`;\n }\n\n return line;\n}\n\n/**\n * Prints verification results to stdout.\n */\nexport function printVerifyResult(result: VerifyResult, verbose: boolean): void {\n process.stdout.write('\\n');\n process.stdout.write(`${colors.bold}nexus-agents verify${colors.reset}\\n`);\n process.stdout.write('===================\\n');\n process.stdout.write('\\n');\n process.stdout.write(`Version: ${result.version}\\n`);\n process.stdout.write(`Node.js: ${result.nodeVersion}\\n`);\n process.stdout.write('\\n');\n\n process.stdout.write(`${colors.cyan}Running checks...${colors.reset}\\n`);\n process.stdout.write('\\n');\n\n for (const check of result.checks) {\n process.stdout.write(formatCheck(check) + '\\n');\n }\n\n process.stdout.write('\\n');\n\n const warnCount = result.checks.filter((c) => !c.passed && c.severity === 'warn').length;\n const hardCount = result.checks.filter((c) => !c.passed && c.severity !== 'warn').length;\n\n if (result.allPassed) {\n process.stdout.write(\n `${colors.green}${colors.bold}Installation verified successfully!${colors.reset}\\n`\n );\n process.stdout.write('\\n');\n process.stdout.write(`${colors.cyan}Next steps:${colors.reset}\\n`);\n process.stdout.write(' 1. Run \"nexus-agents doctor\" to check external CLI integrations\\n');\n process.stdout.write(\n ' 2. Run \"nexus-agents review --setup\" to configure GitHub integration\\n'\n );\n process.stdout.write(' 3. Try \"nexus-agents --help\" for all available commands\\n');\n } else if (hardCount === 0) {\n process.stdout.write(\n `${colors.yellow}${colors.bold}Verified with ${String(warnCount)} warning(s) — functional but degraded${colors.reset}\\n`\n );\n process.stdout.write('\\n');\n process.stdout.write('The warnings above indicate reduced functionality but will not\\n');\n process.stdout.write('prevent nexus-agents from running. Fix them when convenient.\\n');\n } else {\n process.stdout.write(\n `${colors.red}${colors.bold}Verification failed: ${String(hardCount)} blocking issue(s), ${String(warnCount)} warning(s)${colors.reset}\\n`\n );\n process.stdout.write('\\n');\n process.stdout.write('Please fix the blocking issues above and try again.\\n');\n }\n\n process.stdout.write('\\n');\n\n if (verbose) {\n process.stdout.write(`${colors.dim}Duration: ${String(result.durationMs)}ms${colors.reset}\\n`);\n process.stdout.write('\\n');\n }\n}\n\n/**\n * Runs the verify command and prints results.\n *\n * Exit codes:\n * - `0`: all checks passed, or only `warn`-severity checks failed (degraded\n * but functional)\n * - `1`: at least one `hard`-severity check failed (broken install)\n */\nexport async function verifyCommand(options: VerifyOptions): Promise<number> {\n const result = await runVerify();\n printVerifyResult(result, options.verbose);\n return result.noHardFailures ? 0 : 1;\n}\n"],"mappings":"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","names":["existsSync","execSync","homedir","join","execSync","execSync","readFileSync","join","join","existsSync","readFileSync","writeFileSync","mkdirSync","join","homedir","join","homedir","existsSync","readFileSync","writeFileSync","mkdirSync","existsSync","readFileSync","writeFileSync","mkdirSync","join","homedir","logger","join","homedir","isAlreadyConfigured","existsSync","readFileSync","mkdirSync","writeFileSync","execFileSync","logger","isAlreadyConfigured","execFileSync","addMcpServer","resolve","writeLine","writeEmptyLine","mcpResult","existsSync"]}
@@ -1,14 +0,0 @@
1
- // src/security/safe-path.ts
2
- import { resolve, sep } from "path";
3
- function resolveInsideRoot(filePath, root = process.cwd()) {
4
- const resolvedRoot = resolve(root);
5
- const resolved = resolve(resolvedRoot, filePath);
6
- if (resolved === resolvedRoot) return resolved;
7
- if (resolved.startsWith(resolvedRoot + sep)) return resolved;
8
- return null;
9
- }
10
-
11
- export {
12
- resolveInsideRoot
13
- };
14
- //# sourceMappingURL=chunk-NUBSJGQZ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/security/safe-path.ts"],"sourcesContent":["/**\n * Safe Path — Path traversal guards for scanner-controlled inputs (#1813, #1814)\n *\n * When reading files whose path comes from untrusted input (e.g., SARIF\n * scanner output), we must verify the resolved path stays within the\n * workspace root. Without this, a malicious scanner can cause arbitrary\n * file reads whose contents are exfiltrated via downstream LLM prompts\n * (CWE-22).\n *\n * Shared helper to prevent the finding-triage/fix-generator drift that\n * required two separate fixes.\n *\n * @module security/safe-path\n */\n\nimport { resolve, sep } from 'node:path';\n\n/**\n * Resolve a scanner-supplied path against the workspace root, returning null\n * if it escapes. Null means the caller must fall back to a safe alternative\n * (e.g., scanner-provided snippet) rather than reading the file.\n *\n * @param filePath - Scanner-supplied path (may be relative or absolute)\n * @param root - Workspace root (defaults to process.cwd())\n * @returns Resolved absolute path if inside root, null otherwise\n */\nexport function resolveInsideRoot(filePath: string, root: string = process.cwd()): string | null {\n const resolvedRoot = resolve(root);\n const resolved = resolve(resolvedRoot, filePath);\n if (resolved === resolvedRoot) return resolved;\n if (resolved.startsWith(resolvedRoot + sep)) return resolved;\n return null;\n}\n"],"mappings":";AAeA,SAAS,SAAS,WAAW;AAWtB,SAAS,kBAAkB,UAAkB,OAAe,QAAQ,IAAI,GAAkB;AAC/F,QAAM,eAAe,QAAQ,IAAI;AACjC,QAAM,WAAW,QAAQ,cAAc,QAAQ;AAC/C,MAAI,aAAa,aAAc,QAAO;AACtC,MAAI,SAAS,WAAW,eAAe,GAAG,EAAG,QAAO;AACpD,SAAO;AACT;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/config/repo-root-detection.ts","../src/config/portable-mode.ts","../src/config/nexus-data-dir.ts","../src/config/sandbox-detection.ts"],"sourcesContent":["/**\n * Repo-root detection — walks upward from a starting directory looking for\n * `.git` so callers can scope state to the current git repo. Built for\n * epic #2872 (issue #2882) to support the per-repo `.nexus-agents/` data\n * directory ratified by vote #2876.\n *\n * Defenses adopted:\n * - Walks upward but stops at filesystem root (no infinite loop on\n * symlinks-to-self).\n * - Stops at filesystem boundary (different `stat.dev`) — refuses to\n * escape across mount points. Prevents a sandboxed workdir from\n * resolving a `.git` on the host filesystem.\n * - Detects git worktrees: `.git` may be a *file* containing\n * `gitdir: <path>` rather than a directory.\n * - Realpath's the final root and rejects results that escape the\n * starting cwd by symlink.\n *\n * Deferred (tracked separately; not blocking #2882):\n * - CVE-2022-24765-style ownership check (refuse `.git` owned by a\n * different uid than the running process). The git CLI added\n * `safe.directory` for this; we don't have a comparable allowlist\n * surface yet. In CI the heuristic is too noisy to be useful\n * (runners often clone as a different uid than the workload).\n * File-system isolation (the `stat.dev` check above) covers the\n * bulk of the attack class.\n *\n * @module config/repo-root-detection\n */\n\nimport { existsSync, readFileSync, realpathSync, statSync } from 'node:fs';\nimport { dirname, isAbsolute, resolve } from 'node:path';\n\n/**\n * Walks upward from `start` looking for `.git` (file or directory).\n * Returns the absolute path to the directory containing it, or `null`.\n *\n * Worktrees: a `.git` file (not dir) with contents `gitdir: <path>` is\n * still recognised as marking the repo root. The pointed-to gitdir is\n * NOT followed — we just need the worktree's own root.\n *\n * Filesystem boundary: stops walking if the next ancestor lives on a\n * different filesystem (different `stat.dev`). This prevents a workdir\n * mounted inside a sandbox from finding a host-side `.git`.\n */\nexport function findRepoRoot(start: string): string | null {\n if (!isAbsolute(start)) {\n start = resolve(start);\n }\n\n let current: string;\n try {\n current = realpathSync(start);\n } catch {\n return null;\n }\n\n let startDev: number | undefined;\n try {\n startDev = statSync(current).dev;\n } catch {\n return null;\n }\n\n // Walk up. Depth cap is paranoid but bounds the loop on pathological\n // symlinks even though realpathSync above should already prevent cycles.\n for (let i = 0; i < 64; i++) {\n if (isRepoRoot(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Hit filesystem root.\n return null;\n }\n try {\n if (statSync(parent).dev !== startDev) {\n // Crossed a mount point; refuse to escape.\n return null;\n }\n } catch {\n return null;\n }\n current = parent;\n }\n return null;\n}\n\n/** True iff `dir/.git` exists as a directory or a git-worktree marker file. */\nexport function isRepoRoot(dir: string): boolean {\n const gitPath = `${dir}/.git`;\n if (!existsSync(gitPath)) return false;\n try {\n const st = statSync(gitPath);\n if (st.isDirectory()) return true;\n if (st.isFile()) {\n // Worktree: first line should be `gitdir: <path>`.\n const head = readFileSync(gitPath, 'utf-8').slice(0, 256);\n return head.startsWith('gitdir:');\n }\n } catch {\n return false;\n }\n return false;\n}\n","/**\n * portable-mode — auto-detect sandboxed execution environments and\n * redirect nexus-agents data to a workspace-local directory.\n *\n * The existing `getNexusDataDir()` already respects `NEXUS_DATA_DIR` and\n * `NEXUS_PORTABLE_MODE`. This module wires up the *auto-detection* path\n * so operators running inside a Docker / restricted-FS / sandboxed\n * environment don't have to set the env vars manually — nexus-agents\n * notices and announces the flip.\n *\n * Source: Issue #2471 (epic #2467 child).\n *\n * Detection rules (first match wins):\n * 1. `NEXUS_DATA_DIR` already set → respect, no auto-detect\n * 2. `NEXUS_PORTABLE_MODE=0` → operator opted out, no auto-detect\n * 3. `NEXUS_PORTABLE_MODE=1` → portable mode, no detection needed\n * 4. Heuristic: home directory not writable\n * 5. Heuristic: container env vars set (KUBERNETES_SERVICE_HOST,\n * DOCKER_CONTAINER, ECS_CONTAINER_METADATA_URI, SANDBOX, etc.)\n *\n * When portable mode fires:\n * - Sets `process.env.NEXUS_DATA_DIR` to `<cwd>/.nexus-agents`\n * - Announces once on stderr (subsequent calls are silent)\n * - If cwd is a git repo, appends `.nexus-agents/` to .gitignore\n *\n * To force off: `NEXUS_PORTABLE_MODE=0`. To force on without heuristics:\n * `NEXUS_PORTABLE_MODE=1`. To override target dir: `NEXUS_DATA_DIR=...`.\n */\n\nimport { accessSync, constants, existsSync, readFileSync, appendFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join, resolve } from 'node:path';\n\n/** Container / sandbox environment variables that strongly imply portable mode. */\nconst SANDBOX_ENV_VARS: readonly string[] = [\n 'KUBERNETES_SERVICE_HOST',\n 'DOCKER_CONTAINER',\n 'ECS_CONTAINER_METADATA_URI',\n 'ECS_CONTAINER_METADATA_URI_V4',\n 'SANDBOX',\n 'NEXUS_SANDBOX',\n];\n\nlet DETECTED = false;\n\ninterface DetectionResult {\n readonly portable: boolean;\n readonly dataDir: string;\n readonly reason:\n | 'env-data-dir'\n | 'env-opt-out'\n | 'env-opt-in'\n | 'home-unwritable'\n | 'container-env'\n | 'default';\n}\n\nfunction isHomeWritable(): boolean {\n try {\n const home = homedir();\n if (!existsSync(home)) return false;\n accessSync(home, constants.W_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction inSandboxEnv(): boolean {\n for (const v of SANDBOX_ENV_VARS) {\n const val = process.env[v];\n if (val !== undefined && val !== '') return true;\n }\n return false;\n}\n\nfunction checkExplicitEnv(cwd: string): DetectionResult | null {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return { portable: false, dataDir: resolve(fromEnv), reason: 'env-data-dir' };\n }\n const portableEnv = process.env['NEXUS_PORTABLE_MODE'];\n if (portableEnv === '0' || portableEnv === 'false') {\n return { portable: false, dataDir: join(homedir(), '.nexus-agents'), reason: 'env-opt-out' };\n }\n if (portableEnv === '1' || portableEnv === 'true') {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'env-opt-in' };\n }\n return null;\n}\n\nfunction checkHeuristics(cwd: string): DetectionResult | null {\n if (!isHomeWritable()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'home-unwritable' };\n }\n if (inSandboxEnv()) {\n return { portable: true, dataDir: join(cwd, '.nexus-agents'), reason: 'container-env' };\n }\n return null;\n}\n\n/**\n * Decide whether to flip nexus-agents to portable mode and what data dir\n * to use. Pure function; no side effects beyond reading env + filesystem.\n */\nexport function detectPortableMode(cwd: string = process.cwd()): DetectionResult {\n return (\n checkExplicitEnv(cwd) ??\n checkHeuristics(cwd) ?? {\n portable: false,\n dataDir: join(homedir(), '.nexus-agents'),\n reason: 'default',\n }\n );\n}\n\n/**\n * Apply detected portable mode at process startup. Idempotent (subsequent\n * calls are no-ops). Announces on stderr once when the auto-detection\n * heuristic triggers — env-opt-in/out doesn't announce because the\n * operator already knows.\n */\nexport function applyPortableMode(cwd: string = process.cwd()): void {\n if (DETECTED) return;\n DETECTED = true;\n\n const result = detectPortableMode(cwd);\n\n // No env, no heuristic match — nothing to do.\n if (!result.portable) return;\n\n // The heuristic-driven cases announce; explicit opt-in is silent.\n if (result.reason === 'home-unwritable' || result.reason === 'container-env') {\n process.stderr.write(\n `[portable-mode] Sandbox detected (${result.reason}). Using ${result.dataDir} for all nexus-agents data.\\n` +\n ` Set NEXUS_PORTABLE_MODE=0 to override; see docs/guides/SANDBOXED-USAGE.md\\n`\n );\n }\n\n process.env['NEXUS_DATA_DIR'] = result.dataDir;\n\n if (isInsideGitRepo(cwd)) {\n ensureGitignored(cwd, '.nexus-agents/');\n }\n}\n\nfunction isInsideGitRepo(cwd: string): boolean {\n try {\n const gitDir = join(cwd, '.git');\n return existsSync(gitDir) && statSync(gitDir).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Append `entry` to `<cwd>/.gitignore` if not already present. Stderr\n * announce on the first append. Exported because the repo-preferred\n * resolver in `nexus-data-dir.ts` reuses the same auto-gitignore behavior\n * when `<repo>/.nexus-agents/` becomes the active data dir (epic #2872).\n */\nexport function ensureGitignored(cwd: string, entry: string): void {\n const path = join(cwd, '.gitignore');\n try {\n const existing = existsSync(path) ? readFileSync(path, 'utf-8') : '';\n const lines = existing.split('\\n').map((l) => l.trim());\n if (lines.includes(entry) || lines.includes(entry.replace(/\\/$/, ''))) return;\n const sep = existing.length > 0 && !existing.endsWith('\\n') ? '\\n' : '';\n appendFileSync(path, `${sep}${entry}\\n`, 'utf-8');\n process.stderr.write(\n `[portable-mode] Added '${entry}' to ${path} so nexus-agents data is gitignored.\\n`\n );\n } catch (e: unknown) {\n // Non-fatal — operator can add the line themselves.\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[portable-mode] Could not auto-update ${path} (${msg}). Add '${entry}' manually if you don't want nexus-agents data tracked by git.\\n`\n );\n }\n}\n\n/** Test-only reset of the once-only state. */\nexport function _resetDetectedForTests(): void {\n DETECTED = false;\n}\n","/**\n * Nexus runtime data directory resolver (#2302, child of #2301).\n *\n * Returns the absolute path under which nexus-agents stores runtime state.\n * Two categories of state exist (epic #2872, vote #2876):\n *\n * - **Per-repo** state (sessions, checkpoints, traces, runs, audit,\n * pipeline, tasks) — tied to the work happening in a specific codebase.\n * When `NEXUS_REPO_PREFERRED=1` is set and the caller is inside a git\n * repo, these resolve to `<repo-root>/.nexus-agents/<subdir>/`.\n * - **Cross-repo** state (learning, voting, memory, weather, research,\n * auth, usage, models manifest) — accumulates across the operator's\n * whole workflow. Always resolves to `~/.nexus-agents/<subdir>/` so the\n * learning/routing feedback loop from #1389 / #1407 keeps working.\n *\n * Resolution order (first match wins):\n * 1. `NEXUS_DATA_DIR` env var if set + non-empty. Wins for both categories\n * — the operator's explicit choice overrides the split.\n * 2. Sandbox-mode default (#2501): when `NEXUS_SANDBOX` is set, use\n * `${NEXUS_SANDBOX_ROOT ?? '/'}/.nexus-agents`. Sandboxed deployments\n * typically mount a multi-repo root; state goes there, shared across\n * repo subfolders rather than buried inside one.\n * 3. Per-repo subdirs (when a repo root is known AND `NEXUS_REPO_PREFERRED`\n * is not explicitly `'0'`): `<repo-root>/.nexus-agents/<subdir>/`. The\n * repo root is the active workspace root when one has been set via\n * `setActiveWorkspaceRoot()` (the MCP server derives it from the client's\n * declared `roots`, MCP spec — see #3991), otherwise `findRepoRoot(cwd)`.\n * The active-root path exists because a globally-installed MCP server runs\n * with `process.cwd()` outside the repo, so cwd-based detection would\n * wrongly route per-repo state to homedir. Auto-adds `.nexus-agents/` to\n * the repo's `.gitignore` on first resolution (fail-closed).\n * 4. Cross-repo subdirs when `~/.nexus-agents/` is unwritable AND we're\n * in a repo: per-repo fallback at `<repo-root>/.nexus-agents/<subdir>/`\n * with one-time stderr announce. Issue #2888 — gives sandbox users\n * a working `research/`, `memory/`, etc. without env-var wrangling\n * while preserving the vote #2876 default for normal-machine users.\n * 5. `<homedir>/.nexus-agents` (cross-repo state on normal machines,\n * plus everything when not in a git repo or when\n * `NEXUS_REPO_PREFERRED=0`).\n *\n * `NEXUS_REPO_PREFERRED` defaults **ON** as of this release per vote\n * #2876. Escape hatches preserved:\n * - `NEXUS_REPO_PREFERRED=0` — fully opt out (homedir for everything).\n * - `NEXUS_DATA_DIR=~/.nexus-agents` — explicit override wins over\n * the tier and the categorization both.\n * - `nexus-agents migrate` (#2879) — relocate existing homedir state\n * into `<repo>/.nexus-agents/` before flipping in production.\n *\n * @module config/nexus-data-dir\n */\n\nimport {\n accessSync,\n constants as fsConstants,\n copyFileSync,\n existsSync,\n mkdirSync,\n realpathSync,\n renameSync,\n statSync,\n unlinkSync,\n} from 'node:fs';\nimport { homedir } from 'node:os';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\n\nimport { detectSandbox } from './sandbox-detection.js';\nimport { findRepoRoot } from './repo-root-detection.js';\nimport { ensureGitignored } from './portable-mode.js';\n\n/**\n * Subdirs scoped to a single repo's work (per the epic #2872 vote).\n * When `NEXUS_REPO_PREFERRED=1` and the caller is inside a git repo, these\n * route to `<repo-root>/.nexus-agents/`; otherwise homedir. The complement\n * (everything not in this set) always goes to homedir so cross-project\n * learning state stays intact.\n *\n * Source-of-truth for the categorization: vote #2876 conditions + audit\n * thread on epic #2872. Adding a new entry here is the appropriate way to\n * mark new state as per-repo — do that as a deliberate decision, not by\n * default.\n */\nconst PER_REPO_SUBDIRS: ReadonlySet<string> = new Set([\n // Scratch space for short-lived work (worktrees, prompt files, MCP configs).\n // Per-repo because a throwaway worktree of repo A is meaningless to repo B,\n // and because containing it here keeps it inside the already-gitignored\n // `.nexus-agents/` tree instead of accumulating in a shared `/tmp` nobody\n // reaps. A full `/tmp` is not a theoretical problem: it silently broke this\n // repo's own test suite, with ~1,100 files failing to COLLECT and zero\n // assertion failures, which reads like a code fault and is not one.\n 'tmp',\n 'sessions',\n 'checkpoints',\n 'traces',\n 'runs',\n 'audit',\n 'pipeline',\n 'tasks',\n // Async-mode job results (#3042 / epic #2631). Tied to work happening\n // in a specific codebase — a job dispatched on repo A shouldn't be\n // polled-against on repo B, which would happen if jobs/ were homedir-scoped.\n 'jobs',\n // CI health-check events (#3076 / #3084). Per-repo because outages\n // reported via `ci_health_check({ repo })` are repo-correlated; a wedge\n // on repo A's queue does not predict repo B's health.\n 'ci-health',\n // Governance artifacts — the runtime authentic-vote-record ledger\n // (`governance/vote-records.jsonl`, #3897/#3927/#3991). Per-repo because a\n // promotion proposal is scoped to the codebase being changed; with\n // `NEXUS_REPO_PREFERRED=1` (default) these route to\n // `<repo>/.nexus-agents/governance/` (gitignored), and to\n // `~/.nexus-agents/governance/` otherwise (global / non-repo install). This\n // is DISTINCT from the committed `<repo>/governance/vote-records.jsonl`\n // ledger the promotion gate reads — that artifact is reached only via the\n // explicit `NEXUS_VOTE_RECORDS_PATH` override (#3991).\n 'governance',\n]);\n\n/** Returns the absolute path to the nexus-agents data directory. */\nexport function getNexusDataDir(): string {\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') {\n return resolve(fromEnv);\n }\n const sandbox = detectSandbox();\n if (sandbox.active) {\n return resolve(sandbox.root ?? '/', '.nexus-agents');\n }\n return join(homedir(), '.nexus-agents');\n}\n\n/**\n * No-op kept for source-compatibility with consumers that called this\n * earlier in development. The resolver is no longer cached, so resetting\n * is unnecessary. Kept exported (rather than removed) to avoid breaking\n * imports in tests that may have already adopted it.\n */\nexport function resetNexusDataDirCache(): void {\n // intentionally empty — see module docstring\n}\n\n/**\n * Tracks per-process state for the auto-gitignore wiring: we only need\n * to (a) probe + append to `.gitignore` once per repo, and (b) bail\n * cleanly if the operator has explicitly silenced the auto-add via\n * `NEXUS_GITIGNORE_AUTO=0`. Reset in tests via the helper below.\n */\nconst gitignoredRoots = new Set<string>();\n\n/** Test helper — clears the auto-gitignore \"already did this\" memo. */\nexport function _resetGitignoreMemoForTests(): void {\n gitignoredRoots.clear();\n}\n\n/**\n * Active workspace root, when known from a source more authoritative than\n * `process.cwd()`. Set by the MCP server (#3991) from the client's declared\n * `roots` (MCP spec) once the initialize handshake completes, because a\n * globally-installed server runs with cwd OUTSIDE the repo being worked on —\n * so cwd-based repo detection would route per-repo `.nexus-agents` state to\n * homedir instead of `<repo>/.nexus-agents/`. CLI / in-repo callers never set\n * this, so they keep resolving via `findRepoRoot(cwd)` exactly as before.\n */\nlet activeWorkspaceRoot: string | undefined;\n\n/**\n * Records the active workspace root for repo-scoped data resolution. The\n * value is cross-trust-boundary input (it arrives over the MCP transport from\n * the client), so it is validated before use: resolved to an absolute path,\n * canonicalized via `realpathSync` (defeats symlink games), and required to\n * be an existing directory. Returns `true` when accepted, `false` when the\n * value is empty/invalid (in which case the previous root is left untouched).\n * Passing `null`/empty explicitly clears any previously-set root.\n */\nexport function setActiveWorkspaceRoot(root: string | null | undefined): boolean {\n const trimmed = root?.trim();\n if (trimmed === undefined || trimmed === '') {\n activeWorkspaceRoot = undefined;\n return false;\n }\n try {\n const canonical = realpathSync(resolve(trimmed));\n if (!isAbsolute(canonical) || !statSync(canonical).isDirectory()) return false;\n activeWorkspaceRoot = canonical;\n return true;\n } catch {\n // Nonexistent path, permission error, or broken symlink — reject and keep\n // whatever was set before so a bad late signal can't corrupt resolution.\n return false;\n }\n}\n\n/** Returns the active workspace root if one has been set, else `undefined`. */\nexport function getActiveWorkspaceRoot(): string | undefined {\n return activeWorkspaceRoot;\n}\n\n/** Test helper — clears the active workspace root memo. */\nexport function _resetActiveWorkspaceRootForTests(): void {\n activeWorkspaceRoot = undefined;\n}\n\n/**\n * Returns the repo-scoped `.nexus-agents/` directory if all of the\n * following hold: `NEXUS_REPO_PREFERRED` is NOT explicitly `'0'` (it\n * defaults ON as of vote #2876), `NEXUS_DATA_DIR` is not explicitly set\n * (explicit override always wins), no sandbox is active, and a repo root is\n * known — either the active workspace root (set via\n * `setActiveWorkspaceRoot()`) or an ancestor `.git` found by\n * `findRepoRoot(cwd)`.\n *\n * Side effect: on the first successful resolution per process per repo,\n * appends `.nexus-agents/` to the repo's `.gitignore` if not already\n * present. The operator can silence this with `NEXUS_GITIGNORE_AUTO=0`\n * (e.g. on CI runners with a frozen working tree).\n *\n * Returns `null` when any precondition fails; callers fall back to\n * `getNexusDataDir()` (homedir).\n */\nexport function getNexusRepoDir(): string | null {\n if (process.env['NEXUS_REPO_PREFERRED'] === '0') return null;\n const fromEnv = process.env['NEXUS_DATA_DIR']?.trim();\n if (fromEnv !== undefined && fromEnv !== '') return null;\n if (detectSandbox().active) return null;\n // Prefer the MCP-client-declared workspace root (#3991) when the server has\n // set it; fall back to walking up from cwd for CLI / in-repo callers.\n const root = activeWorkspaceRoot ?? findRepoRoot(process.cwd());\n if (root === null) return null;\n maybeAutoGitignore(root);\n return join(root, '.nexus-agents');\n}\n\n/**\n * Best-effort fail-closed wiring: once per process per repo, ensure\n * `.nexus-agents/` is in `<repo>/.gitignore`. Silenced via\n * `NEXUS_GITIGNORE_AUTO=0`. Failures are non-fatal — the helper logs\n * to stderr and continues.\n */\nfunction maybeAutoGitignore(repoRoot: string): void {\n if (process.env['NEXUS_GITIGNORE_AUTO'] === '0') return;\n if (gitignoredRoots.has(repoRoot)) return;\n gitignoredRoots.add(repoRoot);\n // Only attempt when the repo root looks real — avoid spamming stderr\n // from temp-dir test fixtures that race the lifecycle.\n if (!existsSync(repoRoot)) return;\n ensureGitignored(repoRoot, '.nexus-agents/');\n}\n\n/**\n * Per-process memo: was the homedir base directory found writable on first\n * probe? Used to short-circuit the sandbox-fallback decision in\n * `nexusDataPath()` without spending a syscall on every call.\n */\nlet homedirWritable: boolean | undefined;\n\n/** Per-process memo: have we already announced a fallback for this subdir? */\nconst announcedFallbacks = new Set<string>();\n\n/** Test helper — clears the homedir-writability + announcement memos. */\nexport function _resetWritabilityMemoForTests(): void {\n homedirWritable = undefined;\n announcedFallbacks.clear();\n}\n\n/**\n * Returns true if `getNexusDataDir()` resolves to a writable location. Probed\n * once per process and memo'd — flips the sandbox-fallback on/off for\n * cross-repo subdirs. `mkdirSync(recursive: true)` is the safest probe\n * because it both creates the dir if missing and surfaces ENOENT/EACCES\n * issues at the same point. Failures here are non-fatal — they just route\n * cross-repo state to the per-repo fallback (issue #2888).\n */\nfunction isHomedirBaseWritable(): boolean {\n if (homedirWritable !== undefined) return homedirWritable;\n const base = getNexusDataDir();\n try {\n mkdirSync(base, { recursive: true });\n accessSync(base, fsConstants.W_OK);\n homedirWritable = true;\n } catch {\n homedirWritable = false;\n }\n return homedirWritable;\n}\n\n/**\n * Emit a one-time stderr warning when a cross-repo subdir falls back to\n * the per-repo location. Operators in sandboxes get a clear signal about\n * what happened without per-call noise.\n */\nfunction announceCrossRepoFallback(subdir: string, repoPath: string): void {\n if (announcedFallbacks.has(subdir)) return;\n announcedFallbacks.add(subdir);\n process.stderr.write(\n `[nexus] ${subdir}: homedir ~/.nexus-agents is not writable; ` +\n `using per-repo fallback at ${repoPath}. See docs/guides/SANDBOXED-USAGE.md.\\n`\n );\n}\n\n/**\n * Returns a path joined under the appropriate data directory for the\n * given subdir.\n *\n * Routing decision (first match wins):\n * 1. **Per-repo subdir + in a repo + `NEXUS_REPO_PREFERRED` not '0'** →\n * `<repo>/.nexus-agents/<subdir>/...`. Standard behavior since #2884.\n * 2. **Cross-repo subdir + homedir unwritable + in a repo** →\n * `<repo>/.nexus-agents/<subdir>/...` as a sandbox-friendly fallback\n * (issue #2888). Emits a one-time stderr warning per subdir.\n * 3. **Otherwise** → `<homedir>/.nexus-agents/<subdir>/...`. If homedir\n * is also unwritable, the actual write will surface the error.\n *\n * Existing callers don't need to change — the routing decision is driven\n * by the first segment, not by caller-declared intent.\n */\nexport function nexusDataPath(...segments: string[]): string {\n const first = segments[0];\n\n // Tier 1: per-repo subdir + repo-preferred default.\n if (first !== undefined && PER_REPO_SUBDIRS.has(first)) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n return join(repoDir, ...segments);\n }\n }\n\n // Tier 2: cross-repo subdir but homedir isn't reachable. Fall back to\n // the per-repo location if we're in a repo, so sandbox users get a\n // working `research/`, `memory/`, etc. without manual env-var setup.\n // Vote #2876 preserved: this only fires when homedir is physically\n // unreachable; normal-machine users see no change.\n if (first !== undefined && !isHomedirBaseWritable()) {\n const repoDir = getNexusRepoDir();\n if (repoDir !== null) {\n const fallbackPath = join(repoDir, ...segments);\n announceCrossRepoFallback(first, fallbackPath);\n return fallbackPath;\n }\n // No repo to fall back to AND homedir unreachable — return the\n // homedir path anyway. The caller's eventual write will surface\n // the underlying EACCES/ENOENT, which is the right error to show\n // because the environment is genuinely broken at that point.\n }\n\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Like `nexusDataPath()` but eagerly creates the parent directory so\n * callers don't have to remember `mkdirSync(dirname(p), { recursive: true })`\n * before every write. Issue #2890. Use when the returned path will be\n * written to immediately; for read-only resolution prefer `nexusDataPath()`.\n *\n * If the call resolves to a directory (single segment or no segments),\n * the target IS the resolved path. If it resolves to a file (multiple\n * segments), the target is its parent.\n */\nexport function nexusDataPathEnsure(...segments: string[]): string {\n const resolved = nexusDataPath(...segments);\n const target = segments.length > 1 ? dirname(resolved) : resolved;\n mkdirSync(target, { recursive: true });\n return resolved;\n}\n\n/**\n * Explicitly homedir-scoped path. Use this in code that handles\n * cross-project state (routing memory, model registry cache, auth,\n * research catalog) where you want a hard guarantee that the resolver\n * never routes per-repo, even if a new subdir gets accidentally added\n * to the `PER_REPO_SUBDIRS` allowlist later.\n */\nexport function nexusSharedPath(...segments: string[]): string {\n return join(getNexusDataDir(), ...segments);\n}\n\n/**\n * Returns the read-only categorization used by `nexusDataPath()`.\n * Exposed for tests and for tooling that wants to introspect which\n * subdirs are per-repo (e.g. the `nexus-agents migrate` command).\n */\nexport function getPerRepoSubdirs(): ReadonlySet<string> {\n return PER_REPO_SUBDIRS;\n}\n\n/** Per-process memo — the legacy session-DB relocation runs at most once. */\nlet legacySessionsDbMigrated = false;\n\n/** Test helper — clears the session-DB migration \"already did this\" memo. */\nexport function _resetSessionsDbMigrationMemoForTests(): void {\n legacySessionsDbMigrated = false;\n}\n\n/** SQLite sidecar suffixes that must travel with the main DB file. */\nconst SQLITE_SIDECAR_SUFFIXES = ['', '-wal', '-shm', '-journal'] as const;\n\n/**\n * Resolves the session-database path, performing a one-time relocation\n * of any pre-#2902 database on the first call per process.\n *\n * The session DB is per-repo episodic state and belongs in the\n * `sessions/` bucket alongside the session journals — vote #2876\n * categorized `sessions/` as per-repo, and issue #2902 (consensus\n * 3/3) extended that to the DB. Before #2902 the DB resolved via\n * `nexusDataPath('sessions.db')`; because `sessions.db` is not a\n * per-repo first segment, that landed cross-repo at\n * `~/.nexus-agents/sessions.db`, so a DB started in one repo leaked\n * into every other repo.\n *\n * If a legacy DB exists at the old location and none exists at the\n * new one, the legacy file (and any SQLite sidecars) is moved so\n * existing session history is preserved rather than silently\n * orphaned. The move is best-effort: a failure leaves the legacy DB\n * untouched for manual recovery and the caller gets a fresh DB at the\n * new path. `NEXUS_DATA_DIR` / `NEXUS_REPO_PREFERRED` / the\n * `NEXUS_SESSIONS_DB` override all sit upstream of this resolver.\n */\nexport function sessionsDbPath(): string {\n const target = nexusDataPath('sessions', 'sessions.db');\n if (!legacySessionsDbMigrated) {\n legacySessionsDbMigrated = true;\n migrateLegacySessionsDb(target);\n }\n return target;\n}\n\n/** One-time relocation of a pre-#2902 session DB. Best-effort — see `sessionsDbPath`. */\nfunction migrateLegacySessionsDb(target: string): void {\n const legacy = nexusDataPath('sessions.db'); // pre-#2902 resolution\n if (legacy === target || !existsSync(legacy) || existsSync(target)) return;\n try {\n mkdirSync(dirname(target), { recursive: true });\n for (const suffix of SQLITE_SIDECAR_SUFFIXES) {\n const from = `${legacy}${suffix}`;\n if (existsSync(from)) moveFile(from, `${target}${suffix}`);\n }\n } catch {\n // Best-effort — a migration failure must never break session storage.\n }\n}\n\n/** Moves a file, falling back to copy+unlink across a filesystem boundary. */\nfunction moveFile(from: string, to: string): void {\n try {\n renameSync(from, to);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EXDEV') throw err;\n copyFileSync(from, to);\n unlinkSync(from);\n }\n}\n","/**\n * Sandbox detection (#2501, child 1 of epic #2500).\n *\n * nexus-agents needs to know whether it's running inside a host-provided\n * sandbox (Docker Desktop Sandbox + OpenCode, Codex sandbox, locked-down\n * CI runner) so it can adjust behaviour:\n *\n * - default `NEXUS_DATA_DIR` to the multi-repo root rather than `~/...`\n * - skip CLI subprocess detection (binaries aren't there)\n * - fail-fast when the gateway is unreachable instead of degrading\n * - suppress diagnostics that don't apply\n *\n * The signal is **explicit**: the image author (Dockerfile, devcontainer,\n * harness wrapper) sets `NEXUS_SANDBOX=<flavor>`. The flavor string is\n * for diagnostics + per-flavor branching (`docker-opencode`, `codex`,\n * `claude-code`, `ci-restricted`, …); presence is the on/off signal.\n *\n * A heuristic check runs alongside for verification — if `NEXUS_SANDBOX`\n * claims `docker-opencode` but `/.dockerenv` is missing, that's a\n * misconfiguration worth surfacing to the operator.\n *\n * @module config/sandbox-detection\n */\n\nimport { existsSync, readFileSync } from 'node:fs';\n\n/**\n * Heuristic detection result. Independent from the explicit\n * `NEXUS_SANDBOX` env var; produced by inspecting filesystem markers.\n */\nexport type SandboxHeuristic = 'docker' | 'podman' | 'unknown' | null;\n\nexport interface SandboxInfo {\n /**\n * True iff `NEXUS_SANDBOX` is set + non-empty. The explicit signal\n * the rest of the codebase keys off — never `true` by heuristic alone.\n */\n readonly active: boolean;\n /**\n * Operator-supplied flavor string, e.g. `docker-opencode`. Undefined\n * when `active === false` or the env var is empty.\n */\n readonly flavor: string | undefined;\n /**\n * Multi-repo root the user mounted. From `NEXUS_SANDBOX_ROOT`. Undefined\n * when unset; consumers that need a default substitute `/`.\n */\n readonly root: string | undefined;\n /**\n * Independent heuristic match — `null` when we couldn't run the check,\n * `'unknown'` when ran but no marker matched. Used by `doctor` to flag\n * mismatches between the explicit signal and the runtime environment.\n */\n readonly heuristicMatch: SandboxHeuristic;\n}\n\n/**\n * Detect whether nexus-agents is running inside a host-provided sandbox.\n * Pure read of env + filesystem; no caching (cheap enough to recompute,\n * and tests routinely mutate process.env).\n */\nexport function detectSandbox(): SandboxInfo {\n const flavorRaw = process.env['NEXUS_SANDBOX']?.trim();\n const flavor = flavorRaw !== undefined && flavorRaw !== '' ? flavorRaw : undefined;\n const active = flavor !== undefined;\n\n const rootRaw = process.env['NEXUS_SANDBOX_ROOT']?.trim();\n const root = rootRaw !== undefined && rootRaw !== '' ? rootRaw : undefined;\n\n return {\n active,\n flavor,\n root,\n heuristicMatch: detectContainerHeuristic(),\n };\n}\n\n/**\n * Look for filesystem markers indicating a container runtime. Used as a\n * cross-check against the explicit `NEXUS_SANDBOX` signal.\n *\n * Order of checks:\n * 1. `/.dockerenv` (Docker)\n * 2. `/run/.containerenv` (Podman)\n * 3. `/proc/1/cgroup` containing `docker` or `containerd` strings\n *\n * Returns `null` when none of the checks could run (non-Linux host with\n * no `/proc`, sandbox blocks `existsSync`, etc.) — distinct from\n * `'unknown'` (checks ran, no marker matched) so the doctor message can\n * differentiate \"we couldn't tell\" from \"we checked, no container\".\n */\nfunction detectContainerHeuristic(): SandboxHeuristic {\n try {\n if (existsSync('/.dockerenv')) return 'docker';\n if (existsSync('/run/.containerenv')) return 'podman';\n if (existsSync('/proc/1/cgroup')) {\n const cgroup = readFileSync('/proc/1/cgroup', 'utf8');\n if (/\\bdocker\\b/.test(cgroup)) return 'docker';\n if (/\\bcontainerd\\b/.test(cgroup)) return 'docker';\n }\n return 'unknown';\n } catch {\n return null;\n }\n}\n"],"mappings":"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","names":["existsSync","readFileSync","statSync","resolve","accessSync","existsSync","realpathSync","statSync","homedir","dirname","isAbsolute","join","resolve","existsSync","readFileSync","resolve","join","homedir","realpathSync","resolve","isAbsolute","statSync","join","existsSync","accessSync","join","dirname","existsSync","dirname"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/research-helpers-io.ts","../src/cli/research-scaffold.ts"],"sourcesContent":["/**\n * Research Registry I/O Operations\n *\n * File operations for loading and saving research registry YAML files.\n * Includes path traversal protection per Issue #353.\n *\n * @see docs/research/RESEARCH_INDEX.md\n * @see Issue #237 (Epic #225)\n * @see Issue #353 (Security - Path traversal fix)\n */\n\nimport * as fs from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { resolveInsideRoot } from '../security/safe-path.js';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { Result } from '../core/index.js';\nimport { SecurityError, getErrorMessage } from '../core/index.js';\nimport { ParseError } from '../core/types/workflow.js';\nimport type { TechniquesRegistry, PapersRegistry } from './research-types.js';\nimport { ensureRegistryFile } from './research-scaffold.js';\n\n// =============================================================================\n// CONSTANTS\n// =============================================================================\n\n/** Path to research registry directory relative to project root */\nexport const REGISTRY_PATH = 'docs/research/registry';\n\n/** Filename for techniques registry */\nexport const TECHNIQUES_FILE = 'techniques.yaml';\n\n/** Filename for papers registry */\nexport const PAPERS_FILE = 'papers.yaml';\n\n// =============================================================================\n// PROJECT ROOT\n// =============================================================================\n\n/**\n * Get the project root directory.\n * Note: Returns cwd since registry operations use explicit rootDir parameter\n */\nexport function getProjectRoot(): string {\n return process.cwd();\n}\n\n// =============================================================================\n// PATH VALIDATION\n// =============================================================================\n\n/**\n * Validates that a constructed path stays within the allowed root directory.\n * Prevents path traversal attacks (e.g., ../../../etc/passwd).\n *\n * @param constructedPath - The full path to validate\n * @param allowedRoot - The root directory that paths must be within\n * @returns Result with validated absolute path or SecurityError\n */\nfunction validatePath(constructedPath: string, allowedRoot: string): Result<string, SecurityError> {\n const resolved = resolveInsideRoot(constructedPath, allowedRoot);\n if (resolved === null) {\n return {\n ok: false,\n error: new SecurityError('Path traversal detected: path escapes allowed root directory', {\n context: { constructedPath, allowedRoot: resolve(allowedRoot) },\n }),\n };\n }\n return { ok: true, value: resolved };\n}\n\n// =============================================================================\n// LOAD OPERATIONS\n// =============================================================================\n\n/**\n * Load techniques registry from YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with TechniquesRegistry or SecurityError/ParseError\n */\nexport async function loadTechniquesRegistry(\n rootDir?: string\n): Promise<Result<TechniquesRegistry, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n // #2470: try to scaffold an empty techniques.yaml on first run so research_*\n // workflows don't error on a fresh install. No-op when file exists.\n // When scaffold refuses (e.g. <rootDir>/docs/ doesn't exist, or\n // NEXUS_NO_SCAFFOLD=1), fall through and let readFile produce the original\n // ENOENT error — that's the existing contract.\n await ensureRegistryFile(root, TECHNIQUES_FILE);\n\n try {\n const content = await fs.readFile(pathValidation.value, 'utf-8');\n return { ok: true, value: parseYaml(content) as TechniquesRegistry };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to load techniques registry: ${message}`) };\n }\n}\n\n/**\n * Load papers registry from YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with PapersRegistry or SecurityError/ParseError\n */\nexport async function loadPapersRegistry(\n rootDir?: string\n): Promise<Result<PapersRegistry, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n // #2470: try to scaffold an empty papers.yaml on first run so research_*\n // workflows don't error on a fresh install. No-op when file exists; falls\n // through silently when scaffold refuses (no docs/ root, or NEXUS_NO_SCAFFOLD).\n await ensureRegistryFile(root, PAPERS_FILE);\n\n try {\n const content = await fs.readFile(pathValidation.value, 'utf-8');\n return { ok: true, value: parseYaml(content) as PapersRegistry };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to load papers registry: ${message}`) };\n }\n}\n\n// =============================================================================\n// SAVE OPERATIONS\n// =============================================================================\n\n/**\n * Save techniques registry to YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param registry - The techniques registry to save\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with void on success or SecurityError/ParseError on failure\n */\nexport async function saveTechniquesRegistry(\n registry: TechniquesRegistry,\n rootDir?: string\n): Promise<Result<void, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, TECHNIQUES_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n try {\n const content = stringifyYaml(registry, { lineWidth: 100 });\n await fs.writeFile(pathValidation.value, content, 'utf-8');\n return { ok: true, value: undefined };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to save techniques registry: ${message}`) };\n }\n}\n\n/**\n * Save papers registry to YAML file.\n * Validates path to prevent directory traversal attacks.\n *\n * @param registry - The papers registry to save\n * @param rootDir - Project root directory (defaults to cwd)\n * @returns Result with void on success or SecurityError/ParseError on failure\n */\nexport async function savePapersRegistry(\n registry: PapersRegistry,\n rootDir?: string\n): Promise<Result<void, SecurityError | ParseError>> {\n const root = rootDir ?? process.cwd();\n const filePath = join(root, REGISTRY_PATH, PAPERS_FILE);\n\n const pathValidation = validatePath(filePath, root);\n if (!pathValidation.ok) {\n return pathValidation;\n }\n\n try {\n const content = stringifyYaml(registry, { lineWidth: 100 });\n await fs.writeFile(pathValidation.value, content, 'utf-8');\n return { ok: true, value: undefined };\n } catch (error) {\n const message = getErrorMessage(error);\n return { ok: false, error: new ParseError(`Failed to save papers registry: ${message}`) };\n }\n}\n","/**\n * research-scaffold — auto-create empty research registry files when missing.\n *\n * On a fresh install or in a sandboxed environment where the project doesn't\n * have `docs/research/registry/`, the research workflows previously errored\n * with `Failed to load papers registry: ENOENT`. The user has no idea what\n * to do.\n *\n * This module provides `ensureResearchRegistry()`: idempotent scaffolding\n * that creates empty YAML registries on ENOENT, announces what it did to\n * stderr (so the operator knows), and returns the path that's now safe to\n * read.\n *\n * Source: Issue #2470 (epic #2467 child).\n */\n\nimport * as fs from 'node:fs/promises';\nimport { existsSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\nimport { stringify as stringifyYaml } from 'yaml';\n\nimport type { Result } from '../core/index.js';\nimport { ok, err, getErrorMessage } from '../core/index.js';\nimport { ParseError } from '../core/types/workflow.js';\n\nimport { REGISTRY_PATH, PAPERS_FILE, TECHNIQUES_FILE } from './research-helpers-io.js';\nimport { parseBoolEnv } from '../config/defaults-env.js';\n\n/**\n * Names of the registry files this scaffolder knows how to create.\n * `sources.yaml` and `alignments.yaml` live alongside but are populated by\n * different code paths; this scaffolder only owns the two YAML registries\n * that block `research_query` / `research_synthesize` on a fresh install.\n */\ntype ScaffoldedFile = typeof PAPERS_FILE | typeof TECHNIQUES_FILE;\n\nconst SCHEMA_VERSION = '1.0';\n\nconst ANNOUNCED = new Set<string>();\n\nfunction announceOnce(path: string, kind: ScaffoldedFile): void {\n // De-dupe so that running multiple research commands in one session\n // produces one announcement per scaffolded file, not per call.\n if (ANNOUNCED.has(path)) return;\n ANNOUNCED.add(path);\n process.stderr.write(\n `[scaffold] Created empty ${kind} at ${path}; add entries via 'nexus-agents research add' or research_add MCP tool.\\n`\n );\n}\n\n/**\n * Operator opt-out. When set, scaffolding errors loudly with an actionable\n * message instead of silently writing files. Useful for CI and for\n * environments that want strict \"fail when state is wrong\" semantics.\n */\nfunction scaffoldDisabled(): boolean {\n return parseBoolEnv('NEXUS_NO_SCAFFOLD', false);\n}\n\nfunction emptyPapersYaml(): string {\n return stringifyYaml({ schema_version: SCHEMA_VERSION, papers: {} });\n}\n\nfunction emptyTechniquesYaml(): string {\n return stringifyYaml({ schema_version: SCHEMA_VERSION, techniques: {} });\n}\n\n/**\n * Create the registry directory + an empty YAML file if either is missing.\n * Idempotent: existing files are left untouched.\n *\n * Scaffolds only when `<rootDir>/docs/` already exists. That guard keeps\n * tests, vitest's package cwd, and random scratch directories from getting\n * a `docs/research/` subtree implicitly. Operators on a real project root\n * (which always has `docs/`, either from cloning the repo or running\n * `nexus-agents init`) still get the auto-create behavior the issue asks for.\n *\n * Returns the resolved file path on success, or a ParseError when scaffolding\n * is disabled / refused (so the caller can pass the error through unchanged\n * for backwards compat with existing call sites).\n */\nexport async function ensureRegistryFile(\n rootDir: string,\n filename: ScaffoldedFile\n): Promise<Result<string, ParseError>> {\n const filePath = resolve(rootDir, REGISTRY_PATH, filename);\n\n if (existsSync(filePath)) return ok(filePath);\n\n if (scaffoldDisabled()) {\n return err(\n new ParseError(\n `Registry file missing: ${filePath}. Scaffolding disabled (NEXUS_NO_SCAFFOLD=1). Create the file manually or unset the env var.`\n )\n );\n }\n\n // Refuse to create a docs/ subtree in directories that aren't already\n // documented project roots. The presence of `<rootDir>/docs/` is the\n // strong signal we use.\n if (!existsSync(resolve(rootDir, 'docs'))) {\n return err(\n new ParseError(\n `Registry file missing: ${filePath}. ${rootDir}/docs/ does not exist; ` +\n `refusing to create a Nexus subtree in a non-documented root. ` +\n `Run 'nexus-agents init' first, or create ${rootDir}/docs/ manually.`\n )\n );\n }\n\n try {\n await fs.mkdir(dirname(filePath), { recursive: true });\n const content = filename === PAPERS_FILE ? emptyPapersYaml() : emptyTechniquesYaml();\n await fs.writeFile(filePath, content, 'utf-8');\n announceOnce(filePath, filename);\n return ok(filePath);\n } catch (e: unknown) {\n return err(\n new ParseError(\n `Failed to scaffold ${filename} at ${filePath}: ${getErrorMessage(e)}. Set NEXUS_NO_SCAFFOLD=1 and create manually if scaffolding is unwanted.`\n )\n );\n }\n}\n\n/**\n * Convenience: scaffold both papers.yaml and techniques.yaml. Used by the\n * research workflow startup paths that need both registries available.\n */\nexport async function ensureResearchRegistry(rootDir?: string): Promise<Result<void, ParseError>> {\n const root = rootDir ?? process.cwd();\n const papers = await ensureRegistryFile(root, PAPERS_FILE);\n if (!papers.ok) return papers;\n const techniques = await ensureRegistryFile(root, TECHNIQUES_FILE);\n if (!techniques.ok) return techniques;\n return ok(undefined);\n}\n\n/**\n * Test-only reset of the announcement de-dupe set. Production code never\n * needs this; tests do because Vitest reuses process state across files.\n */\nexport function _resetAnnouncedForTests(): void {\n ANNOUNCED.clear();\n}\n"],"mappings":"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","names":["fs","resolve","stringifyYaml","resolve","stringifyYaml"]}