AI-Repo-Commander/Docs/diagrams/fingerprint-strong.puml

440 lines
12 KiB
Plaintext

' ===================================================================
' File: fingerprint-strong.puml
' Purpose: Single source of truth for module-level activity + per-function sequences.
' Module: fingerprint-strong.js — Stable fingerprints for assistant message elements.
' Edit rules: Follow the legend at bottom; preserve VIEW/METHOD anchors for automation.
' ===================================================================
' Neutral defaults — typography/layout only (keeps partition colors intact)
skinparam Shadowing false
skinparam SequenceMessageAlign center
skinparam SequenceLifeLineBorderColor #666666
skinparam SequenceLifeLineBorderThickness 1
' ==== VIEW: Branch Flow (fingerprint-strong.js) =============================
@startuml
title fingerprint-strong.js — Branch Flow (full module)
start
:fingerprint-strong;
fork
' -------- MSG_SELECTORS (const) --------
partition "MSG_SELECTORS (assistant message selectors)" #E7FAE3 {
:[ '[data-message-author-role=\"assistant\"]',\n '.chat-message:not([data-message-author-role=\"user\"])',\n '.message-content' ];
:Used by context scans and ordinal computation;
kill
}
fork again
' -------- norm(s) --------
partition "norm(s)" #FFF6D1 {
:norm;
:remove \\r and zero-width spaces;\ncollapse spaces before \\n; trim;
:return normalized string;
kill
}
fork again
' -------- hash(s) --------
partition "hash(s)" #FFE1DB {
:hash;
:djb2-xor over chars;\nreturn unsigned base36;
kill
}
fork again
' -------- commandLikeText(el) --------
partition "commandLikeText(el)" #DCF9EE {
:commandLikeText;
:prefer <pre><code> blocks that contain full @bridge@...@end@ with action:;\nelse fallback to el.textContent (<=2000 chars), normalized;
:return text slice;
kill
}
fork again
' -------- prevContextHash(el) --------
partition "prevContextHash(el)" #FFE6F0 {
:prevContextHash;
:scan previous assistant messages via MSG_SELECTORS;\ncollect trailing text up to 2000 chars; hash();
:return base36;
kill
}
fork again
' -------- intraPrefixHash(el) --------
partition "intraPrefixHash(el)" #E6F3FF {
:intraPrefixHash;
:find first @bridge@ block; hash the text just before it (<=2000 chars);\nif none, hash trailing slice of whole text;
:return base36;
kill
}
fork again
' -------- domHint(node) --------
partition "domHint(node)" #F0E6FA {
:domHint;
:build tag#id.class (first class only), slice 40 chars;
:return small hint;
kill
}
fork again
' -------- ordinalForKey(el, key) --------
partition "ordinalForKey(el, key)" #E7FAF7 {
:ordinalForKey;
:scan all message nodes; compute same-key for peers;\nreturn index among matches;
kill
}
fork again
' -------- fingerprintElement(el) --------
partition "fingerprintElement(el)" #FFF2E7 {
:fingerprintElement;
:ch = hash(commandLikeText(el));\nph = prevContextHash(el);\nih = intraPrefixHash(el);\nkey=`ch:..|ph:..|ih:..`;\nn = ordinalForKey(el,key);\ndh = hash(domHint(el));\nreturn key+`|hint:${dh}|n:${n}`;
kill
}
fork again
' -------- getStableFingerprint(el) --------
partition "getStableFingerprint(el)" #E7F7FF {
:getStableFingerprint;
:if dataset.aiRcStableFp present -> return;\nelse compute fingerprintElement(el) and cache in dataset;
:return fp;
kill
}
fork again
' -------- expose globals --------
partition "bootstrap (expose globals)" #FFF7E7 {
:window.AI_REPO_FINGERPRINT = fingerprintElement;\nwindow.AI_REPO_STABLE_FINGERPRINT = getStableFingerprint;
kill
}
end fork
@enduml
' ==== METHOD: norm(s) =======================================================
@startuml
title fingerprint-strong:norm(s): \n Normalize text for stable hashing
participant "Caller" as CL
participant "norm(s)" as NRM
activate CL
CL -> NRM : s
activate NRM
NRM -> NRM : s = (s||'')\n .replace(/\\r/g,'')\n .replace(/\\u200b/g,'')\n .replace(/[ \\t]+\\n/g,'\\n')\n .trim()
NRM --> CL : normalized s
deactivate NRM
deactivate CL
@enduml
' ==== METHOD: hash(s) =======================================================
@startuml
title fingerprint-strong:hash(s): \n djb2-xor -> unsigned base36
participant "Caller" as CL
participant "hash(s)" as HSH
activate CL
CL -> HSH : s
activate HSH
HSH -> HSH : h=5381; for each char: h=((h<<5)+h)^code
HSH --> CL : (h>>>0).toString(36)
deactivate HSH
deactivate CL
@enduml
' ==== METHOD: commandLikeText(el) ==========================================
@startuml
title fingerprint-strong:commandLikeText(el): \n Prefer fenced @bridge@ blocks; fallback to raw text
participant "Caller" as CL
participant "commandLikeText(el)" as CLT
participant "norm(s)" as NRM
participant "DOM" as DOM
activate CL
CL -> CLT : el
activate CLT
CLT -> DOM : el.querySelectorAll('pre code, pre, code')
DOM --> CLT : NodeList blocks
loop for each block b
CLT -> NRM : norm(b.textContent||'')
NRM --> CLT : t
alt looks like full @bridge@ ... @end@ and contains 'action:'
CLT --> CL : t
deactivate CLT
deactivate CL
return
end
end
' fallback to element text
CLT -> NRM : norm((el.textContent||'').slice(0,2000))
NRM --> CLT : text
CLT --> CL : text
deactivate CLT
deactivate CL
@enduml
' ==== METHOD: prevContextHash(el) ==========================================
@startuml
title fingerprint-strong:prevContextHash(el): \n Hash trailing text of prior assistant messages (≤2000 chars)
participant "Caller" as CL
participant "prevContextHash(el)" as PCH
participant "DOM" as DOM
participant "norm(s)" as NRM
participant "hash(s)" as HSH
activate CL
CL -> PCH : el
activate PCH
PCH -> DOM : querySelectorAll(MSG_SELECTORS.join(','))
DOM --> PCH : list[]
PCH -> PCH : idx = list.indexOf(el)
alt idx <= 0
PCH --> CL : "0"
deactivate PCH
deactivate CL
return
end
PCH -> PCH : rem=2000; buf=''
loop for i = idx-1 down to 0 while rem>0
PCH -> NRM : norm(list[i].textContent||'')
NRM --> PCH : t
alt t non-empty
PCH -> PCH : take = t.slice(-rem); buf = take + buf; rem -= take.length
end
end
PCH -> HSH : hash(buf.slice(-2000))
HSH --> PCH : ph
PCH --> CL : ph
deactivate PCH
deactivate CL
@enduml
' ==== METHOD: intraPrefixHash(el) ==========================================
@startuml
title fingerprint-strong:intraPrefixHash(el): \n Hash text immediately before first command block
participant "Caller" as CL
participant "intraPrefixHash(el)" as IPH
participant "hash(s)" as HSH
participant "norm(s)" as NRM
activate CL
CL -> IPH : el
activate IPH
IPH -> IPH : t = el.textContent||''
IPH -> IPH : m = t.match(/@bridge@[\\s\\S]*?@end@/m)
IPH -> IPH : endIdx = m ? t.indexOf(m[0]) : t.length
IPH -> NRM : norm(t.slice(max(0,endIdx-2000), endIdx))
NRM --> IPH : slice
IPH -> HSH : hash(slice)
HSH --> IPH : ih
IPH --> CL : ih
deactivate IPH
deactivate CL
@enduml
' ==== METHOD: domHint(node) ================================================
@startuml
title fingerprint-strong:domHint(node): \n Tiny tag#id.class hint (first class, ≤40 chars)
participant "Caller" as CL
participant "domHint(node)" as DH
participant "DOM" as DOM
activate CL
CL -> DH : node
activate DH
alt !node
DH --> CL : ""
deactivate DH
deactivate CL
return
end
DH -> DOM : node.id / node.className / node.tagName
DOM --> DH : values
DH -> DH : cls = (typeof className==='string' ? className.split(' ')[0] : '')\n hint = `${tag}#${id}.${cls}`.slice(0,40)
DH --> CL : hint
deactivate DH
deactivate CL
@enduml
' ==== METHOD: ordinalForKey(el, key) =======================================
@startuml
title fingerprint-strong:ordinalForKey(el, key): \n Index el among elements with same ch|ph|ih
participant "Caller" as CL
participant "ordinalForKey(el, key)" as ORD
participant "DOM" as DOM
participant "commandLikeText(el)" as CLT
participant "prevContextHash(el)" as PCH
participant "intraPrefixHash(el)" as IPH
participant "hash(s)" as HSH
activate CL
CL -> ORD : el, key
activate ORD
ORD -> DOM : querySelectorAll(MSG_SELECTORS.join(','))
DOM --> ORD : list[]
ORD -> ORD : n = 0
loop for each node in list
alt node === el
ORD -> ORD : nodeKey = key
else other node
ORD -> CLT : commandLikeText(node)
CLT --> ORD : text
ORD -> HSH : hash(text.slice(0,2000))
HSH --> ORD : ch
ORD -> PCH : prevContextHash(node)
PCH --> ORD : ph
ORD -> IPH : intraPrefixHash(node)
IPH --> ORD : ih
ORD -> ORD : nodeKey = `ch:${ch}|ph:${ph}|ih:${ih}`
end
alt nodeKey == key
alt node === el
ORD --> CL : n
deactivate ORD
deactivate CL
return
else
ORD -> ORD : n++
end
end
end
ORD --> CL : n
deactivate ORD
deactivate CL
@enduml
' ==== METHOD: fingerprintElement(el) =======================================
@startuml
title fingerprint-strong:fingerprintElement(el): \n Compose ch|ph|ih + hint + ordinal
participant "Caller" as CL
participant "fingerprintElement(el)" as FPE
participant "commandLikeText(el)" as CLT
participant "prevContextHash(el)" as PCH
participant "intraPrefixHash(el)" as IPH
participant "domHint(node)" as DH
participant "ordinalForKey(el, key)" as ORD
participant "hash(s)" as HSH
activate CL
CL -> FPE : el
activate FPE
' ch
FPE -> CLT : commandLikeText(el)
CLT --> FPE : text
FPE -> HSH : hash(text.slice(0,2000))
HSH --> FPE : ch
' ph / ih
FPE -> PCH : prevContextHash(el)
PCH --> FPE : ph
FPE -> IPH : intraPrefixHash(el)
IPH --> FPE : ih
FPE -> FPE : key = `ch:${ch}|ph:${ph}|ih:${ih}`
' ordinal
FPE -> ORD : ordinalForKey(el, key)
ORD --> FPE : n
' hint
FPE -> DH : domHint(el)
DH --> FPE : hint
FPE -> HSH : hash(hint)
HSH --> FPE : dh
FPE --> CL : `ch:${ch}|ph:${ph}|ih:${ih}|hint:${dh}|n:${n}`
deactivate FPE
deactivate CL
@enduml
' ==== METHOD: getStableFingerprint(el) =====================================
@startuml
title fingerprint-strong:getStableFingerprint(el): \n Cache-first wrapper over fingerprintElement(el)
participant "Caller" as CL
participant "getStableFingerprint(el)" as GSF
participant "fingerprintElement(el)" as FPE
participant "Dataset" as DATA
activate CL
CL -> GSF : el
activate GSF
GSF -> DATA : read el.dataset.aiRcStableFp
DATA --> GSF : fp | undefined
alt cached
GSF --> CL : fp
else not cached
GSF -> FPE : fingerprintElement(el)
FPE --> GSF : fp
GSF -> DATA : el.dataset.aiRcStableFp = fp (try/catch)
DATA --> GSF : ok | ignored
GSF --> CL : fp
end
deactivate GSF
deactivate CL
@enduml
' ==== METHOD: bootstrap (expose globals) ===================================
@startuml
title fingerprint-strong:bootstrap: \n Expose helpers on window for consumers
participant "Caller" as CL
participant "bootstrap" as BOOT
participant "fingerprintElement(el)" as FPE
participant "getStableFingerprint(el)" as GSF
participant "window" as WIN
activate CL
CL -> BOOT : module load
activate BOOT
BOOT -> WIN : WIN.AI_REPO_FINGERPRINT = FPE
WIN --> BOOT : ok
BOOT -> WIN : WIN.AI_REPO_STABLE_FINGERPRINT = GSF
WIN --> BOOT : ok
BOOT --> CL : (void)
deactivate BOOT
deactivate CL
@enduml
' ==== LEGEND ===============================================================
@startuml
legend bottom
== fingerprint-strong UML Style Guide (for future edits) ==
• Scope: One .puml per module. Keep two views:
(1) Activity "Branch Flow" for the whole module (partitions + soft colors),
(2) Per-function Sequence diagrams for each exported or significant helper.
• Sequence conventions:
1) First participant is the external caller (use "Caller" or a concrete origin).
2) Do NOT add a module lifeline; the module name appears in the title only.
3) Include every directly-called helper/system as a participant
(e.g., "commandLikeText()", "prevContextHash()", "intraPrefixHash()", "domHint()", "ordinalForKey()", "hash()", "norm()", "Dataset", "DOM", "window").
4) Prefer simple messages; Use --> for returns; -> for calls.
5) Use activate/deactivate as you see fit for clarity.
6) Use alt blocks only when branches meaningfully change the message flow.
• Activity view conventions:
A) Start with module node then fork partitions for each function.
B) One partition per function; soft background color; terminate branches with 'kill'.
C) Keep wording aligned with code (e.g., 2000-char slices, base36 hash, dataset cache key).
• Color palette (soft pastels)
• Use --> for returns; -> for calls.
• Participants use quoted function names for internals; plain nouns for systems ("DOM", "Dataset", "window").
• Keep this legend at the end of the file to standardize edits.
endlegend
@enduml