Compare commits

..

12 Commits

Author SHA1 Message Date
Alexander Whitestone
60d82da70a fix: guard memory_mine.py against git commit shell injection (closes #1430)
Some checks failed
Review Approval Gate / verify-review (pull_request) Failing after 10s
CI / test (pull_request) Failing after 57s
CI / validate (pull_request) Failing after 57s
2026-04-21 23:28:44 -04:00
324cdb0d26 Merge PR #1684
Some checks failed
Deploy Nexus / deploy (push) Failing after 7s
Staging Verification Gate / verify-staging (push) Failing after 13s
Merge PR #1684: portal hot-reload
2026-04-22 03:15:13 +00:00
b4473267e0 Merge PR #1685
Some checks failed
Deploy Nexus / deploy (push) Failing after 5s
Staging Verification Gate / verify-staging (push) Failing after 6s
Merge PR #1685: test collection errors
2026-04-22 03:15:07 +00:00
ed733d4eea Merge PR #1686
Some checks failed
Deploy Nexus / deploy (push) Failing after 3s
Staging Verification Gate / verify-staging (push) Has been cancelled
Merge PR #1686: A11Y text contrast
2026-04-22 03:15:03 +00:00
7c9f4310d0 Merge branch 'main' into fix/1536-hot-reload
Some checks failed
Review Approval Gate / verify-review (pull_request) Failing after 8s
CI / test (pull_request) Failing after 1m8s
CI / validate (pull_request) Failing after 1m7s
2026-04-22 01:12:04 +00:00
2016a7e076 Merge branch 'main' into fix/1509-tests
Some checks failed
Review Approval Gate / verify-review (pull_request) Failing after 10s
CI / test (pull_request) Failing after 1m9s
CI / validate (pull_request) Failing after 1m14s
2026-04-22 01:11:58 +00:00
b6ee9ba01b Merge branch 'main' into mimo/code/issue-702
Some checks failed
Review Approval Gate / verify-review (pull_request) Failing after 10s
CI / test (pull_request) Failing after 1m10s
CI / validate (pull_request) Failing after 1m13s
2026-04-22 01:11:53 +00:00
15b9a4398c Merge branch 'main' into fix/1536-hot-reload
Some checks failed
Review Approval Gate / verify-review (pull_request) Failing after 8s
CI / test (pull_request) Failing after 1m7s
CI / validate (pull_request) Failing after 1m11s
2026-04-22 01:05:01 +00:00
3f7277d920 Merge branch 'main' into fix/1509-tests
Some checks failed
Review Approval Gate / verify-review (pull_request) Failing after 12s
CI / test (pull_request) Failing after 1m10s
CI / validate (pull_request) Failing after 1m12s
2026-04-22 01:04:55 +00:00
cb944be172 Merge branch 'main' into mimo/code/issue-702
Some checks failed
CI / test (pull_request) Failing after 1m10s
Review Approval Gate / verify-review (pull_request) Failing after 12s
CI / validate (pull_request) Failing after 1m8s
2026-04-22 01:04:50 +00:00
Alexander Whitestone
ec2ed3c62f fix: test collection errors in bannerlord and evennia tests (closes #1509)
Some checks failed
CI / test (pull_request) Failing after 1m22s
CI / validate (pull_request) Failing after 1m3s
Review Approval Gate / verify-review (pull_request) Failing after 4s
- nexus/bannerlord_harness.py: fixed bare import to absolute
- nexus/evennia_ws_bridge.py: added clean_lines, normalize_event,
  parse_room_output functions that tests expected

Test results:
- test_bannerlord_harness.py: 39 tests collected
- test_evennia_ws_bridge.py: 5 tests collected
2026-04-21 08:08:49 -04:00
Alexander Whitestone
11175e72c0 feat: portal hot-reload from portals.json without server restart (closes #1536)
Some checks failed
CI / test (pull_request) Failing after 1m20s
CI / validate (pull_request) Failing after 1m24s
Review Approval Gate / verify-review (pull_request) Failing after 9s
2026-04-21 08:01:56 -04:00
7 changed files with 174 additions and 20 deletions

3
app.js
View File

@@ -734,6 +734,9 @@ async function init() {
const response = await fetch('./portals.json');
const portalData = await response.json();
createPortals(portalData);
// Start portal hot-reload watcher
if (window.PortalHotReload) PortalHotReload.start(5000);
} catch (e) {
console.error('Failed to load portals.json:', e);
addChatMessage('error', 'Portal registry offline. Check logs.');

View File

@@ -32,6 +32,14 @@ from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional
# ── Safety guard ───────────────────────────────────────────────────────
# Prevent accidental execution from git commit messages containing
# code examples with backticks (shell substitution). See issue #1430.
if os.environ.get("GIT_DIR") or os.environ.get("GIT_INDEX_FILE"):
# Running inside a git hook — exit silently to prevent
# shell substitution in commit messages from triggering mining.
sys.exit(0)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",

View File

@@ -397,6 +397,7 @@
<script src="./boot.js"></script>
<script src="./avatar-customization.js"></script>
<script src="./lod-system.js"></script>
<script src="./portal-hot-reload.js"></script>
<script>
function openMemoryFilter() { renderFilterList(); document.getElementById('memory-filter').style.display = 'flex'; }
function closeMemoryFilter() { document.getElementById('memory-filter').style.display = 'none'; }

View File

@@ -29,7 +29,7 @@ from typing import Any, Callable, Optional
import websockets
from bannerlord_trace import BannerlordTraceLogger
from nexus.bannerlord_trace import BannerlordTraceLogger
# ═══════════════════════════════════════════════════════════════════════════
# CONFIGURATION

View File

@@ -304,6 +304,43 @@ async def inject_event(event_type: str, ws_url: str, **kwargs):
sys.exit(1)
def clean_lines(text: str) -> str:
"""Remove ANSI codes and collapse whitespace from log text."""
import re
text = strip_ansi(text)
text = re.sub(r'\s+', ' ', text).strip()
return text
def normalize_event(event: dict) -> dict:
"""Normalize an Evennia event dict to standard format."""
return {
"type": event.get("type", "unknown"),
"actor": event.get("actor", event.get("name", "")),
"room": event.get("room", event.get("location", "")),
"message": event.get("message", event.get("text", "")),
"timestamp": event.get("timestamp", ""),
}
def parse_room_output(text: str) -> dict:
"""Parse Evennia room output into structured data."""
import re
lines = text.strip().split("\n")
result = {"name": "", "description": "", "exits": [], "objects": []}
if lines:
result["name"] = strip_ansi(lines[0]).strip()
if len(lines) > 1:
result["description"] = strip_ansi(lines[1]).strip()
for line in lines[2:]:
line = strip_ansi(line).strip()
if line.startswith("Exits:"):
result["exits"] = [e.strip() for e in line[6:].split(",") if e.strip()]
elif line.startswith("You see:"):
result["objects"] = [o.strip() for o in line[8:].split(",") if o.strip()]
return result
def main():
parser = argparse.ArgumentParser(description="Evennia -> Nexus WebSocket Bridge")
sub = parser.add_subparsers(dest="mode")

105
portal-hot-reload.js Normal file
View File

@@ -0,0 +1,105 @@
/**
* Portal Hot-Reload for The Nexus
*
* Watches portals.json for changes and hot-reloads portal list
* without server restart. Existing connections unaffected.
*
* Usage:
* PortalHotReload.start(intervalMs);
* PortalHotReload.stop();
* PortalHotReload.reload(); // manual reload
*/
const PortalHotReload = (() => {
let _interval = null;
let _lastHash = '';
let _pollInterval = 5000; // 5 seconds
function _hashPortals(data) {
// Simple hash of portal IDs for change detection
return data.map(p => p.id || p.name).sort().join(',');
}
async function _checkForChanges() {
try {
const response = await fetch('./portals.json?t=' + Date.now());
if (!response.ok) return;
const data = await response.json();
const hash = _hashPortals(data);
if (hash !== _lastHash) {
console.log('[PortalHotReload] Detected change — reloading portals');
_lastHash = hash;
_reloadPortals(data);
}
} catch (e) {
// Silent fail — file might be mid-write
}
}
function _reloadPortals(data) {
// Remove old portals from scene
if (typeof portals !== 'undefined' && Array.isArray(portals)) {
portals.forEach(p => {
if (p.group && typeof scene !== 'undefined' && scene) {
scene.remove(p.group);
}
});
portals.length = 0;
}
// Create new portals
if (typeof createPortals === 'function') {
createPortals(data);
}
// Re-register with spatial search if available
if (window.SpatialSearch && typeof portals !== 'undefined') {
portals.forEach(p => {
if (p.config && p.config.name && p.group) {
SpatialSearch.register('portal', p, p.config.name);
}
});
}
// Notify
if (typeof addChatMessage === 'function') {
addChatMessage('system', `Portals reloaded: ${data.length} portals active`);
}
console.log(`[PortalHotReload] Reloaded ${data.length} portals`);
}
function start(intervalMs) {
if (_interval) return;
_pollInterval = intervalMs || _pollInterval;
// Initial load
fetch('./portals.json').then(r => r.json()).then(data => {
_lastHash = _hashPortals(data);
}).catch(() => {});
_interval = setInterval(_checkForChanges, _pollInterval);
console.log(`[PortalHotReload] Watching portals.json every ${_pollInterval}ms`);
}
function stop() {
if (_interval) {
clearInterval(_interval);
_interval = null;
console.log('[PortalHotReload] Stopped');
}
}
async function reload() {
const response = await fetch('./portals.json?t=' + Date.now());
const data = await response.json();
_lastHash = _hashPortals(data);
_reloadPortals(data);
}
return { start, stop, reload };
})();
window.PortalHotReload = PortalHotReload;

View File

@@ -1,36 +1,36 @@
{
"generated_at": "2026-04-17T02:24:33.139525+00:00",
"generated_at": "2026-04-11T01:14:54.632326+00:00",
"repo": "Timmy_Foundation/the-nexus",
"git": {
"commit": "b9bbcae29879bbf460d63d77a2c141668fbc9028",
"branch": "fix/1513",
"remote": "https://c2dbfd059b793a57df68837d0b85bc3c3bf4eec4@forge.alexanderwhitestone.com/Timmy_Foundation/the-nexus.git",
"dirty": false
"commit": "d408d2c365a9efc0c1e3a9b38b9cc4eed75695c5",
"branch": "mimo/build/issue-686",
"remote": "https://forge.alexanderwhitestone.com/Timmy_Foundation/the-nexus.git",
"dirty": true
},
"files": {
"index.html": {
"sha256": "688938028c0fae734fddae8527bcfb7acd1702cab1fc65f6fcef5b066596eab3",
"size": 21079
"sha256": "71ba27afe8b6b42a09efe09d2b3017599392ddc3bc02543b31c2277dfb0b82cc",
"size": 25933
},
"app.js": {
"sha256": "2ab33d147ceec755e5ee7d3cc35ae65593cd238b2b2d1d9e73af5227ba33dfe2",
"size": 141038
"sha256": "2b765a724a0fcda29abd40ba921bc621d2699f11d0ba14cf1579cbbdafdc5cd5",
"size": 132902
},
"style.css": {
"sha256": "a7228e516f8210bac580a1caa2f6223ec9ec533e46c58b585a7cbc53bc047fba",
"size": 60727
"sha256": "cd3068d03eed6f52a00bbc32cfae8fba4739b8b3cb194b3ec09fd747a075056d",
"size": 44198
},
"gofai_worker.js": {
"sha256": "01d1444b1e4c899a7579aa4e5624d5a0683e10b54a924005a7003534c607a500",
"size": 1925
"sha256": "d292f110aa12a8aa2b16b0c2d48e5b4ce24ee15b1cffb409ab846b1a05a91de2",
"size": 969
},
"server.py": {
"sha256": "79292dfd6955020f5d8ae368e8ce61a4831255dc1a935cbf8f51b35eaa2e4498",
"size": 4389
"sha256": "e963cc9715accfc8814e3fe5c44af836185d66740d5a65fd0365e9c629d38e05",
"size": 4185
},
"portals.json": {
"sha256": "82f91c3b8707d197e6295e594e616e92f9e215399a7181a130874e23381efa9f",
"size": 6399
"sha256": "889a5e0f724eb73a95f960bca44bca232150bddff7c1b11f253bd056f3683a08",
"size": 3442
},
"vision.json": {
"sha256": "0e3b5c06af98486bbcb2fc2dc627dc8b7b08aed4c3a4f9e10b57f91e1e8ca6ad",
@@ -41,8 +41,8 @@
"size": 495
},
"nexus/components/spatial-memory.js": {
"sha256": "0945845828b3cfaac6060050bf138463b5108b6c135e4c396751da92a7376534",
"size": 38272
"sha256": "60170f6490ddd743acd6d285d3a1af6cad61fbf8aaef3f679ff4049108eac160",
"size": 32782
},
"nexus/components/session-rooms.js": {
"sha256": "9997a60dda256e38cb4645508bf9e98c15c3d963b696e0080e3170a9a7fa7cf1",