analyze.ts 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/env bun
  2. /**
  3. * Analyze upstream changes without merging
  4. *
  5. * Generates a detailed conflict report for a target upstream version.
  6. *
  7. * Usage:
  8. * bun run script/upstream/analyze.ts --version v1.1.49
  9. * bun run script/upstream/analyze.ts --commit abc123
  10. * bun run script/upstream/analyze.ts --version v1.1.49 --base-branch catrielmuller/kilo-opencode-v1.1.44
  11. */
  12. import { $ } from "bun"
  13. import * as git from "./utils/git"
  14. import * as version from "./utils/version"
  15. import * as report from "./utils/report"
  16. import { defaultConfig, loadConfig } from "./utils/config"
  17. import { header, info, success, warn, error, divider, list } from "./utils/logger"
  18. interface AnalyzeOptions {
  19. version?: string
  20. commit?: string
  21. output?: string
  22. baseBranch?: string
  23. }
  24. function parseArgs(): AnalyzeOptions {
  25. const args = process.argv.slice(2)
  26. const options: AnalyzeOptions = {}
  27. const versionIdx = args.indexOf("--version")
  28. if (versionIdx !== -1 && args[versionIdx + 1]) {
  29. options.version = args[versionIdx + 1]
  30. }
  31. const commitIdx = args.indexOf("--commit")
  32. if (commitIdx !== -1 && args[commitIdx + 1]) {
  33. options.commit = args[commitIdx + 1]
  34. }
  35. const outputIdx = args.indexOf("--output")
  36. if (outputIdx !== -1 && args[outputIdx + 1]) {
  37. options.output = args[outputIdx + 1]
  38. }
  39. const baseBranchIdx = args.indexOf("--base-branch")
  40. if (baseBranchIdx !== -1 && args[baseBranchIdx + 1]) {
  41. options.baseBranch = args[baseBranchIdx + 1]
  42. }
  43. return options
  44. }
  45. async function main() {
  46. const options = parseArgs()
  47. const config = loadConfig(options.baseBranch ? { baseBranch: options.baseBranch } : undefined)
  48. header("Upstream Change Analysis")
  49. // Check upstream remote
  50. if (!(await git.hasUpstreamRemote())) {
  51. error("No 'upstream' remote found. Please add it:")
  52. info(" git remote add upstream [email protected]:anomalyco/opencode.git")
  53. process.exit(1)
  54. }
  55. // Fetch upstream
  56. info("Fetching upstream...")
  57. await git.fetchUpstream()
  58. // Determine target
  59. let target: version.VersionInfo | null = null
  60. if (options.commit) {
  61. target = await version.getVersionForCommit(options.commit)
  62. if (!target) {
  63. target = { version: "unknown", tag: "unknown", commit: options.commit }
  64. }
  65. } else if (options.version) {
  66. const versions = await version.getAvailableUpstreamVersions()
  67. target = versions.find((v) => v.version === options.version || v.tag === options.version) || null
  68. if (!target) {
  69. error(`Version ${options.version} not found`)
  70. info("Use 'bun run script/upstream/list-versions.ts' to see available versions")
  71. process.exit(1)
  72. }
  73. } else {
  74. target = await version.getLatestUpstreamVersion()
  75. }
  76. if (!target) {
  77. error("Could not determine target version")
  78. process.exit(1)
  79. }
  80. success(`Analyzing: ${target.tag} (${target.commit.slice(0, 8)})`)
  81. divider()
  82. // Analyze conflicts
  83. info("Analyzing changes...")
  84. // Use commit hash directly since we may not have the tag fetched locally
  85. const conflicts = await report.analyzeConflicts(target.commit, config.baseBranch, config.keepOurs)
  86. // Group by type
  87. const byType = new Map<string, report.ConflictFile[]>()
  88. for (const c of conflicts) {
  89. const list = byType.get(c.type) || []
  90. list.push(c)
  91. byType.set(c.type, list)
  92. }
  93. // Group by recommendation
  94. const byRec = new Map<string, report.ConflictFile[]>()
  95. for (const c of conflicts) {
  96. const list = byRec.get(c.recommendation) || []
  97. list.push(c)
  98. byRec.set(c.recommendation, list)
  99. }
  100. console.log()
  101. success(`Total files changed: ${conflicts.length}`)
  102. console.log()
  103. // By type
  104. info("Changes by type:")
  105. for (const [type, files] of byType) {
  106. console.log(` ${type.padEnd(12)} ${files.length}`)
  107. }
  108. console.log()
  109. // By recommendation
  110. info("Changes by recommendation:")
  111. for (const [rec, files] of byRec) {
  112. const label =
  113. rec === "keep-ours"
  114. ? "Keep Kilo's"
  115. : rec === "codemod"
  116. ? "Auto-transform"
  117. : rec === "keep-theirs"
  118. ? "Take upstream"
  119. : "Manual review"
  120. console.log(` ${label.padEnd(16)} ${files.length}`)
  121. }
  122. // Show manual review files
  123. const manual = byRec.get("manual") || []
  124. if (manual.length > 0 && manual.length <= 20) {
  125. console.log()
  126. warn("Files requiring manual review:")
  127. list(manual.map((f) => f.path))
  128. }
  129. // Generate report
  130. const conflictReport: report.ConflictReport = {
  131. timestamp: new Date().toISOString(),
  132. upstreamVersion: target.version,
  133. upstreamCommit: target.commit,
  134. baseBranch: config.baseBranch,
  135. mergeBranch: "",
  136. totalConflicts: conflicts.length,
  137. conflicts,
  138. recommendations: [],
  139. }
  140. if (manual.length > 0) {
  141. conflictReport.recommendations.push(`${manual.length} files require manual review`)
  142. }
  143. const codemodFiles = byRec.get("codemod") || []
  144. if (codemodFiles.length > 0) {
  145. conflictReport.recommendations.push(`${codemodFiles.length} files will be auto-transformed`)
  146. }
  147. const keepOursFiles = byRec.get("keep-ours") || []
  148. if (keepOursFiles.length > 0) {
  149. conflictReport.recommendations.push(`${keepOursFiles.length} files will keep Kilo's version`)
  150. }
  151. // Save report
  152. const outputPath = options.output || `upstream-analysis-${target.version}.md`
  153. await report.saveReport(conflictReport, outputPath)
  154. divider()
  155. success(`Report saved to ${outputPath}`)
  156. console.log()
  157. info("Next steps:")
  158. info(" 1. Review the report")
  159. info(" 2. Run merge with: bun run script/upstream/merge.ts --version " + target.tag)
  160. }
  161. main().catch((err) => {
  162. error(`Error: ${err}`)
  163. process.exit(1)
  164. })