Fix CI tooling and semantic skill recommendations

Resolve the pinned XcodeGen archive layout and let the keyboard derive bounded clipboard recommendations from the full catalog independently of installation state.
This commit is contained in:
Rocky
2026-08-22 16:46:37 +08:00
parent 386ba140f7
commit fe8f657c8d
8 changed files with 95 additions and 13 deletions
@@ -1,9 +1,9 @@
// ClipboardSkillSemanticRanker.swift
// OSGKeyboard · Shared
//
// Keeps the user's saved skill order as the stable fallback, then temporarily
// promotes relevant skills for the newest accepted clipboard entry. Analysis
// is local and ephemeral; neither labels nor reordered IDs are persisted.
// Selects skills from the complete catalog for the newest accepted clipboard
// entry. Analysis is local and ephemeral; installation state, user-managed
// ordering, labels, and recommendations are never persisted.
import Combine
import Foundation
@@ -19,7 +19,40 @@ public enum ClipboardSkillSemanticRanker {
uiLanguage: AppUILanguage
) -> [AIClipboardSkill] {
guard skills.count > 1 else { return skills }
return sorted(
skills,
scores: relevanceScores(
sourceText: sourceText,
analysis: analysis,
uiLanguage: uiLanguage
)
)
}
/// Returns only skills supported by current semantic evidence. No matching
/// label produces no recommendation instead of a fixed fallback row.
public static func recommended(
skills: [AIClipboardSkill],
sourceText: String,
analysis: ClipboardSemanticAnalysis,
uiLanguage: AppUILanguage,
limit: Int
) -> [AIClipboardSkill] {
guard limit > 0 else { return [] }
let scores = relevanceScores(
sourceText: sourceText,
analysis: analysis,
uiLanguage: uiLanguage
)
let relevant = skills.filter { scores[$0.id, default: 0] > 0 }
return Array(sorted(relevant, scores: scores).prefix(limit))
}
private static func relevanceScores(
sourceText: String,
analysis: ClipboardSemanticAnalysis,
uiLanguage: AppUILanguage
) -> [String: Int] {
var scores: [String: Int] = [:]
func boost(_ id: String, _ value: Int) {
scores[id, default: 0] += value
@@ -71,6 +104,8 @@ public enum ClipboardSkillSemanticRanker {
if analysis.hasOrganizationName,
analysis.task.isDetected || analysis.question.isDetected || analysis.invitation.isDetected {
boost(AIClipboardSkillCatalog.businessReplyID, 125)
} else if analysis.hasOrganizationName {
boost(AIClipboardSkillCatalog.businessReplyID, 70)
}
if isListLike(sourceText) {
@@ -84,7 +119,16 @@ public enum ClipboardSkillSemanticRanker {
boost(AIClipboardSkillCatalog.extractConclusionsID, 125)
boost(AIClipboardSkillCatalog.saveToNotesID, 85)
}
if analysis.sentiment == .positive {
boost(AIClipboardSkillCatalog.replyID, 45)
}
return scores
}
private static func sorted(
_ skills: [AIClipboardSkill],
scores: [String: Int]
) -> [AIClipboardSkill] {
let baseline = Dictionary(
uniqueKeysWithValues: skills.enumerated().map { ($0.element.id, $0.offset) }
)
@@ -150,6 +150,10 @@ public final class KeyboardState: ObservableObject {
/// ensures Darwin updates publish prompt/name changes even when IDs stay unchanged.
@Published public var enabledClipboardSkills: [AIClipboardSkill] =
AIClipboardSkillCatalog.visible()
/// Complete resolved catalog used for semantic recommendations. Keyboard
/// visibility is independent from the Skills page installation state.
@Published public var clipboardSkillCatalog: [AIClipboardSkill] =
AIClipboardSkillCatalog.catalog
/// Export skills whose companion Shortcut setup the user confirmed.
@Published public var confirmedClipboardShortcutIDs: [String] = []
/// App language captured with the same App Group snapshot as skill copy.