package domain import "strings" // GamePluginIdentityKey collapses generated manifest IDs for the same first-party // plugin so platform storage keeps one current registry record per plugin. func GamePluginIdentityKey(plugin GamePlugin) string { baseID := CanonicalGamePluginID(plugin.ID) return "id:" + baseID } func CanonicalGamePluginID(id string) string { id = strings.ToLower(strings.TrimSpace(id)) if marker := strings.Index(id, ".codex."); marker > 0 { return id[:marker] } return id } func GamePluginIsNewer(candidate GamePlugin, current GamePlugin) bool { if comparison := CompareGamePluginVersions(candidate.Version, current.Version); comparison != 0 { return comparison > 0 } if score := gamePluginPreferenceScore(candidate) - gamePluginPreferenceScore(current); score != 0 { return score > 0 } return strings.Compare(strings.ToLower(strings.TrimSpace(candidate.ID)), strings.ToLower(strings.TrimSpace(current.ID))) < 0 } func CompareGamePluginVersions(left string, right string) int { leftParts := gamePluginVersionParts(left) rightParts := gamePluginVersionParts(right) limit := len(leftParts) if len(rightParts) > limit { limit = len(rightParts) } for index := 0; index < limit; index++ { var leftValue, rightValue int if index < len(leftParts) { leftValue = leftParts[index] } if index < len(rightParts) { rightValue = rightParts[index] } if leftValue != rightValue { if leftValue > rightValue { return 1 } return -1 } } leftText := strings.ToLower(strings.TrimSpace(left)) rightText := strings.ToLower(strings.TrimSpace(right)) if leftText == rightText { return 0 } if leftText == "" { return -1 } if rightText == "" { return 1 } if strings.Contains(leftText, "-") != strings.Contains(rightText, "-") { if strings.Contains(leftText, "-") { return -1 } return 1 } if leftText > rightText { return 1 } return -1 } func gamePluginVersionParts(version string) []int { fields := strings.FieldsFunc(version, func(r rune) bool { return r < '0' || r > '9' }) parts := make([]int, 0, len(fields)) for _, field := range fields { value := 0 for _, r := range field { value = value*10 + int(r-'0') } parts = append(parts, value) } return parts } func gamePluginPreferenceScore(plugin GamePlugin) int { score := 0 id := strings.ToLower(strings.TrimSpace(plugin.ID)) if id == CanonicalGamePluginID(id) { score += 4 } if strings.HasPrefix(strings.ToLower(strings.TrimSpace(plugin.ManifestRef)), "artifact://") { score += 2 } if plugin.Status == GamePluginStatusInstalled { score++ } return score }