mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat(media-use): agent-driven asset reuse (candidates + reuse)
Reuse now hands the semantic judgment to the coding agent instead of a string heuristic, while keeping the deterministic normalize-exact match as an automatic dedup floor. No LLM/embedding call enters resolve; it stays offline-capable. - lib/match.mjs: shared matchTokens + typesMatch (extracted from adopt.mjs and resolve.mjs so the icon<->image equivalence and token rules can't drift between the do-path and the look-path); adds tokenOverlap ranker. - resolve --candidates: side-effect-free listing of reusable assets across the project manifest AND the global ~/.media cache, ranked by lexical overlap, capped per scope, --json or human table. Never hard-filters on zero overlap (that would pre-empt the agent's judgment); the agent decides. - resolve --reuse <sha>: import a specific global-cache asset by content sha/prefix (from --candidates) into the project via importFromCache, marked source=reused-explicit / provenance.reused_by=agent. - Adherence nudge: on a resolve that misses the exact floor and is about to fetch, print a one-line stderr hint when similar cached assets exist, pointing at --candidates. Offline, stderr (safe under --json), never auto-reuses a fuzzy match. - cache.mjs: export readGlobalManifest; add findGlobalBySha (prefix resolve with ambiguity/miss handling). - Telemetry: media_use_candidates event + reused-explicit source on media_use_resolve (type/scope/counts only, no intent text or paths). - SKILL.md: 'Reuse before you resolve' guidance + trust guardrail (prefer-fresh-when-unsure, entity-exact for brand, cross-project bleed). - Tests: lib/candidates.test.mjs (ranking, no-hard-filter, cap/truncation, icon<->image, sha resolution, formatter); adopt.mjs refactor covered by existing lib/adopt.test.mjs. Full media-use suite green; verified e2e against the live catalog (cross-project resolve->candidates->reuse; hint fires on miss).
This commit is contained in:
@@ -33,10 +33,32 @@ function markComplete(entryDir) {
|
||||
// global manifest must be addressed by HOME, not by globalMediaDir() — passing
|
||||
// the latter nested it at ~/.media/.media/manifest.jsonl, invisible to the
|
||||
// Studio /api/assets/global route (which reads the documented flat path).
|
||||
function readGlobalManifest() {
|
||||
export function readGlobalManifest() {
|
||||
return readManifest(homedir());
|
||||
}
|
||||
|
||||
// Resolve a content-sha (full or unambiguous prefix) to a reusable global-cache
|
||||
// record, for `resolve --reuse <sha>`. Returns null on no match, or
|
||||
// { ambiguous: true, count } when a prefix matches multiple distinct entries.
|
||||
// Completeness (the .hf-complete sentinel) is left to importFromCache so the
|
||||
// caller can surface an "incomplete cache entry" error distinctly from a miss.
|
||||
export function findGlobalBySha(shaPrefix) {
|
||||
const p = String(shaPrefix || "")
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
if (!p) return null;
|
||||
const matches = readGlobalManifest().filter(
|
||||
(r) => r.reusable && typeof r.sha === "string" && r.sha.startsWith(p),
|
||||
);
|
||||
if (matches.length === 0) return null;
|
||||
if (matches.length > 1) {
|
||||
const exact = matches.find((r) => r.sha === p);
|
||||
if (exact) return exact;
|
||||
return { ambiguous: true, count: matches.length };
|
||||
}
|
||||
return matches[0];
|
||||
}
|
||||
|
||||
function validateCacheHit(match) {
|
||||
if (!match?.sha) return null;
|
||||
return isComplete(cacheEntryDir(globalMediaDir(), match.sha)) ? match : null;
|
||||
|
||||
Reference in New Issue
Block a user