diff --git a/src/pyob/autoreviewer.py b/src/pyob/autoreviewer.py
index 8731c56..a854132 100644
--- a/src/pyob/autoreviewer.py
+++ b/src/pyob/autoreviewer.py
@@ -344,16 +344,18 @@ def run_pipeline(self, current_iteration: int):
logger.info(
f"Found pending {PR_FILE_NAME} and/or {FEATURE_FILE_NAME} from a previous run."
)
- proposals_handled = self._handle_pending_proposals(
+ proposals_outcome = self._handle_pending_proposals(
"Hit ENTER to PROCEED, type 'SKIP' to ignore",
allow_delete=True,
)
- if not proposals_handled:
+ if proposals_outcome == "SKIPPED":
logger.info(
"Pending proposals were not applied or deleted. Halting current pipeline iteration to await user action."
)
return
- changes_made = True
+ elif proposals_outcome == "APPLIED":
+ changes_made = True
+ # If proposals_outcome is "DELETED", changes_made remains False, allowing the scan to proceed.
if not changes_made:
logger.info("==================================================")
diff --git a/src/pyob/cascade_queue_handler.py b/src/pyob/cascade_queue_handler.py
index 3cffc5f..08a2fc7 100644
--- a/src/pyob/cascade_queue_handler.py
+++ b/src/pyob/cascade_queue_handler.py
@@ -6,6 +6,11 @@ class CascadeControllerProtocol(Protocol):
def add_to_cascade_queue(self, item: str) -> None: ...
def remove_cascade_queue_item(self, item_id: str) -> None: ...
def move_cascade_queue_item(self, item_id: str, direction: str) -> None: ...
+ def process_next_cascade_item(
+ self,
+ ) -> (
+ str | None
+ ): ... # Returns the ID of the processed item, or None if queue is empty
class CascadeQueueHandler:
@@ -27,6 +32,26 @@ def handle_add_to_cascade_queue(self, item: str):
except Exception as e:
return json.dumps({"error": f"Internal server error: {str(e)}"}).encode()
+ def handle_process_next_cascade_item(self):
+ try:
+ processed_item_id = self.controller.process_next_cascade_item()
+ if processed_item_id:
+ return json.dumps(
+ {"message": f"Item '{processed_item_id}' processed successfully"}
+ ).encode()
+ else:
+ return json.dumps(
+ {"message": "Cascade queue is empty, no item to process"}
+ ).encode()
+ except AttributeError:
+ return json.dumps(
+ {
+ "error": "Controller method 'process_next_cascade_item' not found. Ensure entrance.py is updated."
+ }
+ ).encode()
+ except Exception as e:
+ return json.dumps({"error": f"Internal server error: {str(e)}"}).encode()
+
def handle_remove_from_cascade_queue(self, item_id: str):
try:
self.controller.remove_cascade_queue_item(item_id)
diff --git a/src/pyob/dashboard_html.py b/src/pyob/dashboard_html.py
index d8a85d8..ade7d82 100644
--- a/src/pyob/dashboard_html.py
+++ b/src/pyob/dashboard_html.py
@@ -61,6 +61,7 @@
+
@@ -122,10 +123,22 @@
document.getElementById('iteration').innerText = data.iteration || "0";
document.getElementById('ledger').innerText = (data.ledger_stats?.definitions || 0) + " SYM";
document.getElementById('queue-count').innerText = data.cascade_queue?.length || "0";
@@ -292,10 +305,10 @@
async function reviewPatch(patchId, action) {
try {
if (action === 'approved') {
- await fetch('/api/approve_patch', {
+ await fetch('/api/review_patch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ patch_id: patchId })
+ body: JSON.stringify({ patch_id: patchId, action: 'approved' })
});
} else {
await fetch('/api/review_patch', {
diff --git a/src/pyob/dashboard_utils.js b/src/pyob/dashboard_utils.js
new file mode 100644
index 0000000..77abe0d
--- /dev/null
+++ b/src/pyob/dashboard_utils.js
@@ -0,0 +1,290 @@
+async function updateStats() {
+ try {
+ const response = await fetch('/api/status');
+ const data = await response.json();
+ document.getElementById('iteration').innerText = data.iteration || "0";
+ document.getElementById('ledger').innerText = (data.ledger_stats?.definitions || 0) + " SYM";
+ document.getElementById('queue-count').innerText = data.cascade_queue?.length || "0";
+ const pill = document.getElementById('status-pill');
+ const isEvolving = data.cascade_queue?.length > 0 || data.patches_count > 0;
+ pill.innerText = isEvolving ? "EVOLVING" : "STABLE";
+ pill.className = isEvolving ? "status-pill evolving" : "status-pill";
+ document.getElementById('memory').value = data.memory || "Brain empty.";
+ document.getElementById('history').innerText = data.history || "No logs.";
+ // Handle structured analysis data for interactive issues
+ const analysisContainer = document.getElementById('analysis');
+ analysisContainer.innerHTML = ''; // Clear existing content
+ const filterText = document.getElementById('analysisFilter').value.toLowerCase();
+
+ if (Array.isArray(data.analysis) && data.analysis.length > 0) {
+ const filteredAnalysis = data.analysis.filter(issue =>
+ issue.description.toLowerCase().includes(filterText)
+ );
+ filteredAnalysis.forEach(issue => {
+ const issueElement = document.createElement('div');
+ issueElement.style.marginBottom = '8px';
+ issueElement.style.padding = '8px';
+ issueElement.style.background = '#00000066';
+ issueElement.style.borderRadius = '4px';
+ issueElement.style.display = 'flex';
+ issueElement.style.alignItems = 'center';
+ issueElement.style.justifyContent = 'space-between';
+ issueElement.style.fontFamily = 'JetBrains Mono';
+ issueElement.style.fontSize = '0.8em';
+ issueElement.style.color = '#ced4e0';
+
+ const statusColor = issue.status === 'acknowledged' ? 'var(--dim)' : 'var(--err)';
+ const statusText = issue.status === 'acknowledged' ? 'ACKNOWLEDGED' : 'PENDING';
+
+ issueElement.innerHTML = `
+ ${issue.description}
+ ${statusText}
+
+ `;
+ analysisContainer.appendChild(issueElement);
+ });
+ } else {
+ analysisContainer.innerText = "No issues found."; // Always display this if no issues or not an array
+ }
+
+ // Update chart after fetching data
+ updateChart(data);
+
+ const queueDiv = document.getElementById('queue');
+ queueDiv.innerHTML = '';
+ if (data.cascade_queue && data.cascade_queue.length > 0) {
+ data.cascade_queue.forEach((item, index) => {
+ const itemElement = document.createElement('div');
+ itemElement.className = 'queue-item';
+
+ const textSpan = document.createElement('span');
+ textSpan.style.flexGrow = '1';
+ textSpan.innerText = item; // Use innerText for safety
+
+ const buttonDiv = document.createElement('div');
+ buttonDiv.style.display = 'flex';
+ buttonDiv.style.gap = '5px';
+
+ const moveUpBtn = document.createElement('button');
+ moveUpBtn.className = 'move-btn';
+ moveUpBtn.innerHTML = '▲';
+ moveUpBtn.addEventListener('click', () => moveQueueItem(item, 'up'));
+
+ const moveDownBtn = document.createElement('button');
+ moveDownBtn.className = 'move-btn';
+ moveDownBtn.innerHTML = '▼';
+ moveDownBtn.addEventListener('click', () => moveQueueItem(item, 'down'));
+
+ const removeBtn = document.createElement('button');
+ removeBtn.className = 'remove-btn';
+ removeBtn.innerHTML = 'X';
+ removeBtn.addEventListener('click', () => removeQueueItem(item));
+
+ buttonDiv.appendChild(moveUpBtn);
+ buttonDiv.appendChild(moveDownBtn);
+ buttonDiv.appendChild(removeBtn);
+
+ itemElement.appendChild(textSpan);
+ itemElement.appendChild(buttonDiv);
+ queueDiv.appendChild(itemElement);
+ });
+ } else {
+ queueDiv.innerText = "EMPTY";
+ }
+
+ await updatePendingPatches();
+ } catch (e) { document.getElementById('status-pill').innerText = "OFFLINE"; console.error("Error updating stats:", e); }
+}
+
+async function acknowledgeIssue(issueId) {
+ try {
+ const response = await fetch(`/api/acknowledge-issue/${issueId}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ status: 'acknowledged' })
+ });
+ if (response.ok) {
+ await updateStats(); // Refresh dashboard to show updated status
+ } else {
+ console.error(`Failed to acknowledge issue ${issueId}:`, await response.text());
+ alert('Error acknowledging issue.');
+ }
+ } catch (e) {
+ console.error(`Failed to acknowledge issue ${issueId}:`, e);
+ alert('Network error acknowledging issue.');
+ }
+}
+
+async function updatePendingPatches() {
+ try {
+ const response = await fetch('/api/pending_patches');
+ const data = await response.json();
+ const patchesDiv = document.getElementById('pending-patches');
+ patchesDiv.innerHTML = '';
+
+ if (data.patches && data.patches.length > 0) {
+ data.patches.forEach(patch => {
+ const patchElement = document.createElement('div');
+ patchElement.style.marginBottom = '10px';
+ patchElement.innerHTML = `
+ Patch ID: ${patch.id}
+ File: ${patch.file || 'N/A'}
+ Description: ${patch.description || 'No description'}
+
+
+
+ `;
+ patchesDiv.appendChild(patchElement);
+ });
+ } else {
+ patchesDiv.innerText = "No pending patches.";
+ }
+ } catch (e) {
+ console.error("Failed to fetch pending patches:", e);
+ document.getElementById('pending-patches').innerText = "Error loading patches.";
+ }
+}
+
+async function reviewPatch(patchId, action) {
+ try {
+ if (action === 'approved') {
+ await fetch('/api/review_patch', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ patch_id: patchId, action: 'approved' })
+ });
+ } else {
+ await fetch('/api/review_patch', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ patch_id: patchId, action: action })
+ });
+ }
+ await updateStats();
+ } catch (e) {
+ console.error(`Failed to ${action} patch ${patchId}:`, e);
+ }
+}
+
+async function saveMemory() {
+ const memoryContent = document.getElementById('memory').value;
+ try {
+ const response = await fetch('/api/update_memory', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: memoryContent })
+ });
+ if (response.ok) {
+ alert('Logic Memory saved successfully!');
+ await updateStats();
+ }
+ } catch (e) {
+ console.error("Failed to save Logic Memory:", e);
+ }
+}
+
+async function addCascadeItem() {
+ const item = document.getElementById('cascadeItem').value;
+ if (!item) return;
+ try {
+ const response = await fetch('/api/cascade_queue/add', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ item: item })
+ });
+ if (response.ok) {
+ document.getElementById('cascadeItem').value = '';
+ await updateStats();
+ }
+ } catch (e) {
+ console.error("Failed to add item to cascade queue:", e);
+ }
+}
+
+async function moveQueueItem(itemId, direction) {
+ try {
+ await fetch('/api/cascade_queue/move', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ item_id: itemId, direction: direction })
+ });
+ await updateStats();
+ } catch (e) {
+ console.error(`Failed to move item ${itemId} ${direction}:`, e);
+ }
+}
+
+async function removeQueueItem(itemId) {
+ if (!confirm(`Are you sure you want to remove "${itemId.replace(/"/g, '\\"')}" from the queue?`)) return;
+ try {
+ await fetch('/api/cascade_queue/remove', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ item_id: itemId })
+ });
+ await updateStats();
+ } catch (e) {
+ console.error(`Failed to remove item ${itemId}:`, e);
+ }
+}
+
+async function viewPatchDiff(patchId) {
+ try {
+ const response = await fetch(`/api/patch_diff/${patchId}`);
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
+ const data = await response.json();
+ document.getElementById('diffModalPatchId').innerText = `(ID: ${patchId})`;
+ document.getElementById('diffContent').innerText = data.diff || "No diff available.";
+ openDiffModal();
+ } catch (e) {
+ console.error(`Failed to fetch diff for patch ${patchId}:`, e);
+ alert(`Error viewing diff: ${e.message}`);
+ }
+}
+
+function openDiffModal() {
+ document.getElementById('diffModal').style.display = 'block';
+}
+
+function closeDiffModal() {
+ document.getElementById('diffModal').style.display = 'none';
+ document.getElementById('diffContent').innerText = ''; // Clear content on close
+ document.getElementById('diffModalPatchId').innerText = '';
+}
+
+function updateChart(data) {
+ const ctx = document.getElementById('statsChart').getContext('2d');
+ const iterationData = data.iterationHistory ? data.iterationHistory.map((val, index) => { return { x: index, y: val }; }) : [];
+
+ if (window.statsChart) {
+ window.statsChart.data.labels = iterationData.map(d => d.x);
+ window.statsChart.data.datasets[0].data = iterationData.map(d => d.y);
+ window.statsChart.update();
+ } else {
+ window.statsChart = new Chart(ctx, {
+ type: 'line',
+ data: {
+ labels: iterationData.map(d => d.x),
+ datasets: [{
+ label: 'Iterations Over Time',
+ data: iterationData.map(d => d.y),
+ borderColor: 'rgb(75, 192, 192)',
+ tension: 0.1
+ }]
+ },
+ options: {
+ scales: {
+ y: {
+ beginAtZero: true
+ }
+ }
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/pyob/evolution_mixins.py b/src/pyob/evolution_mixins.py
index ba0d0d6..efa089d 100644
--- a/src/pyob/evolution_mixins.py
+++ b/src/pyob/evolution_mixins.py
@@ -233,7 +233,7 @@ def build_initial_analysis(self):
lambda t: len(t) > 5,
context="Project Genesis",
).strip()
- content = f"# Project Analysis\n\n**Project Summary:**\n{p_summary}\n\n-----n\n## File Directory\n\n"
+ content = f"# Project Analysis\n\n**Project Summary:**\n{p_summary}\n\n-----\n## File Directory\n\n"
file_structures = {}
for f_path in all_files:
diff --git a/src/pyob/stats_updater.py b/src/pyob/stats_updater.py
index e326836..a9bc2b7 100644
--- a/src/pyob/stats_updater.py
+++ b/src/pyob/stats_updater.py
@@ -27,7 +27,7 @@ async def review_patch(self, patch_id, action):
await fetch_api(
"/api/review_patch",
method="POST",
- data=json.dumps({"patch_id": patch_id, "action": action}),
+ data={"patch_id": patch_id, "action": action},
)
except Exception as e:
print(f"Failed to {action} patch {patch_id}: {e}")
@@ -69,3 +69,15 @@ async def remove_queue_item(self, item_id):
)
except Exception as e:
print(f"Failed to remove item {item_id}: {e}")
+
+ async def fetch_cascade_queue(self):
+ """
+ Fetches the current list of items in the cascade queue.
+ """
+ try:
+ response = await fetch_api("/api/cascade_queue")
+ data = await response.json()
+ return data
+ except Exception as e:
+ print(f"Failed to fetch cascade queue: {e}")
+ return None