(
report: DiagnosticReport,
description: string,
_title: string,
sessionFilter: string | null = null,
)
| 107 | } |
| 108 | |
| 109 | export async function bundleIssueReport( |
| 110 | report: DiagnosticReport, |
| 111 | description: string, |
| 112 | _title: string, |
| 113 | sessionFilter: string | null = null, |
| 114 | ): Promise<BundledIssueReport> { |
| 115 | const LOG_TAIL_LINES = 400; |
| 116 | const allLogLines = report.logFile.exists |
| 117 | ? readFileSync(report.logFile.path, "utf-8").split(/\r?\n/) |
| 118 | : []; |
| 119 | const logLines = filterLogLinesBySession(allLogLines, sessionFilter); |
| 120 | const recentLog = sanitizeLogContent(logLines.slice(-LOG_TAIL_LINES).join("\n")).trim(); |
| 121 | |
| 122 | // Also extract historian-failure lines from a wider window so issue reports |
| 123 | // still capture failure signals even when more recent noise has pushed them |
| 124 | // out of the 400-line tail. We scan the last 4000 lines for historian |
| 125 | // patterns and keep up to 30 matches. |
| 126 | const historianScanWindow = sanitizeLogContent(logLines.slice(-4000).join("\n")); |
| 127 | const historianFailureLines = extractHistorianFailureLines(historianScanWindow, 30); |
| 128 | |
| 129 | // Pull the most recent 20 ERROR-shaped lines into their own dedicated |
| 130 | // section. The body-size cap (`capBodyToGithubLimit`) only shrinks the |
| 131 | // main `## Log (last N lines)` block, so promoting errors here |
| 132 | // guarantees the agent / human reading the issue still sees them even |
| 133 | // when the body needs heavy truncation. We scan a wide window for the |
| 134 | // same reason as historian: a flood of debug noise after the error |
| 135 | // shouldn't push the cause out of view. |
| 136 | const errorScanWindow = sanitizeLogContent(logLines.slice(-4000).join("\n")); |
| 137 | const recentErrorLines = extractRecentErrors(errorScanWindow, 20); |
| 138 | |
| 139 | const configBody = JSON.stringify( |
| 140 | sanitizeConfigValue(report.magicContextConfig.flags), |
| 141 | null, |
| 142 | 2, |
| 143 | ); |
| 144 | const sanitizedConfigPath = report.configPaths.magicContextConfig.replace(homedir(), "~"); |
| 145 | |
| 146 | const rawBodyMarkdown = [ |
| 147 | "## Description", |
| 148 | description, |
| 149 | "", |
| 150 | "## Environment", |
| 151 | `- Plugin: v${report.pluginVersion}`, |
| 152 | `- OS: ${report.platform} ${report.arch}`, |
| 153 | `- Node: ${report.nodeVersion}`, |
| 154 | `- OpenCode: ${report.opencodeVersion ?? "not installed"}`, |
| 155 | "", |
| 156 | "## Configuration", |
| 157 | `Config from \`${sanitizedConfigPath}\`:`, |
| 158 | "```jsonc", |
| 159 | configBody, |
| 160 | "```", |
| 161 | "", |
| 162 | "## Diagnostics", |
| 163 | renderDiagnosticsMarkdown(report), |
| 164 | "", |
| 165 | "## Historian failure signals (log, sanitized)", |
| 166 | historianFailureLines.length === 0 |
no test coverage detected