@@ -200,9 +200,27 @@ func createMainGoFile(b *buildingMaterial) (err error) {
200200
201201// downloadGoModFile run go mod commands to download dependencies
202202func downloadGoModFile (b * buildingMaterial ) (err error ) {
203- // If user specify a module replacement, use it. Otherwise, use the latest version.
204- if len (b .answerModuleReplacement ) > 0 {
205- replacement := fmt .Sprintf ("%s=%s" , "github.com/apache/answer" , b .answerModuleReplacement )
203+ answerReplacement := b .answerModuleReplacement
204+
205+ // If no replacement specified and current binary is v2+, auto-determine replacement.
206+ // This is needed because go mod tidy would otherwise resolve github.com/apache/answer
207+ // to the latest v1.x version, causing v2+ features (e.g. AI/MCP) to disappear.
208+ if len (answerReplacement ) == 0 && b .originalAnswerInfo .Version != "" {
209+ ver , verErr := semver .NewVersion (strings .TrimPrefix (b .originalAnswerInfo .Version , "v" ))
210+ if verErr == nil && ver .Major () >= 2 {
211+ answerReplacement = fmt .Sprintf ("github.com/apache/answer@%s" , b .originalAnswerInfo .Version )
212+ }
213+ }
214+
215+ if len (answerReplacement ) > 0 {
216+ // For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
217+ // go mod tidy rejects the version because the module path lacks a /v2 suffix.
218+ // Work around this by cloning the repo locally and using a local path replacement.
219+ localPath , resolveErr := resolveAnswerModuleReplacement (answerReplacement , b .tmpDir )
220+ if resolveErr != nil {
221+ return resolveErr
222+ }
223+ replacement := fmt .Sprintf ("%s=%s" , "github.com/apache/answer" , localPath )
206224 err = b .newExecCmd ("go" , "mod" , "edit" , "-replace" , replacement ).Run ()
207225 if err != nil {
208226 return err
@@ -221,6 +239,56 @@ func downloadGoModFile(b *buildingMaterial) (err error) {
221239 return
222240}
223241
242+ // resolveAnswerModuleReplacement resolves the ANSWER_MODULE value to a usable local path or
243+ // remote replacement string. For v2+ versioned module paths (e.g. github.com/apache/answer@v2.0.0),
244+ // Go module system rejects the version because the module path has no /v2 suffix. In that case
245+ // the repository is cloned locally and the local path is returned instead.
246+ func resolveAnswerModuleReplacement (replacement , tmpDir string ) (string , error ) {
247+ // Local paths can be used as-is.
248+ if strings .HasPrefix (replacement , "/" ) || strings .HasPrefix (replacement , "./" ) || strings .HasPrefix (replacement , "../" ) {
249+ return replacement , nil
250+ }
251+
252+ // Parse module@version format.
253+ moduleName , version , hasVersion := strings .Cut (replacement , "@" )
254+ if ! hasVersion {
255+ return replacement , nil
256+ }
257+
258+ // Only handle v2+ versions on module paths without the /vN suffix.
259+ ver , err := semver .StrictNewVersion (strings .TrimPrefix (version , "v" ))
260+ if err != nil || ver .Major () < 2 {
261+ return replacement , nil
262+ }
263+ if strings .HasSuffix (moduleName , fmt .Sprintf ("/v%d" , ver .Major ())) {
264+ return replacement , nil
265+ }
266+
267+ // Clone the repo to a local directory and return its path.
268+ gitURL := "https://" + moduleName
269+ tag := "v" + strings .TrimPrefix (version , "v" )
270+ localPath := filepath .Join (filepath .Dir (tmpDir ), fmt .Sprintf ("answer_src_%s" , strings .ReplaceAll (version , "." , "_" )))
271+
272+ if _ , statErr := os .Stat (localPath ); statErr == nil {
273+ fmt .Printf ("[build] using cached local clone at %s\n " , localPath )
274+ return localPath , nil
275+ }
276+
277+ fmt .Printf ("[build] v2+ module detected, cloning %s@%s to local path %s...\n " , moduleName , version , localPath )
278+ cloneCmd := exec .Command ("git" , "clone" , "--depth=1" , "--branch=" + tag , gitURL , localPath )
279+ cloneCmd .Stdout = os .Stdout
280+ cloneCmd .Stderr = os .Stderr
281+ if err = cloneCmd .Run (); err != nil {
282+ return "" , fmt .Errorf (
283+ "failed to clone %s@%s: %w\n Tip: set ANSWER_MODULE to a local checkout path instead, e.g. ANSWER_MODULE=/path/to/answer" ,
284+ moduleName , version , err ,
285+ )
286+ }
287+
288+ fmt .Printf ("[build] successfully cloned to %s\n " , localPath )
289+ return localPath , nil
290+ }
291+
224292// movePluginToVendor move plugin to vendor dir
225293// Traverse the plugins, and if the plugin path is not github.com/apache/answer-plugins, move the contents of the current plugin to the vendor/github.com/apache/answer-plugins/ directory.
226294func movePluginToVendor (b * buildingMaterial ) (err error ) {
0 commit comments