feat(dashboard,adrs): enrich dashboard tabs with domain model parity and resolve consequence schema handling
This commit is contained in:
1 parent
73c3d41c87
commit
f546579596
5 files changed
+798
-143
No files matched your search
+307
-77
@@ -110,21 +110,36 @@ function showInspector(nodeId) {
|
||||
const titleEl = document.getElementById("inspector-title");
|
||||
if (titleEl)
|
||||
titleEl.innerText = entity.name;
|
||||
const typeEl = document.getElementById("inspector-type");
|
||||
if (typeEl)
|
||||
typeEl.innerText = entity.entity_type;
|
||||
const nsEl = document.getElementById("inspector-namespace");
|
||||
if (nsEl)
|
||||
nsEl.innerText = entity.namespace || "global";
|
||||
const obsHtml = (entity.observations || []).map((o) => `<li>${o}</li>`).join("");
|
||||
const metaEl = document.querySelector(".inspector-meta");
|
||||
if (metaEl) {
|
||||
const createdStr = entity.created_at ? new Date(entity.created_at * 1000).toISOString().replace("T", " ").substring(0, 19) : "";
|
||||
const updatedStr = entity.updated_at ? new Date(entity.updated_at * 1000).toISOString().replace("T", " ").substring(0, 19) : "";
|
||||
metaEl.innerHTML = `
|
||||
<div><strong>Type:</strong> <span id="inspector-type">${escapeHtml(entity.entity_type)}</span></div>
|
||||
<div><strong>Namespace:</strong> <span id="inspector-namespace">${escapeHtml(entity.namespace || "global")}</span></div>
|
||||
${entity.repo_name ? `<div><strong>Repository:</strong> <span style="font-family:monospace;">${escapeHtml(entity.repo_name)}</span></div>` : ""}
|
||||
${entity.file_path ? `<div><strong>File:</strong> <span style="font-family:monospace; word-break:break-all;">${escapeHtml(entity.file_path)}</span></div>` : ""}
|
||||
${entity.git_branch ? `<div><strong>Branch:</strong> <span style="font-family:monospace;">${escapeHtml(entity.git_branch)}</span></div>` : ""}
|
||||
${createdStr ? `<div><strong>Created:</strong> <span>${escapeHtml(createdStr)}</span></div>` : ""}
|
||||
${updatedStr ? `<div><strong>Updated:</strong> <span>${escapeHtml(updatedStr)}</span></div>` : ""}
|
||||
`;
|
||||
} else {
|
||||
const typeEl = document.getElementById("inspector-type");
|
||||
if (typeEl)
|
||||
typeEl.innerText = entity.entity_type;
|
||||
const nsEl = document.getElementById("inspector-namespace");
|
||||
if (nsEl)
|
||||
nsEl.innerText = entity.namespace || "global";
|
||||
}
|
||||
const obsHtml = (entity.observations || []).map((o) => `<li>${escapeHtml(o)}</li>`).join("");
|
||||
const obsEl = document.getElementById("inspector-observations");
|
||||
if (obsEl)
|
||||
obsEl.innerHTML = obsHtml || "<li><em>No observations recorded.</em></li>";
|
||||
const rels = rawRelations.filter((r) => r.from === nodeId || r.to === nodeId);
|
||||
const relHtml = rels.map((r) => {
|
||||
if (r.from === nodeId)
|
||||
return `<li>→ <em>${r.relation_type}</em> → <strong>${r.to}</strong></li>`;
|
||||
return `<li>← <em>${r.relation_type}</em> ← <strong>${r.from}</strong></li>`;
|
||||
return `<li>→ <em>${escapeHtml(r.relation_type)}</em> → <strong>${escapeHtml(r.to)}</strong></li>`;
|
||||
return `<li>← <em>${escapeHtml(r.relation_type)}</em> ← <strong>${escapeHtml(r.from)}</strong></li>`;
|
||||
}).join("");
|
||||
const relEl = document.getElementById("inspector-relations");
|
||||
if (relEl)
|
||||
@@ -142,10 +157,10 @@ function toggleFilter(type) {
|
||||
updateGraphData();
|
||||
}
|
||||
function renderFilters() {
|
||||
const container = document.getElementById("graph-filters");
|
||||
if (!container)
|
||||
const container2 = document.getElementById("graph-filters");
|
||||
if (!container2)
|
||||
return;
|
||||
container.innerHTML = Array.from(allTypes).sort().map((type) => {
|
||||
container2.innerHTML = Array.from(allTypes).sort().map((type) => {
|
||||
const isActive = activeFilters.has(type);
|
||||
const color = getColorForType(type).bg || getColorForType(type).background;
|
||||
return `<button class="filter-chip ${isActive ? "active" : ""}" onclick="toggleFilter('${type}')">
|
||||
@@ -212,8 +227,8 @@ async function loadGraph() {
|
||||
rawRelations = data.relations || [];
|
||||
updateGraphData();
|
||||
if (!network) {
|
||||
const container = document.getElementById("network-container");
|
||||
if (!container)
|
||||
const container2 = document.getElementById("network-container");
|
||||
if (!container2)
|
||||
return;
|
||||
const options = {
|
||||
nodes: { shape: "dot", size: 16, font: { size: 12 } },
|
||||
@@ -242,7 +257,7 @@ async function loadGraph() {
|
||||
},
|
||||
interaction: { hover: true, tooltipDelay: 100, zoomView: false }
|
||||
};
|
||||
network = new vis.Network(container, { nodes: nodesData, edges: edgesData }, options);
|
||||
network = new vis.Network(container2, { nodes: nodesData, edges: edgesData }, options);
|
||||
network.once("stabilizationIterationsDone", function() {
|
||||
network.setOptions({ physics: { enabled: false } });
|
||||
});
|
||||
@@ -252,7 +267,7 @@ async function loadGraph() {
|
||||
network.on("dragEnd", function() {
|
||||
network.setOptions({ physics: { enabled: false } });
|
||||
});
|
||||
container.addEventListener("wheel", function(event) {
|
||||
container2.addEventListener("wheel", function(event) {
|
||||
event.preventDefault();
|
||||
const direction = event.deltaY > 0 ? -0.15 : 0.15;
|
||||
zoomGraph(direction);
|
||||
@@ -268,9 +283,9 @@ async function loadGraph() {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Failed to load graph", err);
|
||||
const container = document.getElementById("network-container");
|
||||
if (container) {
|
||||
container.innerHTML = '<div style="color:#e74c3c;padding:20px;font-weight:bold;">Error loading graph: ' + err.message + "</div>";
|
||||
const container2 = document.getElementById("network-container");
|
||||
if (container2) {
|
||||
container2.innerHTML = '<div style="color:#e74c3c;padding:20px;font-weight:bold;">Error loading graph: ' + err.message + "</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,20 +340,46 @@ function buildTaskTreeHTML(tasks, parentId, depth = 0) {
|
||||
isBlocked = true;
|
||||
}
|
||||
}
|
||||
const prio = (t.priority || "").toLowerCase();
|
||||
let prioBadge = "";
|
||||
if (prio === "critical" || prio === "urgent") {
|
||||
prioBadge = `<span style="background:rgba(231,76,60,0.18); color:#e74c3c; border:1px solid rgba(231,76,60,0.5); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">CRITICAL</span>`;
|
||||
} else if (prio === "high") {
|
||||
prioBadge = `<span style="background:rgba(230,126,34,0.18); color:#e67e22; border:1px solid rgba(230,126,34,0.5); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">HIGH</span>`;
|
||||
} else if (prio === "medium" || prio === "med") {
|
||||
prioBadge = `<span style="background:rgba(52,152,219,0.15); color:#3498db; border:1px solid rgba(52,152,219,0.4); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">MEDIUM</span>`;
|
||||
} else if (prio === "low") {
|
||||
prioBadge = `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.7em; padding:2px 6px; border-radius:4px; text-transform:uppercase;">LOW</span>`;
|
||||
}
|
||||
html += `<div class="${cardClass}" style="margin-left: ${depth * 20}px; margin-bottom: 10px;">`;
|
||||
html += `<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px; margin-bottom:4px;">`;
|
||||
html += `<div style="display:flex; align-items:center; gap:6px; flex-wrap:wrap;">`;
|
||||
if (prioBadge)
|
||||
html += prioBadge;
|
||||
if (t.assigned_agent) {
|
||||
html += `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.7em; padding:2px 6px; border-radius:4px;">\uD83E\uDD16 ${escapeHtml(t.assigned_agent)}</span>`;
|
||||
}
|
||||
if (isBlocked && !isCompleted && !isCancelled) {
|
||||
html += `<div style="background:#e74c3c; color:white; font-size:0.75em; padding:2px 6px; border-radius:3px; display:inline-block; margin-bottom:6px; font-weight:bold;">[BLOCKED]</div>`;
|
||||
if (blockers.length > 0) {
|
||||
html += `<div style="font-size:0.8em; color:#e74c3c; margin-bottom:6px;">Waiting on: ${blockers.join(", ")}</div>`;
|
||||
}
|
||||
html += `<span style="background:#e74c3c; color:white; font-size:0.7em; padding:2px 6px; border-radius:3px; font-weight:bold;">BLOCKED</span>`;
|
||||
}
|
||||
if (isCancelled) {
|
||||
html += `<div style="background:#95a5a6; color:white; font-size:0.75em; padding:2px 6px; border-radius:3px; display:inline-block; margin-bottom:6px; font-weight:bold;">[CANCELLED]</div>`;
|
||||
html += `<span style="background:#95a5a6; color:white; font-size:0.7em; padding:2px 6px; border-radius:3px; font-weight:bold;">CANCELLED</span>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
if (!isCompleted && !isCancelled && !isBlocked) {
|
||||
html += `<button class="complete-btn" style="position:static;" onclick="completeTask('${escapeHtml(t.id)}')" title="Mark Completed">✓</button>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
if (isBlocked && blockers.length > 0 && !isCompleted && !isCancelled) {
|
||||
html += `<div style="font-size:0.8em; color:#e74c3c; margin-bottom:6px;">Waiting on: ${escapeHtml(blockers.join(", "))}</div>`;
|
||||
}
|
||||
html += `<strong>${escapeHtml(t.title)}</strong>`;
|
||||
if (t.description) {
|
||||
html += `<div style="margin-top:5px; font-size:0.9em; color:var(--text-secondary); line-height:1.4;">${escapeHtml(t.description)}</div>`;
|
||||
}
|
||||
if (t.verification_command) {
|
||||
html += `<div style="margin-top:6px; font-size:0.8em; font-family:monospace; background:var(--canvas-bg); padding:3px 8px; border-radius:4px; border:1px solid var(--border-color); color:var(--accent-color);">\uD83E\uDDEA Test: ${escapeHtml(t.verification_command)}</div>`;
|
||||
}
|
||||
const criteria = t.acceptance_criteria || [];
|
||||
if (criteria.length > 0) {
|
||||
html += `<ul style="margin:8px 0 0 0; padding-left:20px; font-size: 0.9em; color: var(--text-secondary);">`;
|
||||
@@ -349,16 +390,21 @@ function buildTaskTreeHTML(tasks, parentId, depth = 0) {
|
||||
unmetCriteria = true;
|
||||
const check = isMet ? "☑" : "☐";
|
||||
const strike = isMet ? "text-decoration: line-through;" : "";
|
||||
html += `<li style="${strike}">${check} ${c.description}</li>`;
|
||||
html += `<li style="${strike}">${check} ${escapeHtml(c.description)}${c.verification_command ? ` <code style="font-size:0.8em; background:var(--canvas-bg); padding:1px 4px; border-radius:3px;">(${escapeHtml(c.verification_command)})</code>` : ""}</li>`;
|
||||
});
|
||||
html += `</ul>`;
|
||||
if (unmetCriteria && !isCompleted && !isCancelled)
|
||||
isBlocked = true;
|
||||
}
|
||||
html += progressHtml;
|
||||
if (!isCompleted && !isCancelled && !isBlocked) {
|
||||
html += `<button class="complete-btn" onclick="completeTask('${t.id}')" title="Mark Completed">✓</button>`;
|
||||
if (t.git_branch || t.repo_name) {
|
||||
html += `<div style="margin-top:8px; font-size:0.75em; display:flex; gap:6px; flex-wrap:wrap; color:var(--text-secondary);">`;
|
||||
if (t.repo_name)
|
||||
html += `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(t.repo_name)}</span>`;
|
||||
if (t.git_branch)
|
||||
html += `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; border:1px solid var(--border-color);">\uD83C\uDF3F ${escapeHtml(t.git_branch)}</span>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
html += progressHtml;
|
||||
if (allChildren.length > 0) {
|
||||
html += `<div style="margin-top: 15px; border-left: 2px solid var(--border-color); padding-left: 10px;">`;
|
||||
html += buildTaskTreeHTML(tasks, t.id, 0);
|
||||
@@ -416,14 +462,14 @@ async function handleSearch(e) {
|
||||
signal: activeSearchAbortController.signal
|
||||
});
|
||||
const data = await res.json();
|
||||
const container = document.getElementById("search-results");
|
||||
if (!container)
|
||||
const container2 = document.getElementById("search-results");
|
||||
if (!container2)
|
||||
return;
|
||||
if (!data.results || data.results.length === 0) {
|
||||
container.innerHTML = '<div style="padding:15px;color:var(--text-secondary);">No results found.</div>';
|
||||
container2.innerHTML = '<div style="padding:15px;color:var(--text-secondary);">No results found.</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = data.results.map((r) => `
|
||||
container2.innerHTML = data.results.map((r) => `
|
||||
<div class="search-result-item" onclick="switchTab('graph-tab'); if(rawEntities['${r.id}']){ showInspector('${r.id}'); if(network){ network.selectNodes(['${r.id}']); network.focus('${r.id}', {scale:1.2, animation:true}); } }" style="cursor:pointer;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start;">
|
||||
<div>
|
||||
@@ -730,23 +776,23 @@ function setupWS() {
|
||||
};
|
||||
}
|
||||
async function loadGenericList(endpoint, containerId, formatter) {
|
||||
const container = document.getElementById(containerId);
|
||||
try {
|
||||
const res = await fetch(endpoint);
|
||||
if (!res.ok) {
|
||||
if (container) {
|
||||
container.innerHTML = `<div style="color:var(--error-color); padding:20px; text-align:center;">⚠️ Failed to load ${endpoint} (${res.status} ${res.statusText})</div>`;
|
||||
if (container2) {
|
||||
container2.innerHTML = `<div style="color:var(--error-color); padding:20px; text-align:center;">⚠️ Failed to load ${endpoint} (${res.status} ${res.statusText})</div>`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
if (!container)
|
||||
const container2 = document.getElementById(containerId);
|
||||
if (!container2)
|
||||
return;
|
||||
if (!data || data.length === 0) {
|
||||
container.innerHTML = '<div style="color:var(--text-secondary); padding:20px; text-align:center;">No items recorded.</div>';
|
||||
container2.innerHTML = '<div style="color:var(--text-secondary); padding:20px; text-align:center;">No items recorded.</div>';
|
||||
return;
|
||||
}
|
||||
container.innerHTML = data.map((item) => {
|
||||
container2.innerHTML = data.map((item) => {
|
||||
const date = item.timestamp ? new Date(item.timestamp * 1000).toLocaleString() : "";
|
||||
return `<div class="task-card" style="border-left-color: #9b59b6;">
|
||||
<div style="font-size:0.75em; color:var(--text-secondary); float:right;">${date}</div>
|
||||
@@ -805,46 +851,172 @@ function loadLedger() {
|
||||
});
|
||||
}
|
||||
function loadTerminal() {
|
||||
loadGenericList("/api/terminal/history", "terminal-container", (item) => `
|
||||
<span style="font-weight:bold; font-size:1.05em; color:var(--text-primary);">${item.command}</span>
|
||||
<span style="margin-left:15px; font-size:0.85em;"><span style="font-weight:bold; color:var(--text-secondary);">Exit Code:</span> <span style="color:${item.exit_code === 0 ? "var(--success-color)" : "var(--error-color)"}; font-weight:bold;">${item.exit_code}</span></span>
|
||||
<div style="margin-top:10px; font-size:0.85em;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; margin-right:8px; border:1px solid var(--border-color);">OS: ${item.os || "Unknown"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">CWD: ${item.cwd || "Unknown"}</span>
|
||||
loadGenericList("/api/terminal/history", "terminal-container", (item) => {
|
||||
const isSuccess = item.exit_code === 0;
|
||||
const statusColor = isSuccess ? "var(--success-color)" : "var(--error-color)";
|
||||
const statusBg = isSuccess ? "rgba(46, 204, 113, 0.12)" : "rgba(231, 76, 60, 0.12)";
|
||||
const durationStr = item.duration_ms != null ? item.duration_ms < 1000 ? `${item.duration_ms}ms` : `${(item.duration_ms / 1000).toFixed(2)}s` : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:10px;">
|
||||
<span style="font-family:monospace; font-weight:bold; font-size:1.05em; color:var(--text-primary); word-break:break-all;">$ ${escapeHtml(item.command)}</span>
|
||||
<div style="display:flex; gap:6px; align-items:center; flex-shrink:0;">
|
||||
${durationStr ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.75em; border:1px solid var(--border-color); color:var(--text-secondary); font-family:monospace;">⏱️ ${escapeHtml(durationStr)}</span>` : ""}
|
||||
<span style="background:${statusBg}; color:${statusColor}; border:1px solid ${statusColor}; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; font-family:monospace;">Exit ${item.exit_code}</span>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
<div style="margin-top:8px; font-size:0.85em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.shell ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); font-weight:600;">\uD83D\uDC1A ${escapeHtml(item.shell)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">OS: ${escapeHtml(item.os || "Unknown")}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color); word-break:break-all;">\uD83D\uDCC2 ${escapeHtml(item.cwd || "Unknown")}</span>
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
${item.invoker ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">\uD83D\uDC64 ${escapeHtml(item.invoker)}</span>` : ""}
|
||||
</div>
|
||||
${item.status_reason ? `<div style="margin-top:6px; font-size:0.85em; color:var(--error-color);"><strong>Reason:</strong> ${escapeHtml(item.status_reason)}</div>` : ""}
|
||||
${item.stdout_summary ? `<div style="margin-top:6px; font-size:0.85em; color:var(--text-secondary);"><strong>Output:</strong> ${escapeHtml(item.stdout_summary)}</div>` : ""}
|
||||
${item.error_output ? `
|
||||
<details style="margin-top:8px;" ${!isSuccess ? "open" : ""}>
|
||||
<summary style="font-size:0.8em; color:var(--error-color); cursor:pointer; font-weight:600;">Error Output</summary>
|
||||
<pre style="background:#1a0f0f; color:#ff6b6b; padding:8px; border-radius:4px; border:1px solid rgba(231,76,60,0.3); font-size:0.8em; overflow-x:auto; margin-top:4px; white-space:pre-wrap;">${escapeHtml(item.error_output)}</pre>
|
||||
</details>
|
||||
` : ""}
|
||||
`;
|
||||
});
|
||||
}
|
||||
function loadTechDebt() {
|
||||
loadGenericList("/api/tech_debts", "techdebt-container", (item) => `
|
||||
<strong>${item.id}</strong> ${item.is_resolved ? '<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); margin-left: 8px;">(Resolved)</span>' : '<span style="color:var(--open-color); font-weight:bold; background: rgba(230, 126, 34, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--open-color); margin-left: 8px;">(Open)</span>'}
|
||||
<div style="margin-top:5px;"><strong>Description:</strong> ${item.description}</div>
|
||||
<div style="margin-top:5px;"><strong>Ideal Solution:</strong> ${item.ideal_solution}</div>
|
||||
<div style="margin-top:10px;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; margin-right:8px; border:1px solid var(--border-color);">Git Commit: ${item.git_commit || "None"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color);">Branch: ${item.git_branch || "None"}</span>
|
||||
loadGenericList("/api/tech_debts", "techdebt-container", (item) => {
|
||||
const sev = (item.severity || "medium").toLowerCase();
|
||||
let sevBg = "rgba(241, 196, 15, 0.15)";
|
||||
let sevColor = "#f1c40f";
|
||||
let sevBorder = "rgba(241, 196, 15, 0.4)";
|
||||
if (sev === "critical" || sev === "blocker") {
|
||||
sevBg = "rgba(231, 76, 60, 0.18)";
|
||||
sevColor = "#e74c3c";
|
||||
sevBorder = "rgba(231, 76, 60, 0.5)";
|
||||
} else if (sev === "high") {
|
||||
sevBg = "rgba(230, 126, 34, 0.18)";
|
||||
sevColor = "#e67e22";
|
||||
sevBorder = "rgba(230, 126, 34, 0.5)";
|
||||
} else if (sev === "low" || sev === "trivial") {
|
||||
sevBg = "rgba(52, 152, 219, 0.15)";
|
||||
sevColor = "#3498db";
|
||||
sevBorder = "rgba(52, 152, 219, 0.4)";
|
||||
}
|
||||
const statusBadge = item.is_resolved ? '<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color);">✓ Resolved</span>' : '<span style="color:var(--open-color); font-weight:bold; background: rgba(230, 126, 34, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--open-color);">● Open</span>';
|
||||
const fileLoc = item.file_path ? `${item.file_path}${item.line_range ? `:${item.line_range}` : ""}` : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap;">
|
||||
<strong style="font-family:monospace; font-size:1em;">${escapeHtml(item.id)}</strong>
|
||||
<span style="background:${sevBg}; color:${sevColor}; border:1px solid ${sevBorder}; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">${escapeHtml(item.severity || "medium")}</span>
|
||||
${item.effort_estimate ? `<span style="background:var(--canvas-bg); font-size:0.75em; padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">⏳ ${escapeHtml(item.effort_estimate)}</span>` : ""}
|
||||
</div>
|
||||
<div>${statusBadge}</div>
|
||||
</div>
|
||||
`);
|
||||
${fileLoc ? `<div style="margin-top:6px; font-family:monospace; font-size:0.85em; color:var(--accent-color);">\uD83D\uDCC4 ${escapeHtml(fileLoc)}</div>` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4;"><strong>Description:</strong> ${escapeHtml(item.description || "")}</div>
|
||||
${item.workaround ? `<div style="margin-top:4px; font-size:0.85em; color:var(--text-secondary); background:var(--canvas-bg); padding:4px 8px; border-radius:4px; border-left:3px solid #f39c12;"><strong>Workaround:</strong> ${escapeHtml(item.workaround)}</div>` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4; color:var(--text-secondary);"><strong>Ideal Solution:</strong> ${escapeHtml(item.ideal_solution || "")}</div>
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">Commit: ${item.git_commit ? escapeHtml(item.git_commit.substring(0, 8)) : "None"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83C\uDF3F ${escapeHtml(item.git_branch || "None")}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">\uD83C\uDFF7️ ${escapeHtml(item.namespace || "global")}</span>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
loadGenericList("/api/error_fixes", "errorfixes-container", (item) => `
|
||||
<strong>${item.signature || "Error"}</strong> <span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); margin-left: 8px;">(Resolved)</span>
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:5px;"><em>Solution:</em> ${item.solution || ""}</div>
|
||||
<div style="font-family:monospace; font-size:0.85em; color:#e74c3c;">Commit: ${item.git_commit || "None"}</div>
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px;">
|
||||
<div>
|
||||
<strong style="color:var(--text-primary); font-family:monospace; font-size:1em;">${escapeHtml(item.signature || "Error")}</strong>
|
||||
${item.error_category ? `<span style="background:rgba(231,76,60,0.15); color:var(--error-color); border:1px solid rgba(231,76,60,0.4); font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; margin-left:6px;">${escapeHtml(item.error_category)}</span>` : ""}
|
||||
${item.toolchain ? `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.75em; padding:2px 6px; border-radius:4px; margin-left:6px; font-family:monospace;">\uD83D\uDD27 ${escapeHtml(item.toolchain)}</span>` : ""}
|
||||
</div>
|
||||
<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); flex-shrink:0;">✓ Resolved</span>
|
||||
</div>
|
||||
<div style="margin-top:8px; font-size:0.9em; line-height:1.4;"><strong>Solution:</strong> ${escapeHtml(item.solution || "")}</div>
|
||||
${item.stack_trace ? `
|
||||
<details style="margin-top:8px;">
|
||||
<summary style="font-size:0.8em; color:var(--text-secondary); cursor:pointer; font-weight:600;">Stack Trace</summary>
|
||||
<pre style="background:#111417; color:#ff7b72; padding:8px; border-radius:4px; border:1px solid var(--border-color); font-size:0.8em; overflow-x:auto; margin-top:4px; white-space:pre-wrap;">${escapeHtml(item.stack_trace)}</pre>
|
||||
</details>
|
||||
` : ""}
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">Commit: ${item.git_commit ? escapeHtml(item.git_commit.substring(0, 8)) : "None"}</span>
|
||||
${item.git_branch ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83C\uDF3F ${escapeHtml(item.git_branch)}</span>` : ""}
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
function loadADRs() {
|
||||
loadGenericList("/api/adrs", "adrs-container", (item) => `
|
||||
<strong>${item.id} | ${item.title}</strong>
|
||||
<span style="float:right; background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color); ${item.status === "superseded" ? "text-decoration: line-through; color:var(--error-color);" : "color:var(--success-color);"}">${item.status}</span>
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:8px;"><em>Context:</em> ${item.context || ""}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Decision:</em> ${item.decision || ""}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Consequence:</em> ${item.consequence || ""}</div>
|
||||
${item.supersedes ? `<div style="font-size:0.9em; font-weight:bold; color:var(--warning-color);">Supersedes: ${item.supersedes}</div>` : ""}
|
||||
`);
|
||||
loadGenericList("/api/adrs", "adrs-container", (item) => {
|
||||
const statusLower = (item.status || "").toLowerCase();
|
||||
let badgeStyle = "color:var(--accent-color); border-color:rgba(88, 166, 255, 0.4); background:rgba(88, 166, 255, 0.12);";
|
||||
let badgeText = `● ${escapeHtml(item.status || "accepted")}`;
|
||||
if (statusLower === "implemented" || statusLower === "resolved") {
|
||||
badgeStyle = "color:var(--success-color); border-color:rgba(46, 160, 67, 0.4); background:rgba(46, 160, 67, 0.15); font-weight:600;";
|
||||
badgeText = `✓ ${escapeHtml(item.status)}`;
|
||||
} else if (statusLower === "superseded") {
|
||||
badgeStyle = "text-decoration: line-through; color:var(--error-color); border-color:rgba(248, 81, 73, 0.4); background:rgba(248, 81, 73, 0.12);";
|
||||
badgeText = `✕ ${escapeHtml(item.status)}`;
|
||||
} else if (statusLower === "proposed" || statusLower === "draft") {
|
||||
badgeStyle = "color:var(--warning-color); border-color:rgba(210, 153, 34, 0.4); background:rgba(210, 153, 34, 0.12);";
|
||||
badgeText = `○ ${escapeHtml(item.status)}`;
|
||||
}
|
||||
const resolvedDate = item.resolved_at ? new Date(item.resolved_at * 1000).toISOString().replace("T", " ").substring(0, 19) : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<strong>${escapeHtml(item.id)} | ${escapeHtml(item.title)}</strong>
|
||||
<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color); ${badgeStyle}">${badgeText}</span>
|
||||
</div>
|
||||
${item.git_commit || item.git_branch || item.task_id || item.resolved_at ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:4px; font-family:monospace; display:flex; gap:12px; flex-wrap:wrap;">
|
||||
${item.git_branch || item.git_commit ? `<span>\uD83D\uDCE6 ${escapeHtml(item.git_branch || "repo")} @ ${escapeHtml((item.git_commit || "").substring(0, 8))}</span>` : ""}
|
||||
${item.task_id ? `<span>\uD83C\uDFAF Task: ${escapeHtml(item.task_id)}</span>` : ""}
|
||||
${resolvedDate ? `<span>✓ Resolved: ${escapeHtml(resolvedDate)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:8px;"><em>Context:</em> ${escapeHtml(item.context || "")}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Decision:</em> ${escapeHtml(item.decision || "")}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Consequence:</em> ${escapeHtml(item.consequence || "")}</div>
|
||||
${item.supersedes ? `<div style="font-size:0.9em; font-weight:bold; color:var(--warning-color);">Supersedes: ${escapeHtml(item.supersedes)}</div>` : ""}
|
||||
`;
|
||||
});
|
||||
}
|
||||
function loadMemos() {
|
||||
loadGenericList("/api/handoff_memos", "memos-container", (item) => `
|
||||
<strong>Memo from ${item.author || "System"}</strong>
|
||||
<div style="white-space:pre-wrap; font-size:0.9em; line-height:1.4;">${item.content || item.summary || ""}</div>
|
||||
`);
|
||||
loadGenericList("/api/handoff_memos", "memos-container", (item) => {
|
||||
const blockers = Array.isArray(item.blockers) && item.blockers.length > 0 ? item.blockers : [];
|
||||
const actionItems = Array.isArray(item.action_items) && item.action_items.length > 0 ? item.action_items : [];
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<strong style="color:var(--text-primary); font-size:1em;">\uD83D\uDC64 Memo from ${escapeHtml(item.author || "System")}</strong>
|
||||
<span style="font-size:0.75em; color:var(--text-secondary); background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">${escapeHtml(item.namespace || "global")}</span>
|
||||
</div>
|
||||
${item.git_branch || item.vcs_revision || item.session_id ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:3px; font-family:monospace; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
${item.git_branch ? `<span>\uD83C\uDF3F ${escapeHtml(item.git_branch)}</span>` : ""}
|
||||
${item.vcs_revision ? `<span>Rev: ${escapeHtml(item.vcs_revision.substring(0, 8))}</span>` : ""}
|
||||
${item.session_id ? `<span>Session: ${escapeHtml(item.session_id)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="white-space:pre-wrap; font-size:0.9em; line-height:1.4; margin-top:8px;">${escapeHtml(item.content || item.summary || "")}</div>
|
||||
${blockers.length > 0 ? `
|
||||
<div style="margin-top:8px; background:rgba(231,76,60,0.1); border:1px solid rgba(231,76,60,0.3); border-radius:4px; padding:6px 10px;">
|
||||
<div style="color:var(--error-color); font-weight:bold; font-size:0.85em;">\uD83D\uDEAB Blockers:</div>
|
||||
<ul style="margin:4px 0 0 16px; padding:0; font-size:0.85em; color:var(--text-primary);">
|
||||
${blockers.map((b) => `<li>${escapeHtml(b)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
${actionItems.length > 0 ? `
|
||||
<div style="margin-top:8px; background:rgba(52,152,219,0.1); border:1px solid rgba(52,152,219,0.3); border-radius:4px; padding:6px 10px;">
|
||||
<div style="color:var(--accent-color); font-weight:bold; font-size:0.85em;">\uD83D\uDCCB Action Items:</div>
|
||||
<ul style="margin:4px 0 0 16px; padding:0; font-size:0.85em; color:var(--text-primary);">
|
||||
${actionItems.map((a) => `<li>${escapeHtml(a)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
`;
|
||||
});
|
||||
loadGenericList("/api/session_summaries", "summaries-container", (item) => `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<strong>Session ${item.session_id ? escapeHtml(item.session_id) : "Summary"}</strong>
|
||||
@@ -855,17 +1027,75 @@ function loadMemos() {
|
||||
${item.tasks_completed && item.tasks_completed.length > 0 ? `<div style="margin-top:8px; font-size:0.85em;"><strong>Tasks Completed:</strong><ul style="margin:4px 0 0 16px; padding:0;">${item.tasks_completed.map((t) => `<li>${escapeHtml(t)}</li>`).join("")}</ul></div>` : ""}
|
||||
${item.next_steps && item.next_steps.length > 0 ? `<div style="margin-top:6px; font-size:0.85em;"><strong>Next Steps:</strong><ul style="margin:4px 0 0 16px; padding:0;">${item.next_steps.map((s) => `<li>${escapeHtml(s)}</li>`).join("")}</ul></div>` : ""}
|
||||
`);
|
||||
loadGenericList("/api/milestones", "milestones-container", (item) => `
|
||||
<strong>${item.title || item.name}</strong>
|
||||
<div style="margin-top:5px; font-size:0.85em;">${item.description || ""}</div>
|
||||
`);
|
||||
loadGenericList("/api/milestones", "milestones-container", (item) => {
|
||||
const statusLower = (item.status || "pending").toLowerCase();
|
||||
let statusBg = "rgba(52, 152, 219, 0.15)";
|
||||
let statusColor = "var(--accent-color)";
|
||||
let statusLabel = item.status || "Pending";
|
||||
if (statusLower === "completed" || statusLower === "done") {
|
||||
statusBg = "rgba(46, 204, 113, 0.15)";
|
||||
statusColor = "var(--success-color)";
|
||||
statusLabel = "✓ Completed";
|
||||
} else if (statusLower === "in_progress" || statusLower === "active") {
|
||||
statusBg = "rgba(230, 126, 34, 0.15)";
|
||||
statusColor = "var(--open-color)";
|
||||
statusLabel = "● In Progress";
|
||||
}
|
||||
const targetDate = item.target_date ? new Date(item.target_date * 1000).toLocaleDateString() : "";
|
||||
const completedDate = item.completed_at ? new Date(item.completed_at * 1000).toLocaleDateString() : "";
|
||||
const deliverables = Array.isArray(item.deliverables) && item.deliverables.length > 0 ? item.deliverables : [];
|
||||
const taskIds = Array.isArray(item.task_ids) && item.task_ids.length > 0 ? item.task_ids : [];
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<strong style="font-size:1.05em; color:var(--text-primary);">${escapeHtml(item.title || item.name || "Milestone")}</strong>
|
||||
<span style="background:${statusBg}; color:${statusColor}; border:1px solid ${statusColor}; font-size:0.75em; font-weight:bold; padding:2px 8px; border-radius:4px;">${escapeHtml(statusLabel)}</span>
|
||||
</div>
|
||||
${targetDate || completedDate ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:3px; display:flex; gap:10px;">
|
||||
${targetDate ? `<span>\uD83C\uDFAF Target: ${escapeHtml(targetDate)}</span>` : ""}
|
||||
${completedDate ? `<span>✓ Finished: ${escapeHtml(completedDate)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4; color:var(--text-primary);">${escapeHtml(item.description || "")}</div>
|
||||
${deliverables.length > 0 ? `
|
||||
<div style="margin-top:8px; font-size:0.85em;">
|
||||
<strong>Deliverables:</strong>
|
||||
<ul style="margin:4px 0 0 16px; padding:0;">
|
||||
${deliverables.map((d) => `<li>${escapeHtml(d)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
${taskIds.length > 0 ? `
|
||||
<div style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap; align-items:center;">
|
||||
<span style="font-size:0.8em; color:var(--text-secondary);">Linked Tasks:</span>
|
||||
${taskIds.map((tid) => `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; font-size:0.75em; font-family:monospace; border:1px solid var(--border-color);">${escapeHtml(tid)}</span>`).join("")}
|
||||
</div>
|
||||
` : ""}
|
||||
`;
|
||||
});
|
||||
}
|
||||
function loadSnippets() {
|
||||
loadGenericList("/api/snippets", "snippets-container", (item) => `
|
||||
<strong>${item.description || "Snippet"}</strong>
|
||||
<div style="margin-bottom:8px; font-size:0.8em;">Language: ${item.language || "txt"} | Tags: ${(item.tags || []).join(", ")}</div>
|
||||
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em;">${item.content || item.code || ""}</pre>
|
||||
`);
|
||||
loadGenericList("/api/snippets", "snippets-container", (item) => {
|
||||
const code = item.code || item.content || "";
|
||||
const tags = Array.isArray(item.tags) ? item.tags : [];
|
||||
const origin = item.origin_file ? `${item.origin_file}${item.line_range ? `:${item.line_range}` : ""}` : "";
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px;">
|
||||
<div>
|
||||
<strong style="font-size:1.05em; color:var(--text-primary);">${escapeHtml(item.name || item.description || "Snippet")}</strong>
|
||||
${item.language ? `<span style="background:var(--canvas-bg); color:var(--accent-color); border:1px solid var(--border-color); font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; margin-left:6px; font-family:monospace;">${escapeHtml(item.language)}</span>` : ""}
|
||||
</div>
|
||||
<button class="complete-btn" style="position:static;" onclick='navigator.clipboard.writeText(${JSON.stringify(code)}).then(() => { this.innerText="✓ Copied"; setTimeout(() => { this.innerText="\uD83D\uDCCB Copy"; }, 1500); })' title="Copy to Clipboard">\uD83D\uDCCB Copy</button>
|
||||
</div>
|
||||
${item.description && item.description !== item.name ? `<div style="margin-top:4px; font-size:0.85em; color:var(--text-secondary);">${escapeHtml(item.description)}</div>` : ""}
|
||||
${origin ? `<div style="margin-top:6px; font-family:monospace; font-size:0.8em; color:var(--accent-color);">\uD83D\uDCC4 ${escapeHtml(origin)}</div>` : ""}
|
||||
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em; margin-top:8px; border:1px solid var(--border-color);">${escapeHtml(code)}</pre>
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:6px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">\uD83D\uDCE6 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
${tags.map((t) => `<span style="background:var(--card-bg); padding:1px 6px; border-radius:3px; font-size:0.75em; border:1px solid var(--border-color); color:var(--text-secondary);">#${escapeHtml(t)}</span>`).join(" ")}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
function loadAllExtras() {
|
||||
loadTerminal();
|
||||
|
||||
+324
-60
@@ -40,6 +40,11 @@ interface Entity {
|
||||
entity_type: string;
|
||||
namespace?: string;
|
||||
observations?: string[];
|
||||
git_branch?: string;
|
||||
repo_name?: string;
|
||||
file_path?: string;
|
||||
created_at?: number;
|
||||
updated_at?: number;
|
||||
}
|
||||
|
||||
interface Relation {
|
||||
@@ -56,7 +61,18 @@ interface TaskItem {
|
||||
parent_id?: string;
|
||||
parentId?: string;
|
||||
dependencies?: string[];
|
||||
acceptance_criteria?: { description: string; is_met: boolean }[];
|
||||
acceptance_criteria?: {
|
||||
id?: string;
|
||||
description: string;
|
||||
is_met: boolean;
|
||||
verification_command?: string;
|
||||
verification_output?: string;
|
||||
}[];
|
||||
priority?: string;
|
||||
assigned_agent?: string;
|
||||
verification_command?: string;
|
||||
repo_name?: string;
|
||||
git_branch?: string;
|
||||
}
|
||||
|
||||
interface ActivityItem {
|
||||
@@ -217,14 +233,33 @@ function showInspector(nodeId: string): void {
|
||||
const titleEl = document.getElementById("inspector-title");
|
||||
if (titleEl) titleEl.innerText = entity.name;
|
||||
|
||||
const typeEl = document.getElementById("inspector-type");
|
||||
if (typeEl) typeEl.innerText = entity.entity_type;
|
||||
const metaEl = document.querySelector<HTMLElement>(".inspector-meta");
|
||||
if (metaEl) {
|
||||
const createdStr = entity.created_at
|
||||
? new Date(entity.created_at * 1000).toISOString().replace("T", " ").substring(0, 19)
|
||||
: "";
|
||||
const updatedStr = entity.updated_at
|
||||
? new Date(entity.updated_at * 1000).toISOString().replace("T", " ").substring(0, 19)
|
||||
: "";
|
||||
metaEl.innerHTML = `
|
||||
<div><strong>Type:</strong> <span id="inspector-type">${escapeHtml(entity.entity_type)}</span></div>
|
||||
<div><strong>Namespace:</strong> <span id="inspector-namespace">${escapeHtml(entity.namespace || "global")}</span></div>
|
||||
${entity.repo_name ? `<div><strong>Repository:</strong> <span style="font-family:monospace;">${escapeHtml(entity.repo_name)}</span></div>` : ""}
|
||||
${entity.file_path ? `<div><strong>File:</strong> <span style="font-family:monospace; word-break:break-all;">${escapeHtml(entity.file_path)}</span></div>` : ""}
|
||||
${entity.git_branch ? `<div><strong>Branch:</strong> <span style="font-family:monospace;">${escapeHtml(entity.git_branch)}</span></div>` : ""}
|
||||
${createdStr ? `<div><strong>Created:</strong> <span>${escapeHtml(createdStr)}</span></div>` : ""}
|
||||
${updatedStr ? `<div><strong>Updated:</strong> <span>${escapeHtml(updatedStr)}</span></div>` : ""}
|
||||
`;
|
||||
} else {
|
||||
const typeEl = document.getElementById("inspector-type");
|
||||
if (typeEl) typeEl.innerText = entity.entity_type;
|
||||
|
||||
const nsEl = document.getElementById("inspector-namespace");
|
||||
if (nsEl) nsEl.innerText = entity.namespace || "global";
|
||||
const nsEl = document.getElementById("inspector-namespace");
|
||||
if (nsEl) nsEl.innerText = entity.namespace || "global";
|
||||
}
|
||||
|
||||
const obsHtml = (entity.observations || [])
|
||||
.map((o) => `<li>${o}</li>`)
|
||||
.map((o) => `<li>${escapeHtml(o)}</li>`)
|
||||
.join("");
|
||||
const obsEl = document.getElementById("inspector-observations");
|
||||
if (obsEl)
|
||||
@@ -234,8 +269,8 @@ function showInspector(nodeId: string): void {
|
||||
const relHtml = rels
|
||||
.map((r) => {
|
||||
if (r.from === nodeId)
|
||||
return `<li>→ <em>${r.relation_type}</em> → <strong>${r.to}</strong></li>`;
|
||||
return `<li>← <em>${r.relation_type}</em> ← <strong>${r.from}</strong></li>`;
|
||||
return `<li>→ <em>${escapeHtml(r.relation_type)}</em> → <strong>${escapeHtml(r.to)}</strong></li>`;
|
||||
return `<li>← <em>${escapeHtml(r.relation_type)}</em> ← <strong>${escapeHtml(r.from)}</strong></li>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
@@ -486,16 +521,41 @@ function buildTaskTreeHTML(
|
||||
}
|
||||
}
|
||||
|
||||
const prio = (t.priority || "").toLowerCase();
|
||||
let prioBadge = "";
|
||||
if (prio === "critical" || prio === "urgent") {
|
||||
prioBadge = `<span style="background:rgba(231,76,60,0.18); color:#e74c3c; border:1px solid rgba(231,76,60,0.5); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">CRITICAL</span>`;
|
||||
} else if (prio === "high") {
|
||||
prioBadge = `<span style="background:rgba(230,126,34,0.18); color:#e67e22; border:1px solid rgba(230,126,34,0.5); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">HIGH</span>`;
|
||||
} else if (prio === "medium" || prio === "med") {
|
||||
prioBadge = `<span style="background:rgba(52,152,219,0.15); color:#3498db; border:1px solid rgba(52,152,219,0.4); font-size:0.7em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">MEDIUM</span>`;
|
||||
} else if (prio === "low") {
|
||||
prioBadge = `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.7em; padding:2px 6px; border-radius:4px; text-transform:uppercase;">LOW</span>`;
|
||||
}
|
||||
|
||||
html += `<div class="${cardClass}" style="margin-left: ${depth * 20}px; margin-bottom: 10px;">`;
|
||||
|
||||
html += `<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px; margin-bottom:4px;">`;
|
||||
html += `<div style="display:flex; align-items:center; gap:6px; flex-wrap:wrap;">`;
|
||||
if (prioBadge) html += prioBadge;
|
||||
if (t.assigned_agent) {
|
||||
html += `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.7em; padding:2px 6px; border-radius:4px;">🤖 ${escapeHtml(t.assigned_agent)}</span>`;
|
||||
}
|
||||
if (isBlocked && !isCompleted && !isCancelled) {
|
||||
html += `<div style="background:#e74c3c; color:white; font-size:0.75em; padding:2px 6px; border-radius:3px; display:inline-block; margin-bottom:6px; font-weight:bold;">[BLOCKED]</div>`;
|
||||
if (blockers.length > 0) {
|
||||
html += `<div style="font-size:0.8em; color:#e74c3c; margin-bottom:6px;">Waiting on: ${blockers.join(", ")}</div>`;
|
||||
}
|
||||
html += `<span style="background:#e74c3c; color:white; font-size:0.7em; padding:2px 6px; border-radius:3px; font-weight:bold;">BLOCKED</span>`;
|
||||
}
|
||||
if (isCancelled) {
|
||||
html += `<div style="background:#95a5a6; color:white; font-size:0.75em; padding:2px 6px; border-radius:3px; display:inline-block; margin-bottom:6px; font-weight:bold;">[CANCELLED]</div>`;
|
||||
html += `<span style="background:#95a5a6; color:white; font-size:0.7em; padding:2px 6px; border-radius:3px; font-weight:bold;">CANCELLED</span>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
|
||||
if (!isCompleted && !isCancelled && !isBlocked) {
|
||||
html += `<button class="complete-btn" style="position:static;" onclick="completeTask('${escapeHtml(t.id)}')" title="Mark Completed">✓</button>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
|
||||
if (isBlocked && blockers.length > 0 && !isCompleted && !isCancelled) {
|
||||
html += `<div style="font-size:0.8em; color:#e74c3c; margin-bottom:6px;">Waiting on: ${escapeHtml(blockers.join(", "))}</div>`;
|
||||
}
|
||||
|
||||
html += `<strong>${escapeHtml(t.title)}</strong>`;
|
||||
@@ -503,6 +563,10 @@ function buildTaskTreeHTML(
|
||||
html += `<div style="margin-top:5px; font-size:0.9em; color:var(--text-secondary); line-height:1.4;">${escapeHtml(t.description)}</div>`;
|
||||
}
|
||||
|
||||
if (t.verification_command) {
|
||||
html += `<div style="margin-top:6px; font-size:0.8em; font-family:monospace; background:var(--canvas-bg); padding:3px 8px; border-radius:4px; border:1px solid var(--border-color); color:var(--accent-color);">🧪 Test: ${escapeHtml(t.verification_command)}</div>`;
|
||||
}
|
||||
|
||||
const criteria = t.acceptance_criteria || [];
|
||||
if (criteria.length > 0) {
|
||||
html += `<ul style="margin:8px 0 0 0; padding-left:20px; font-size: 0.9em; color: var(--text-secondary);">`;
|
||||
@@ -512,18 +576,21 @@ function buildTaskTreeHTML(
|
||||
if (!isMet) unmetCriteria = true;
|
||||
const check = isMet ? "☑" : "☐";
|
||||
const strike = isMet ? "text-decoration: line-through;" : "";
|
||||
html += `<li style="${strike}">${check} ${c.description}</li>`;
|
||||
html += `<li style="${strike}">${check} ${escapeHtml(c.description)}${c.verification_command ? ` <code style="font-size:0.8em; background:var(--canvas-bg); padding:1px 4px; border-radius:3px;">(${escapeHtml(c.verification_command)})</code>` : ""}</li>`;
|
||||
});
|
||||
html += `</ul>`;
|
||||
if (unmetCriteria && !isCompleted && !isCancelled) isBlocked = true;
|
||||
}
|
||||
|
||||
html += progressHtml;
|
||||
|
||||
if (!isCompleted && !isCancelled && !isBlocked) {
|
||||
html += `<button class="complete-btn" onclick="completeTask('${t.id}')" title="Mark Completed">✓</button>`;
|
||||
if (t.git_branch || t.repo_name) {
|
||||
html += `<div style="margin-top:8px; font-size:0.75em; display:flex; gap:6px; flex-wrap:wrap; color:var(--text-secondary);">`;
|
||||
if (t.repo_name) html += `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; border:1px solid var(--border-color);">📦 ${escapeHtml(t.repo_name)}</span>`;
|
||||
if (t.git_branch) html += `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; border:1px solid var(--border-color);">🌿 ${escapeHtml(t.git_branch)}</span>`;
|
||||
html += `</div>`;
|
||||
}
|
||||
|
||||
html += progressHtml;
|
||||
|
||||
if (allChildren.length > 0) {
|
||||
html += `<div style="margin-top: 15px; border-left: 2px solid var(--border-color); padding-left: 10px;">`;
|
||||
html += buildTaskTreeHTML(tasks, t.id, 0);
|
||||
@@ -969,7 +1036,6 @@ async function loadGenericList(
|
||||
containerId: string,
|
||||
formatter: (item: any) => string,
|
||||
): Promise<void> {
|
||||
const container = document.getElementById(containerId);
|
||||
try {
|
||||
const res = await fetch(endpoint);
|
||||
if (!res.ok) {
|
||||
@@ -979,6 +1045,7 @@ async function loadGenericList(
|
||||
return;
|
||||
}
|
||||
const data = await res.json();
|
||||
const container = document.getElementById(containerId);
|
||||
if (!container) return;
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
@@ -1073,14 +1140,37 @@ function loadTerminal(): void {
|
||||
loadGenericList(
|
||||
"/api/terminal/history",
|
||||
"terminal-container",
|
||||
(item) => `
|
||||
<span style="font-weight:bold; font-size:1.05em; color:var(--text-primary);">${item.command}</span>
|
||||
<span style="margin-left:15px; font-size:0.85em;"><span style="font-weight:bold; color:var(--text-secondary);">Exit Code:</span> <span style="color:${item.exit_code === 0 ? "var(--success-color)" : "var(--error-color)"}; font-weight:bold;">${item.exit_code}</span></span>
|
||||
<div style="margin-top:10px; font-size:0.85em;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; margin-right:8px; border:1px solid var(--border-color);">OS: ${item.os || "Unknown"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">CWD: ${item.cwd || "Unknown"}</span>
|
||||
(item) => {
|
||||
const isSuccess = item.exit_code === 0;
|
||||
const statusColor = isSuccess ? "var(--success-color)" : "var(--error-color)";
|
||||
const statusBg = isSuccess ? "rgba(46, 204, 113, 0.12)" : "rgba(231, 76, 60, 0.12)";
|
||||
const durationStr = item.duration_ms != null ? (item.duration_ms < 1000 ? `${item.duration_ms}ms` : `${(item.duration_ms / 1000).toFixed(2)}s`) : "";
|
||||
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:10px;">
|
||||
<span style="font-family:monospace; font-weight:bold; font-size:1.05em; color:var(--text-primary); word-break:break-all;">$ ${escapeHtml(item.command)}</span>
|
||||
<div style="display:flex; gap:6px; align-items:center; flex-shrink:0;">
|
||||
${durationStr ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.75em; border:1px solid var(--border-color); color:var(--text-secondary); font-family:monospace;">⏱️ ${escapeHtml(durationStr)}</span>` : ""}
|
||||
<span style="background:${statusBg}; color:${statusColor}; border:1px solid ${statusColor}; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; font-family:monospace;">Exit ${item.exit_code}</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
<div style="margin-top:8px; font-size:0.85em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.shell ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); font-weight:600;">🐚 ${escapeHtml(item.shell)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">OS: ${escapeHtml(item.os || "Unknown")}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color); word-break:break-all;">📂 ${escapeHtml(item.cwd || "Unknown")}</span>
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">📦 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
${item.invoker ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">👤 ${escapeHtml(item.invoker)}</span>` : ""}
|
||||
</div>
|
||||
${item.status_reason ? `<div style="margin-top:6px; font-size:0.85em; color:var(--error-color);"><strong>Reason:</strong> ${escapeHtml(item.status_reason)}</div>` : ""}
|
||||
${item.stdout_summary ? `<div style="margin-top:6px; font-size:0.85em; color:var(--text-secondary);"><strong>Output:</strong> ${escapeHtml(item.stdout_summary)}</div>` : ""}
|
||||
${item.error_output ? `
|
||||
<details style="margin-top:8px;" ${!isSuccess ? "open" : ""}>
|
||||
<summary style="font-size:0.8em; color:var(--error-color); cursor:pointer; font-weight:600;">Error Output</summary>
|
||||
<pre style="background:#1a0f0f; color:#ff6b6b; padding:8px; border-radius:4px; border:1px solid rgba(231,76,60,0.3); font-size:0.8em; overflow-x:auto; margin-top:4px; white-space:pre-wrap;">${escapeHtml(item.error_output)}</pre>
|
||||
</details>
|
||||
` : ""}
|
||||
`;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1088,40 +1178,123 @@ function loadTechDebt(): void {
|
||||
loadGenericList(
|
||||
"/api/tech_debts",
|
||||
"techdebt-container",
|
||||
(item) => `
|
||||
<strong>${item.id}</strong> ${item.is_resolved ? '<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); margin-left: 8px;">(Resolved)</span>' : '<span style="color:var(--open-color); font-weight:bold; background: rgba(230, 126, 34, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--open-color); margin-left: 8px;">(Open)</span>'}
|
||||
<div style="margin-top:5px;"><strong>Description:</strong> ${item.description}</div>
|
||||
<div style="margin-top:5px;"><strong>Ideal Solution:</strong> ${item.ideal_solution}</div>
|
||||
<div style="margin-top:10px;">
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; margin-right:8px; border:1px solid var(--border-color);">Git Commit: ${item.git_commit || "None"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color);">Branch: ${item.git_branch || "None"}</span>
|
||||
(item) => {
|
||||
const sev = (item.severity || "medium").toLowerCase();
|
||||
let sevBg = "rgba(241, 196, 15, 0.15)";
|
||||
let sevColor = "#f1c40f";
|
||||
let sevBorder = "rgba(241, 196, 15, 0.4)";
|
||||
if (sev === "critical" || sev === "blocker") {
|
||||
sevBg = "rgba(231, 76, 60, 0.18)";
|
||||
sevColor = "#e74c3c";
|
||||
sevBorder = "rgba(231, 76, 60, 0.5)";
|
||||
} else if (sev === "high") {
|
||||
sevBg = "rgba(230, 126, 34, 0.18)";
|
||||
sevColor = "#e67e22";
|
||||
sevBorder = "rgba(230, 126, 34, 0.5)";
|
||||
} else if (sev === "low" || sev === "trivial") {
|
||||
sevBg = "rgba(52, 152, 219, 0.15)";
|
||||
sevColor = "#3498db";
|
||||
sevBorder = "rgba(52, 152, 219, 0.4)";
|
||||
}
|
||||
|
||||
const statusBadge = item.is_resolved
|
||||
? '<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color);">✓ Resolved</span>'
|
||||
: '<span style="color:var(--open-color); font-weight:bold; background: rgba(230, 126, 34, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--open-color);">● Open</span>';
|
||||
|
||||
const fileLoc = item.file_path
|
||||
? `${item.file_path}${item.line_range ? `:${item.line_range}` : ""}`
|
||||
: "";
|
||||
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<div style="display:flex; align-items:center; gap:8px; flex-wrap:wrap;">
|
||||
<strong style="font-family:monospace; font-size:1em;">${escapeHtml(item.id)}</strong>
|
||||
<span style="background:${sevBg}; color:${sevColor}; border:1px solid ${sevBorder}; font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; text-transform:uppercase;">${escapeHtml(item.severity || "medium")}</span>
|
||||
${item.effort_estimate ? `<span style="background:var(--canvas-bg); font-size:0.75em; padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">⏳ ${escapeHtml(item.effort_estimate)}</span>` : ""}
|
||||
</div>
|
||||
<div>${statusBadge}</div>
|
||||
</div>
|
||||
`,
|
||||
${fileLoc ? `<div style="margin-top:6px; font-family:monospace; font-size:0.85em; color:var(--accent-color);">📄 ${escapeHtml(fileLoc)}</div>` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4;"><strong>Description:</strong> ${escapeHtml(item.description || "")}</div>
|
||||
${item.workaround ? `<div style="margin-top:4px; font-size:0.85em; color:var(--text-secondary); background:var(--canvas-bg); padding:4px 8px; border-radius:4px; border-left:3px solid #f39c12;"><strong>Workaround:</strong> ${escapeHtml(item.workaround)}</div>` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4; color:var(--text-secondary);"><strong>Ideal Solution:</strong> ${escapeHtml(item.ideal_solution || "")}</div>
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">📦 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">Commit: ${item.git_commit ? escapeHtml(item.git_commit.substring(0, 8)) : "None"}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">🌿 ${escapeHtml(item.git_branch || "None")}</span>
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color); color:var(--text-secondary);">🏷️ ${escapeHtml(item.namespace || "global")}</span>
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
);
|
||||
|
||||
loadGenericList(
|
||||
"/api/error_fixes",
|
||||
"errorfixes-container",
|
||||
(item) => `
|
||||
<strong>${item.signature || "Error"}</strong> <span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.1); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); margin-left: 8px;">(Resolved)</span>
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:5px;"><em>Solution:</em> ${item.solution || ""}</div>
|
||||
<div style="font-family:monospace; font-size:0.85em; color:#e74c3c;">Commit: ${item.git_commit || "None"}</div>
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px;">
|
||||
<div>
|
||||
<strong style="color:var(--text-primary); font-family:monospace; font-size:1em;">${escapeHtml(item.signature || "Error")}</strong>
|
||||
${item.error_category ? `<span style="background:rgba(231,76,60,0.15); color:var(--error-color); border:1px solid rgba(231,76,60,0.4); font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; margin-left:6px;">${escapeHtml(item.error_category)}</span>` : ""}
|
||||
${item.toolchain ? `<span style="background:var(--canvas-bg); color:var(--text-secondary); border:1px solid var(--border-color); font-size:0.75em; padding:2px 6px; border-radius:4px; margin-left:6px; font-family:monospace;">🔧 ${escapeHtml(item.toolchain)}</span>` : ""}
|
||||
</div>
|
||||
<span style="color:var(--success-color); font-weight:bold; background: rgba(46, 204, 113, 0.15); padding: 2px 6px; border-radius: 4px; border: 1px solid var(--success-color); flex-shrink:0;">✓ Resolved</span>
|
||||
</div>
|
||||
<div style="margin-top:8px; font-size:0.9em; line-height:1.4;"><strong>Solution:</strong> ${escapeHtml(item.solution || "")}</div>
|
||||
${item.stack_trace ? `
|
||||
<details style="margin-top:8px;">
|
||||
<summary style="font-size:0.8em; color:var(--text-secondary); cursor:pointer; font-weight:600;">Stack Trace</summary>
|
||||
<pre style="background:#111417; color:#ff7b72; padding:8px; border-radius:4px; border:1px solid var(--border-color); font-size:0.8em; overflow-x:auto; margin-top:4px; white-space:pre-wrap;">${escapeHtml(item.stack_trace)}</pre>
|
||||
</details>
|
||||
` : ""}
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">📦 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-family:monospace; border:1px solid var(--border-color);">Commit: ${item.git_commit ? escapeHtml(item.git_commit.substring(0, 8)) : "None"}</span>
|
||||
${item.git_branch ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">🌿 ${escapeHtml(item.git_branch)}</span>` : ""}
|
||||
</div>
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
function loadADRs(): void {
|
||||
loadGenericList(
|
||||
"/api/adrs",
|
||||
"adrs-container",
|
||||
(item) => `
|
||||
<strong>${item.id} | ${item.title}</strong>
|
||||
<span style="float:right; background:var(--canvas-bg); padding:2px 6px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color); ${item.status === "superseded" ? "text-decoration: line-through; color:var(--error-color);" : "color:var(--success-color);"}">${item.status}</span>
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:8px;"><em>Context:</em> ${item.context || ""}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Decision:</em> ${item.decision || ""}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Consequence:</em> ${item.consequence || ""}</div>
|
||||
${item.supersedes ? `<div style="font-size:0.9em; font-weight:bold; color:var(--warning-color);">Supersedes: ${item.supersedes}</div>` : ""}
|
||||
`,
|
||||
(item) => {
|
||||
const statusLower = (item.status || "").toLowerCase();
|
||||
let badgeStyle = "color:var(--accent-color); border-color:rgba(88, 166, 255, 0.4); background:rgba(88, 166, 255, 0.12);";
|
||||
let badgeText = `● ${escapeHtml(item.status || "accepted")}`;
|
||||
|
||||
if (statusLower === "implemented" || statusLower === "resolved") {
|
||||
badgeStyle = "color:var(--success-color); border-color:rgba(46, 160, 67, 0.4); background:rgba(46, 160, 67, 0.15); font-weight:600;";
|
||||
badgeText = `✓ ${escapeHtml(item.status)}`;
|
||||
} else if (statusLower === "superseded") {
|
||||
badgeStyle = "text-decoration: line-through; color:var(--error-color); border-color:rgba(248, 81, 73, 0.4); background:rgba(248, 81, 73, 0.12);";
|
||||
badgeText = `✕ ${escapeHtml(item.status)}`;
|
||||
} else if (statusLower === "proposed" || statusLower === "draft") {
|
||||
badgeStyle = "color:var(--warning-color); border-color:rgba(210, 153, 34, 0.4); background:rgba(210, 153, 34, 0.12);";
|
||||
badgeText = `○ ${escapeHtml(item.status)}`;
|
||||
}
|
||||
|
||||
const resolvedDate = item.resolved_at ? new Date(item.resolved_at * 1000).toISOString().replace("T", " ").substring(0, 19) : "";
|
||||
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||
<strong>${escapeHtml(item.id)} | ${escapeHtml(item.title)}</strong>
|
||||
<span style="background:var(--canvas-bg); padding:2px 8px; border-radius:4px; font-size:0.8em; border:1px solid var(--border-color); ${badgeStyle}">${badgeText}</span>
|
||||
</div>
|
||||
${(item.git_commit || item.git_branch || item.task_id || item.resolved_at) ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:4px; font-family:monospace; display:flex; gap:12px; flex-wrap:wrap;">
|
||||
${item.git_branch || item.git_commit ? `<span>📦 ${escapeHtml(item.git_branch || "repo")} @ ${escapeHtml((item.git_commit || "").substring(0, 8))}</span>` : ""}
|
||||
${item.task_id ? `<span>🎯 Task: ${escapeHtml(item.task_id)}</span>` : ""}
|
||||
${resolvedDate ? `<span>✓ Resolved: ${escapeHtml(resolvedDate)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="font-size:0.9em; margin-bottom:8px; margin-top:8px;"><em>Context:</em> ${escapeHtml(item.context || "")}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Decision:</em> ${escapeHtml(item.decision || "")}</div>
|
||||
<div style="font-size:0.9em; margin-bottom:8px;"><em>Consequence:</em> ${escapeHtml(item.consequence || "")}</div>
|
||||
${item.supersedes ? `<div style="font-size:0.9em; font-weight:bold; color:var(--warning-color);">Supersedes: ${escapeHtml(item.supersedes)}</div>` : ""}
|
||||
`;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1129,10 +1302,41 @@ function loadMemos(): void {
|
||||
loadGenericList(
|
||||
"/api/handoff_memos",
|
||||
"memos-container",
|
||||
(item) => `
|
||||
<strong>Memo from ${item.author || "System"}</strong>
|
||||
<div style="white-space:pre-wrap; font-size:0.9em; line-height:1.4;">${item.content || item.summary || ""}</div>
|
||||
`,
|
||||
(item) => {
|
||||
const blockers = Array.isArray(item.blockers) && item.blockers.length > 0 ? item.blockers : [];
|
||||
const actionItems = Array.isArray(item.action_items) && item.action_items.length > 0 ? item.action_items : [];
|
||||
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<strong style="color:var(--text-primary); font-size:1em;">👤 Memo from ${escapeHtml(item.author || "System")}</strong>
|
||||
<span style="font-size:0.75em; color:var(--text-secondary); background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">${escapeHtml(item.namespace || "global")}</span>
|
||||
</div>
|
||||
${(item.git_branch || item.vcs_revision || item.session_id) ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:3px; font-family:monospace; display:flex; gap:10px; flex-wrap:wrap;">
|
||||
${item.git_branch ? `<span>🌿 ${escapeHtml(item.git_branch)}</span>` : ""}
|
||||
${item.vcs_revision ? `<span>Rev: ${escapeHtml(item.vcs_revision.substring(0, 8))}</span>` : ""}
|
||||
${item.session_id ? `<span>Session: ${escapeHtml(item.session_id)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="white-space:pre-wrap; font-size:0.9em; line-height:1.4; margin-top:8px;">${escapeHtml(item.content || item.summary || "")}</div>
|
||||
${blockers.length > 0 ? `
|
||||
<div style="margin-top:8px; background:rgba(231,76,60,0.1); border:1px solid rgba(231,76,60,0.3); border-radius:4px; padding:6px 10px;">
|
||||
<div style="color:var(--error-color); font-weight:bold; font-size:0.85em;">🚫 Blockers:</div>
|
||||
<ul style="margin:4px 0 0 16px; padding:0; font-size:0.85em; color:var(--text-primary);">
|
||||
${blockers.map((b: string) => `<li>${escapeHtml(b)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
${actionItems.length > 0 ? `
|
||||
<div style="margin-top:8px; background:rgba(52,152,219,0.1); border:1px solid rgba(52,152,219,0.3); border-radius:4px; padding:6px 10px;">
|
||||
<div style="color:var(--accent-color); font-weight:bold; font-size:0.85em;">📋 Action Items:</div>
|
||||
<ul style="margin:4px 0 0 16px; padding:0; font-size:0.85em; color:var(--text-primary);">
|
||||
${actionItems.map((a: string) => `<li>${escapeHtml(a)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
`;
|
||||
},
|
||||
);
|
||||
|
||||
loadGenericList(
|
||||
@@ -1153,10 +1357,54 @@ function loadMemos(): void {
|
||||
loadGenericList(
|
||||
"/api/milestones",
|
||||
"milestones-container",
|
||||
(item) => `
|
||||
<strong>${item.title || item.name}</strong>
|
||||
<div style="margin-top:5px; font-size:0.85em;">${item.description || ""}</div>
|
||||
`,
|
||||
(item) => {
|
||||
const statusLower = (item.status || "pending").toLowerCase();
|
||||
let statusBg = "rgba(52, 152, 219, 0.15)";
|
||||
let statusColor = "var(--accent-color)";
|
||||
let statusLabel = item.status || "Pending";
|
||||
if (statusLower === "completed" || statusLower === "done") {
|
||||
statusBg = "rgba(46, 204, 113, 0.15)";
|
||||
statusColor = "var(--success-color)";
|
||||
statusLabel = "✓ Completed";
|
||||
} else if (statusLower === "in_progress" || statusLower === "active") {
|
||||
statusBg = "rgba(230, 126, 34, 0.15)";
|
||||
statusColor = "var(--open-color)";
|
||||
statusLabel = "● In Progress";
|
||||
}
|
||||
|
||||
const targetDate = item.target_date ? new Date(item.target_date * 1000).toLocaleDateString() : "";
|
||||
const completedDate = item.completed_at ? new Date(item.completed_at * 1000).toLocaleDateString() : "";
|
||||
const deliverables = Array.isArray(item.deliverables) && item.deliverables.length > 0 ? item.deliverables : [];
|
||||
const taskIds = Array.isArray(item.task_ids) && item.task_ids.length > 0 ? item.task_ids : [];
|
||||
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:8px;">
|
||||
<strong style="font-size:1.05em; color:var(--text-primary);">${escapeHtml(item.title || item.name || "Milestone")}</strong>
|
||||
<span style="background:${statusBg}; color:${statusColor}; border:1px solid ${statusColor}; font-size:0.75em; font-weight:bold; padding:2px 8px; border-radius:4px;">${escapeHtml(statusLabel)}</span>
|
||||
</div>
|
||||
${targetDate || completedDate ? `
|
||||
<div style="font-size:0.8em; color:var(--text-secondary); margin-top:3px; display:flex; gap:10px;">
|
||||
${targetDate ? `<span>🎯 Target: ${escapeHtml(targetDate)}</span>` : ""}
|
||||
${completedDate ? `<span>✓ Finished: ${escapeHtml(completedDate)}</span>` : ""}
|
||||
</div>
|
||||
` : ""}
|
||||
<div style="margin-top:6px; font-size:0.9em; line-height:1.4; color:var(--text-primary);">${escapeHtml(item.description || "")}</div>
|
||||
${deliverables.length > 0 ? `
|
||||
<div style="margin-top:8px; font-size:0.85em;">
|
||||
<strong>Deliverables:</strong>
|
||||
<ul style="margin:4px 0 0 16px; padding:0;">
|
||||
${deliverables.map((d: string) => `<li>${escapeHtml(d)}</li>`).join("")}
|
||||
</ul>
|
||||
</div>
|
||||
` : ""}
|
||||
${taskIds.length > 0 ? `
|
||||
<div style="margin-top:8px; display:flex; gap:6px; flex-wrap:wrap; align-items:center;">
|
||||
<span style="font-size:0.8em; color:var(--text-secondary);">Linked Tasks:</span>
|
||||
${taskIds.map((tid: string) => `<span style="background:var(--canvas-bg); padding:1px 6px; border-radius:3px; font-size:0.75em; font-family:monospace; border:1px solid var(--border-color);">${escapeHtml(tid)}</span>`).join("")}
|
||||
</div>
|
||||
` : ""}
|
||||
`;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1164,14 +1412,30 @@ function loadSnippets(): void {
|
||||
loadGenericList(
|
||||
"/api/snippets",
|
||||
"snippets-container",
|
||||
(item) => `
|
||||
<strong>${item.description || "Snippet"}</strong>
|
||||
<div style="margin-bottom:8px; font-size:0.8em;">Language: ${item.language || "txt"} | Tags: ${(item.tags || []).join(", ")}</div>
|
||||
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em;">${item.content || item.code || ""}</pre>
|
||||
`,
|
||||
(item) => {
|
||||
const code = item.code || item.content || "";
|
||||
const tags = Array.isArray(item.tags) ? item.tags : [];
|
||||
const origin = item.origin_file ? `${item.origin_file}${item.line_range ? `:${item.line_range}` : ""}` : "";
|
||||
|
||||
return `
|
||||
<div style="display:flex; justify-content:space-between; align-items:flex-start; gap:8px;">
|
||||
<div>
|
||||
<strong style="font-size:1.05em; color:var(--text-primary);">${escapeHtml(item.name || item.description || "Snippet")}</strong>
|
||||
${item.language ? `<span style="background:var(--canvas-bg); color:var(--accent-color); border:1px solid var(--border-color); font-size:0.75em; font-weight:bold; padding:2px 6px; border-radius:4px; margin-left:6px; font-family:monospace;">${escapeHtml(item.language)}</span>` : ""}
|
||||
</div>
|
||||
<button class="complete-btn" style="position:static;" onclick='navigator.clipboard.writeText(${JSON.stringify(code)}).then(() => { this.innerText="✓ Copied"; setTimeout(() => { this.innerText="📋 Copy"; }, 1500); })' title="Copy to Clipboard">📋 Copy</button>
|
||||
</div>
|
||||
${item.description && item.description !== item.name ? `<div style="margin-top:4px; font-size:0.85em; color:var(--text-secondary);">${escapeHtml(item.description)}</div>` : ""}
|
||||
${origin ? `<div style="margin-top:6px; font-family:monospace; font-size:0.8em; color:var(--accent-color);">📄 ${escapeHtml(origin)}</div>` : ""}
|
||||
<pre style="background:#111417; color:#d1d8e0; padding:10px; border-radius:4px; overflow-x:auto; font-size:0.85em; margin-top:8px; border:1px solid var(--border-color);">${escapeHtml(code)}</pre>
|
||||
<div style="margin-top:8px; font-size:0.8em; display:flex; gap:6px; flex-wrap:wrap; align-items:center;">
|
||||
${item.repo_name ? `<span style="background:var(--canvas-bg); padding:2px 6px; border-radius:4px; border:1px solid var(--border-color);">📦 ${escapeHtml(item.repo_name)}</span>` : ""}
|
||||
${tags.map((t: string) => `<span style="background:var(--card-bg); padding:1px 6px; border-radius:3px; font-size:0.75em; border:1px solid var(--border-color); color:var(--text-secondary);">#${escapeHtml(t)}</span>`).join(" ")}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function loadAllExtras(): void {
|
||||
loadTerminal();
|
||||
loadTechDebt();
|
||||
|
||||
+144
-2
@@ -378,7 +378,7 @@ impl McpTool for DecisionsHandler {
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<DecisionsTool>(
|
||||
"decisions",
|
||||
"Consolidated Architectural Decision Records (ADRs) management (log, query, delete)",
|
||||
"Consolidated Architectural Decision Records (ADRs) management (log, query, update, delete)",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -396,6 +396,12 @@ impl McpTool for DecisionsHandler {
|
||||
let context = req.context.unwrap_or_default();
|
||||
let decision = req.decision.unwrap_or_default();
|
||||
let consequence = req.consequences.unwrap_or_default();
|
||||
let status_lower = status.to_ascii_lowercase();
|
||||
let resolved_at = if status_lower == "implemented" || status_lower == "resolved" {
|
||||
Some(crate::handlers::utils::now_secs())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let idx = state.get_search_index().await;
|
||||
let mut final_id = String::new();
|
||||
@@ -404,7 +410,7 @@ impl McpTool for DecisionsHandler {
|
||||
state.code.adrs.modify(|adrs| {
|
||||
if let Some(superseded_id) = &req.supersedes {
|
||||
for old_adr in adrs.iter_mut() {
|
||||
if old_adr.id == *superseded_id {
|
||||
if old_adr.id.eq_ignore_ascii_case(superseded_id) {
|
||||
old_adr.status = "superseded".to_string();
|
||||
adrs_to_index.push(old_adr.clone());
|
||||
break;
|
||||
@@ -427,6 +433,10 @@ impl McpTool for DecisionsHandler {
|
||||
alternatives_considered: req.alternatives_considered.unwrap_or_default(),
|
||||
affected_components: req.affected_components.unwrap_or_default(),
|
||||
author: req.author,
|
||||
git_commit: req.git_commit,
|
||||
git_branch: req.git_branch,
|
||||
resolved_at,
|
||||
task_id: req.task_id,
|
||||
};
|
||||
|
||||
adrs_to_index.push(a.clone());
|
||||
@@ -444,6 +454,94 @@ impl McpTool for DecisionsHandler {
|
||||
);
|
||||
Ok(format!("Logged decision {}: {}", final_id, title))
|
||||
}
|
||||
DecisionAction::Update => {
|
||||
let id = req.id.ok_or_else(|| {
|
||||
crate::error::AppError::Internal("Missing required parameter 'id' for action 'update'. Next step: Provide ADR 'id' string in request and retry.".to_string())
|
||||
})?;
|
||||
let mut updated_adr = None;
|
||||
let mut adrs_to_index = Vec::new();
|
||||
|
||||
state.code.adrs.modify(|adrs| {
|
||||
let target_pos = adrs.iter().position(|a| a.id.eq_ignore_ascii_case(&id));
|
||||
if let Some(pos) = target_pos {
|
||||
if let Some(superseded_id) = &req.supersedes {
|
||||
if let Some(s_pos) = adrs.iter().position(|a| a.id.eq_ignore_ascii_case(superseded_id)) {
|
||||
if s_pos != pos {
|
||||
adrs[s_pos].status = "superseded".to_string();
|
||||
adrs_to_index.push(adrs[s_pos].clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let a = &mut adrs[pos];
|
||||
if let Some(t) = req.title {
|
||||
a.title = t;
|
||||
}
|
||||
if let Some(c) = req.context {
|
||||
a.context = c;
|
||||
}
|
||||
if let Some(d) = req.decision {
|
||||
a.decision = d;
|
||||
}
|
||||
if let Some(cons) = req.consequences {
|
||||
a.consequence = cons;
|
||||
}
|
||||
if let Some(s) = req.status {
|
||||
let s_lower = s.to_ascii_lowercase();
|
||||
if (s_lower == "implemented" || s_lower == "resolved") && a.resolved_at.is_none() {
|
||||
a.resolved_at = Some(crate::handlers::utils::now_secs());
|
||||
} else if s_lower != "implemented" && s_lower != "resolved" {
|
||||
a.resolved_at = None;
|
||||
}
|
||||
a.status = s;
|
||||
}
|
||||
if req.supersedes.is_some() {
|
||||
a.supersedes = req.supersedes;
|
||||
}
|
||||
if req.repo_name.is_some() {
|
||||
a.repo_name = req.repo_name;
|
||||
}
|
||||
if let Some(alts) = req.alternatives_considered {
|
||||
a.alternatives_considered = alts;
|
||||
}
|
||||
if let Some(aff) = req.affected_components {
|
||||
a.affected_components = aff;
|
||||
}
|
||||
if req.author.is_some() {
|
||||
a.author = req.author;
|
||||
}
|
||||
if req.git_commit.is_some() {
|
||||
a.git_commit = req.git_commit;
|
||||
}
|
||||
if req.git_branch.is_some() {
|
||||
a.git_branch = req.git_branch;
|
||||
}
|
||||
if req.task_id.is_some() {
|
||||
a.task_id = req.task_id;
|
||||
}
|
||||
|
||||
adrs_to_index.push(a.clone());
|
||||
updated_adr = Some(a.clone());
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(adr) = updated_adr {
|
||||
let idx = state.get_search_index().await;
|
||||
for a in &adrs_to_index {
|
||||
drop(idx.index_adr(a));
|
||||
}
|
||||
state.record_activity(
|
||||
"decision",
|
||||
&format!("Updated {}: {}", adr.id, adr.title),
|
||||
Some(&adr.status),
|
||||
);
|
||||
Ok(format!("Updated decision {}: {} (status: {})", adr.id, adr.title, adr.status))
|
||||
} else {
|
||||
Err(crate::error::AppError::Internal(
|
||||
format!("Decision with id '{}' not found", id),
|
||||
))
|
||||
}
|
||||
}
|
||||
DecisionAction::Query => {
|
||||
let limit = req.limit.unwrap_or(20);
|
||||
let include_body = req.include_body.unwrap_or(true);
|
||||
@@ -458,6 +556,7 @@ impl McpTool for DecisionsHandler {
|
||||
crate::handlers::utils::contains_ignore_ascii_case(&a.title, q)
|
||||
|| crate::handlers::utils::contains_ignore_ascii_case(&a.context, q)
|
||||
|| crate::handlers::utils::contains_ignore_ascii_case(&a.decision, q)
|
||||
|| crate::handlers::utils::contains_ignore_ascii_case(&a.consequence, q)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@@ -476,6 +575,10 @@ impl McpTool for DecisionsHandler {
|
||||
"title": a.title,
|
||||
"status": a.status,
|
||||
"timestamp": a.timestamp,
|
||||
"git_commit": a.git_commit,
|
||||
"git_branch": a.git_branch,
|
||||
"resolved_at": a.resolved_at,
|
||||
"task_id": a.task_id,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
@@ -1787,6 +1890,11 @@ mod tests {
|
||||
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
|
||||
.unwrap();
|
||||
assert_eq!(res1, "Logged decision ADR-0001: Architecture");
|
||||
let q_dec = decision_handler
|
||||
.execute(json!({"action": "query"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(q_dec.contains("Simple"));
|
||||
|
||||
let debt_handler = TechDebtHandler;
|
||||
let args_debt = json!({
|
||||
@@ -2000,6 +2108,40 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(q_dec_res.contains("Use Axum"));
|
||||
assert!(q_dec_res.contains("Fast async API routing"));
|
||||
|
||||
let q_by_consequence = handler_dec
|
||||
.execute(serde_json::json!({"action": "query", "query": "Fast async"}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(q_by_consequence.contains("Use Axum"));
|
||||
|
||||
let update_res = handler_dec
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"action": "update",
|
||||
"id": "ADR-0001",
|
||||
"status": "implemented",
|
||||
"git_commit": "abc1234",
|
||||
"git_branch": "master",
|
||||
"task_id": "TASK-123"
|
||||
}),
|
||||
state.clone(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(update_res.contains("Updated decision ADR-0001"));
|
||||
assert!(update_res.contains("implemented"));
|
||||
|
||||
let q_after_update = handler_dec
|
||||
.execute(serde_json::json!({"action": "query", "include_body": false}), state.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(q_after_update.contains("implemented"));
|
||||
assert!(q_after_update.contains("abc1234"));
|
||||
assert!(q_after_update.contains("master"));
|
||||
assert!(q_after_update.contains("TASK-123"));
|
||||
assert!(q_after_update.contains("resolved_at"));
|
||||
|
||||
let del_dec_res = handler_dec
|
||||
.execute(serde_json::json!({"action": "delete", "id": "ADR-0001"}), state.clone())
|
||||
|
||||
@@ -367,6 +367,7 @@ pub struct Adr {
|
||||
pub title: String,
|
||||
pub context: String,
|
||||
pub decision: String,
|
||||
#[serde(alias = "consequences")]
|
||||
pub consequence: String,
|
||||
#[serde(default = "default_adr_status")]
|
||||
pub status: String,
|
||||
@@ -383,6 +384,14 @@ pub struct Adr {
|
||||
pub affected_components: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub author: Option<String>,
|
||||
#[serde(default)]
|
||||
pub git_commit: Option<String>,
|
||||
#[serde(default)]
|
||||
pub git_branch: Option<String>,
|
||||
#[serde(default)]
|
||||
pub resolved_at: Option<u64>,
|
||||
#[serde(default)]
|
||||
pub task_id: Option<String>,
|
||||
}
|
||||
|
||||
fn default_adr_status() -> String {
|
||||
|
||||
+14
-4
@@ -792,7 +792,7 @@ pub struct SnippetsTool {
|
||||
pub line_range: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DecisionAction {
|
||||
#[serde(alias = "log", alias = "LOG", alias = "Log")]
|
||||
@@ -801,15 +801,18 @@ pub enum DecisionAction {
|
||||
Query,
|
||||
#[serde(alias = "delete", alias = "DELETE", alias = "Delete")]
|
||||
Delete,
|
||||
#[serde(alias = "update", alias = "UPDATE", alias = "Update")]
|
||||
Update,
|
||||
}
|
||||
|
||||
/// Architectural Decision Records (ADRs) log (log, query, delete).
|
||||
/// Architectural Decision Records (ADRs) log (log, query, update, delete).
|
||||
/// Actions:
|
||||
/// - 'log': Record ADR. Required: title. Optional: status, context, decision, consequences, namespace.
|
||||
/// - 'query': Search ADRs. Optional: query, namespace.
|
||||
/// - 'update': Update ADR status or implementation info. Required: id. Optional: status, git_commit, git_branch, task_id, context, decision, consequences, title.
|
||||
/// - 'delete': Remove ADR. Required: id.
|
||||
/// Next steps on error: Provide title for 'log' or id for 'delete'.
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
|
||||
/// Next steps on error: Provide title for 'log' or id for 'update'/'delete'.
|
||||
#[derive(Deserialize, Serialize, JsonSchema, Debug, Clone)]
|
||||
pub struct DecisionsTool {
|
||||
/// Action to perform: 'log', 'query', or 'delete'.
|
||||
pub action: DecisionAction,
|
||||
@@ -824,6 +827,7 @@ pub struct DecisionsTool {
|
||||
/// Decision made (for 'log').
|
||||
pub decision: Option<String>,
|
||||
/// Consequences & tradeoffs (for 'log').
|
||||
#[serde(alias = "consequence")]
|
||||
pub consequences: Option<String>,
|
||||
/// Search query string (for 'query').
|
||||
pub query: Option<String>,
|
||||
@@ -843,6 +847,12 @@ pub struct DecisionsTool {
|
||||
pub limit: Option<usize>,
|
||||
/// Include the full body of the matched decisions. Defaults to true.
|
||||
pub include_body: Option<bool>,
|
||||
/// Optional Git commit where decision was implemented.
|
||||
pub git_commit: Option<String>,
|
||||
/// Optional Git branch where decision was implemented.
|
||||
pub git_branch: Option<String>,
|
||||
/// Optional task ID linked to this decision.
|
||||
pub task_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, JsonSchema, PartialEq)]
|
||||
|
||||
Reference in new issue
Block a user