AI-Repo-Commander/Docs/diagrams/command-executor.puml

435 lines
14 KiB
Plaintext

' ===================================================================
' File: command-executor.puml
' Purpose: Single source of truth for module-level activity + per-method sequences.
' Module: command-executor.js — Execute validated repo commands via bridge API; retries, timeouts, mock mode.
' 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 (command-executor.js) ==============================
@startuml
title command-executor.js — Branch Flow (full module)
start
:command-executor;
fork
' -------- execute(command, el, label) --------
partition "execute(command, el, label)" #E7FAE3 {
:execute;
:log.command(action,'executing');\nautogen commit_message (for file ops);
:if api.disabled -> delay + _success(mock);
:else -> res=_api(command) -> _success(res);
:catch -> _error(err);
kill
}
fork again
' -------- _api(command, attempt=0) --------
partition "_api(command, attempt=0)" #FFF6D1 {
:_api;
:read cfg (maxRetries, timeout, bridgeKey);\nPOST JSON via GM_xmlhttpRequest;
:onload 2xx -> resolve;\nelse -> reject(Error status);
:onerror -> retry with backoff or fail;
:ontimeout -> reject(Error timeout);
kill
}
fork again
' -------- _getBridgeKey() --------
partition "_getBridgeKey()" #FFE1DB {
:_getBridgeKey;
:key = cfg.get('api.bridgeKey');\nif missing -> prompt;\noptional persist via cfg.set;
:return key or throw if empty;
kill
}
fork again
' -------- _success(response, command, el, isMock, label) --------
partition "_success(response, command, el, isMock, label)" #DCF9EE {
:_success;
:parse JSON responseText; _status(el, type);\nbranch to _handleGetFile/_handleListFiles;
:return { success:true, data, isMock };
kill
}
fork again
' -------- _error(error, command, el, label) --------
partition "_error(error, command, el, label)" #FFE6F0 {
:_error;
:_status(el, 'ERROR', details);\nreturn { success:false, error };
kill
}
fork again
' -------- _status(el, type, data) --------
partition "_status(el, type, data)" #E6F3FF {
:_status;
:create styled div;\nleft border color via _color(type);\nappend to el;
kill
}
fork again
' -------- _handleGetFile(data, label) --------
partition "_handleGetFile(data, label)" #F0E6FA {
:_handleGetFile;
:pull content from various shapes;\npush to window.AI_REPO_RESPONSES;
kill
}
fork again
' -------- _handleListFiles(data, label) --------
partition "_handleListFiles(data, label)" #E7FAF7 {
:_handleListFiles;
:files[] -> text fenced listing;\npush to window.AI_REPO_RESPONSES;
kill
}
fork again
' -------- delay(ms) --------
partition "delay(ms)" #FFF2E7 {
:delay;
:Promise(resolve after setTimeout(ms));
kill
}
end fork
@enduml
' ==== METHOD: execute(command, sourceElement, label) ========================
@startuml
title command-executor:execute(command, el, label): \n Build request, call API or mock, normalize and render result
participant "Caller" as CL
participant "execute()" as EXE
participant "delay(ms)" as DLY
participant "_api(command, attempt)" as API
participant "_success(...)" as OK
participant "_error(...)" as ERR
participant "Config" as CFG
participant "Logger" as LOG
activate CL
CL -> EXE : initial request (command, el, label)
activate EXE
EXE -> LOG : command(action,'executing', {repo,path,label})
LOG --> EXE : ok
' Auto-commit message for file ops
EXE -> EXE : if action in {update_file, create_file} and !commit_message\n commit_message = "AI Repo Commander: ... <ISO>"
' Mock path if API disabled
EXE -> CFG : get('api.enabled')
CFG --> EXE : true/false
alt api.enabled == false
EXE -> LOG : warn("API disabled, using mock")
EXE -> DLY : delay(300)
DLY --> EXE : done
EXE -> OK : _success({status:200,responseText:'{\"success\":true,...}'}, command, el, true, label)
OK --> EXE : result
EXE -> LOG : command(action,'complete', {mock:true})
EXE --> CL : result
else real API call
EXE -> LOG : verbose("Making API request", {url,label})
EXE -> API : _api(command)
API --> EXE : res {status, responseText}
EXE -> LOG : verbose("API request succeeded", {status})
EXE -> OK : _success(res, command, el, false, label)
OK --> EXE : result
EXE -> LOG : command(action,'complete', {repo,path})
EXE --> CL : result
' error handling
else error thrown
EXE -> LOG : command(action,'error', {error})
EXE -> LOG : error("Command execution failed", {action,error})
EXE -> ERR : _error(err, command, el, label)
ERR --> EXE : error result
EXE --> CL : error result
end
deactivate EXE
deactivate CL
@enduml
' ==== METHOD: _api(command, attempt=0) =====================================
@startuml
title command-executor:_api(command, attempt=0): \n POST JSON via GM_xmlhttpRequest with retries and timeout
participant "Caller" as CL
participant "_api(command, attempt)" as API
participant "_getBridgeKey()" as KEY
participant "Config" as CFG
participant "Logger" as LOG
participant "GM_xmlhttpRequest" as GMX
participant "Timer" as TMR
activate CL
CL -> API : command, attempt=0
activate API
API -> CFG : get('api.maxRetries') / get('api.timeout')
CFG --> API : maxRetries / timeout
API -> KEY : _getBridgeKey()
KEY --> API : bridgeKey
API -> LOG : trace("GM_xmlhttpRequest details", {method:'POST', url, timeout, hasKey, attempt})
LOG --> API : ok
API -> GMX : POST { url:command.url, headers:{X-Bridge-Key,Content-Type}, data:JSON.stringify(command), timeout }
alt onload 2xx
GMX --> API : response {status 2xx, responseText}
API --> CL : response
else onload error status
GMX --> API : response {status !2xx, statusText}
API --> CL : throws Error(`API Error ${status}: ${statusText}`)
else onerror (network)
GMX --> API : error
alt attempt < maxRetries
API -> LOG : warn("Network error, retrying", {nextDelay})
API -> TMR : setTimeout(1000*(attempt+1))
TMR --> API : wake
API -> API : return _api(command, attempt+1)
API --> CL : bubbled result
else max retries exceeded
API -> LOG : error("Network error, max retries exceeded")
API --> CL : throws Error(`Network error after ${attempt+1} attempts`)
end
else ontimeout
GMX --> API : timeout
API -> LOG : error("API request timed out", {timeout, action})
API --> CL : throws Error(`API timeout after ${timeout}ms`)
end
deactivate API
deactivate CL
@enduml
' ==== METHOD: _getBridgeKey() ==============================================
@startuml
title command-executor:_getBridgeKey(): \n Read bridge key from config or prompt; optionally persist
participant "Caller" as CL
participant "_getBridgeKey()" as KEY
participant "Config" as CFG
participant "Logger" as LOG
participant "Prompt" as PR
participant "Confirm" as CF
activate CL
CL -> KEY : initial request
activate KEY
KEY -> CFG : get('api.bridgeKey')
CFG --> KEY : key | undefined
alt key present
KEY -> LOG : trace("Using saved bridge key from config")
LOG --> KEY : ok
KEY --> CL : key
else key missing
KEY -> LOG : warn("Bridge key not found, prompting user")
LOG --> KEY : ok
KEY -> PR : prompt("Enter your bridge key…")
PR --> KEY : key | ""
alt empty string
KEY -> LOG : error("User did not provide bridge key")
KEY --> CL : throws Error('Bridge key required when API is enabled')
else provided
KEY -> CF : confirm("Save this bridge key?")
CF --> KEY : yes/no
alt yes
KEY -> CFG : set('api.bridgeKey', key)
CFG --> KEY : ok
KEY -> LOG : info("Bridge key saved to config")
else no
KEY -> LOG : info("Bridge key accepted for this session only")
end
KEY --> CL : key
end
end
deactivate KEY
deactivate CL
@enduml
' ==== METHOD: _success(response, command, el, isMock, label) ================
@startuml
title command-executor:_success(response, command, el, isMock, label): \n Parse body, render status, route to output handlers
participant "Caller" as CL
participant "_success(...)" as OK
participant "JSON" as JS
participant "_status(...)" as STAT
participant "_handleGetFile(...)" as HGF
participant "_handleListFiles(...)" as HLF
activate CL
CL -> OK : response, command, el, isMock, label
activate OK
OK -> JS : JSON.parse(response.responseText || "{}")
alt parse ok
JS --> OK : data
else parse error
JS --> OK : { message: "Operation completed" }
end
OK -> STAT : _status(el, isMock?'MOCK':'SUCCESS', {action, details: data.message || 'Completed successfully', label})
STAT --> OK : rendered
alt command.action == "get_file"
OK -> HGF : _handleGetFile(data, label)
HGF --> OK : stored
else command.action == "list_files"
OK -> HLF : _handleListFiles(data, label)
HLF --> OK : stored
end
OK --> CL : { success:true, data, isMock }
deactivate OK
deactivate CL
@enduml
' ==== METHOD: _error(error, command, el, label) =============================
@startuml
title command-executor:_error(error, command, el, label): \n Render error status and return failure object
participant "Caller" as CL
participant "_error(...)" as ERR
participant "_status(...)" as STAT
activate CL
CL -> ERR : error, command, el, label
activate ERR
ERR -> STAT : _status(el, 'ERROR', {action: command.action, details: error.message, label})
STAT --> ERR : rendered
ERR --> CL : { success:false, error: error.message }
deactivate ERR
deactivate CL
@enduml
' ==== METHOD: _status(el, type, data) ======================================
@startuml
title command-executor:_status(el, type, data): \n Append styled status div to message element
participant "Caller" as CL
participant "_status(...)" as STAT
participant "_color(type)" as CLR
participant "DOM" as DOM
activate CL
CL -> STAT : el, type, data
activate STAT
STAT -> CLR : _color(type)
CLR --> STAT : hex color
STAT -> DOM : createElement('div'); set styles (left border color)
DOM --> STAT : div
STAT -> DOM : set textContent `${label||action} — ${type}: details`
DOM --> STAT : ready
STAT -> DOM : appendChild(el, div)
DOM --> STAT : appended
STAT --> CL : (void)
deactivate STAT
deactivate CL
@enduml
' ==== METHOD: _handleGetFile(data, label) ===================================
@startuml
title command-executor:_handleGetFile(data, label): \n Extract content and store for paste-back
participant "Caller" as CL
participant "_handleGetFile(...)" as HGF
participant "Logger" as LOG
activate CL
CL -> HGF : data, label
activate HGF
HGF -> HGF : content = data?.content?.data || data?.content || data?.result?.content?.data || data?.result?.content
alt content missing
HGF -> LOG : warn("get_file response missing content field")
else content present
HGF -> HGF : window.AI_REPO_RESPONSES ||= []; push({label, content})
HGF -> LOG : verbose("File content stored for paste-back", {label, contentLength})
end
HGF --> CL : (void)
deactivate HGF
deactivate CL
@enduml
' ==== METHOD: _handleListFiles(data, label) =================================
@startuml
title command-executor:_handleListFiles(data, label): \n Build fenced listing of files and store for paste-back
participant "Caller" as CL
participant "_handleListFiles(...)" as HLF
participant "Logger" as LOG
activate CL
CL -> HLF : data, label
activate HLF
HLF -> HLF : files = data?.files || data?.result?.files
alt files is Array
HLF -> HLF : listing = "```text\\n" + map(files)->path/name + "\\n```"
HLF -> HLF : window.AI_REPO_RESPONSES ||= []; push({label, content: listing})
HLF -> LOG : verbose("File listing stored", {label, fileCount: files.length})
else invalid files
HLF -> LOG : warn("list_files response missing files array")
end
HLF --> CL : (void)
deactivate HLF
deactivate CL
@enduml
' ==== METHOD: delay(ms) =====================================================
@startuml
title command-executor:delay(ms): \n Resolve after a timeout
participant "Caller" as CL
participant "delay(ms)" as DLY
participant "Timer" as TMR
activate CL
CL -> DLY : ms
activate DLY
DLY -> TMR : setTimeout(ms)
TMR --> DLY : wake
DLY --> CL : (void)
deactivate DLY
deactivate CL
@enduml
' ==== LEGEND ===============================================================
@startuml
legend bottom
== command-executor 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 internal method.
• 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 method or subsystem as a participant
(e.g., "execute()", "_api()", "_getBridgeKey()", "_success()", "_error()", "_status()", "_handleGetFile()", "_handleListFiles()", "GM_xmlhttpRequest", "JSON", "Config", "Logger", "Timer", "DOM", "Prompt", "Confirm").
4) Prefer simple messages; Use --> for returns; -> for calls.
5) Use activate/deactivate as you see fit for clarity (no strict rule).
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/method.
B) One partition per function; soft background color; terminate branches with 'kill'.
C) Keep wording aligned with code (e.g., exact error text patterns, parameter names).
• Color palette (soft pastels)
• Use --> for returns; -> for calls.
• Participants use quoted method names for internals (e.g., "execute()"), and plain nouns for systems ("JSON", "localStorage", "GM_xmlhttpRequest", "Prompt", "Confirm").
• Keep this legend at the end of the file to standardize edits.
endlegend
@enduml