Phase 8: Mission Directives — OKR/goal tracking with AI review

- DB: directives, directive_key_results, directive_links tables
- reactor.py v8.0.0: directive_review handler — fetches active directives + KRs + links, Claude generates executive progress briefing, injects into conversations
- directives.php: new API endpoint (list/get/save/delete/key_result_update/link/summary)
- api.php: routes directives/* endpoint
- admin/index.php: Directives nav + tab — objective cards with progress bars, editor with multi-KR builder (title/current/target/unit), AI Review button per directive and global
- index.html: DIRECTIVES tab — collapsible objective cards with progress bars, KR counts, AI Review button, link to admin
- chat.php: Tier 0.9i directive review detection; daily briefing now includes active directive progress %

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 11:59:44 +00:00
parent b6c417948e
commit aaf07edacb
5 changed files with 690 additions and 1 deletions
+89 -1
View File
@@ -948,6 +948,20 @@ body::after{
.comms-compose-field{width:100%;background:rgba(0,212,255,0.04);border:1px solid var(--panel-border);border-radius:3px;padding:6px 8px;color:var(--text);font-family:var(--font-mono);font-size:0.6rem;box-sizing:border-box;margin-bottom:7px}
.comms-compose-field:focus{outline:none;border-color:var(--cyan)}
.comms-compose-actions{display:flex;gap:6px;margin-top:8px}
/* ── DIRECTIVES HUD ──────────────────────────────────────────────── */
.dir-card{background:rgba(0,212,255,0.03);border:1px solid var(--panel-border);border-radius:var(--r);margin-bottom:7px;overflow:hidden}
.dir-card-head{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;user-select:none}
.dir-card-head:hover{background:rgba(0,212,255,0.06)}
.dir-card-title{font-family:var(--font-display);font-size:0.6rem;letter-spacing:1px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dir-card-body{display:none;padding:0 10px 10px;border-top:1px solid var(--panel-border)}
.dir-card.open .dir-card-body{display:block}
.dir-progress-bar{height:5px;background:rgba(255,255,255,0.08);border-radius:3px;margin:6px 0}
.dir-progress-fill{height:100%;border-radius:3px;transition:width 0.4s ease}
.dir-kr-row{display:flex;align-items:center;gap:6px;margin:4px 0;font-family:var(--font-mono);font-size:0.52rem;color:var(--text-dim)}
.dir-kr-bar{flex:1;height:3px;background:rgba(255,255,255,0.06);border-radius:2px}
.dir-kr-fill{height:100%;border-radius:2px;background:rgba(0,212,255,0.5)}
.dir-admin-btn{width:100%;background:rgba(0,212,255,0.06);border:1px solid rgba(0,212,255,0.3);border-radius:4px;padding:5px;color:var(--cyan);font-family:var(--font-display);font-size:0.52rem;letter-spacing:2px;cursor:pointer;margin-bottom:7px}
.dir-admin-btn:hover{background:rgba(0,212,255,0.12)}
/* ── MISSION OPS HUD ─────────────────────────────────────────────── */
.mission-card{background:rgba(0,212,255,0.03);border:1px solid var(--panel-border);border-radius:var(--r);margin-bottom:7px;overflow:hidden}
.mission-card-head{display:flex;align-items:center;gap:8px;padding:8px 10px;cursor:pointer;user-select:none}
@@ -1193,6 +1207,7 @@ body::after{
<div class="tab" id="tab-btn-comms" onclick="switchTab('comms')">COMMS</div>
<div class="tab" id="tab-btn-guardian" onclick="switchTab('guardian')">GUARDIAN</div>
<div class="tab" id="tab-btn-missions" onclick="switchTab('missions')">MISSIONS</div>
<div class="tab" id="tab-btn-directives" onclick="switchTab('directives')">DIRECTIVES</div>
</div>
<div id="tab-vms" class="tab-pane" style="overflow-y:auto;flex:1">
<div id="vm-list"><div class="loading-shimmer"></div></div>
@@ -1223,6 +1238,9 @@ body::after{
<div id="tab-missions" class="tab-pane" style="overflow-y:auto;flex:1;padding:4px 0">
<div id="missions-hud"><div class="loading-shimmer"></div></div>
</div>
<div id="tab-directives" class="tab-pane" style="overflow-y:auto;flex:1;padding:4px 0">
<div id="directives-hud"><div class="loading-shimmer"></div></div>
</div>
</div>
</div>
@@ -3143,7 +3161,8 @@ function switchTab(name) {
if (name === 'intel') loadIntel();
if (name === 'comms') { loadComms(); loadCommsOutbox(); }
if (name === 'guardian') loadGuardian();
if (name === 'missions') loadMissionsHud();
if (name === 'missions') loadMissionsHud();
if (name === 'directives') loadDirectivesHud();
if (name === 'alerts') loadAlerts();
}
@@ -4272,6 +4291,75 @@ async function hudRunMission(id) {
}
}
// ── DIRECTIVES HUD ────────────────────────────────────────────────────────────
let _dirOpenCards = new Set();
async function loadDirectivesHud() {
const el = document.getElementById('directives-hud');
if (!el) return;
try {
const d = await api('directives/list?status=active');
const list = (d.directives || []);
let html = '<button class="dir-admin-btn" onclick="window.open(\'/admin#directives\',\'_blank\')">◈ MANAGE IN ADMIN</button>';
if (!list.length) {
html += '<div class="comms-empty">◈ NO ACTIVE DIRECTIVES<br><span style="opacity:0.5">Create objectives in Admin → Directives</span></div>';
el.innerHTML = html;
return;
}
const catColors = {work:'var(--cyan)',personal:'#a78bfa',health:'#00ff88',finance:'#ffd700',home:'var(--panel-border)',other:'var(--text-dim)'};
for (const dir of list) {
const pct = Math.min(100, Math.round(dir.progress || 0));
const isOpen = _dirOpenCards.has(dir.id);
const color = catColors[dir.category] || 'var(--cyan)';
const fillColor = pct >= 80 ? '#00ff88' : pct >= 40 ? '#ffd700' : '#ff6644';
const daysLeft = dir.target_date
? Math.ceil((new Date(dir.target_date) - new Date()) / 86400000) : null;
const dueTxt = daysLeft !== null
? (daysLeft < 0 ? `OVERDUE ${Math.abs(daysLeft)}d` : `${daysLeft}d left`)
: '';
const dueColor = daysLeft !== null && daysLeft < 0 ? '#ff2244' : daysLeft < 14 ? '#ffd700' : 'var(--text-dim)';
html += `<div class="dir-card${isOpen?' open':''}" id="dir-card-${dir.id}">
<div class="dir-card-head" onclick="toggleDirCard(${dir.id})">
<span style="font-family:var(--font-mono);font-size:0.55rem;color:${color};flex-shrink:0">${dir.category.toUpperCase()}</span>
<span class="dir-card-title" style="color:${color}">${escHtml(dir.title)}</span>
<span style="font-family:var(--font-mono);font-size:0.55rem;color:${fillColor};flex-shrink:0">${pct}%</span>
${dueTxt ? `<span style="font-family:var(--font-mono);font-size:0.48rem;color:${dueColor};flex-shrink:0">${dueTxt}</span>` : ''}
</div>
<div class="dir-card-body">
<div class="dir-progress-bar"><div class="dir-progress-fill" style="width:${pct}%;background:${fillColor}"></div></div>
<div style="font-family:var(--font-mono);font-size:0.5rem;color:var(--text-dim);margin-bottom:6px">${dir.kr_count||0} KEY RESULTS · ${dir.link_count||0} LINKED ITEMS</div>
<button onclick="hudDirectiveReview(${dir.id})" style="background:rgba(0,212,255,0.06);border:1px solid rgba(0,212,255,0.2);border-radius:3px;padding:3px 8px;color:var(--cyan);font-family:var(--font-display);font-size:0.48rem;letter-spacing:1px;cursor:pointer">◈ AI REVIEW</button>
</div>
</div>`;
}
el.innerHTML = html;
} catch(e) {
if (el) el.innerHTML = '<div class="comms-empty">DIRECTIVES OFFLINE</div>';
}
}
function toggleDirCard(id) {
const card = document.getElementById('dir-card-' + id);
if (!card) return;
if (_dirOpenCards.has(id)) _dirOpenCards.delete(id);
else _dirOpenCards.add(id);
card.classList.toggle('open');
}
async function hudDirectiveReview(id) {
const res = await api('arc?action=job_create', 'POST', {
type: 'directive_review', payload: {directive_id: id, provider: 'claude'}, priority: 6,
});
if (res.job_id) {
addMessage('jarvis', `◈ DIRECTIVE REVIEW initiated (Job #${res.job_id}). Analyzing objectives and key results now. Results will appear here shortly.`);
speak(`Directive review underway. I'll brief you on your progress in a moment.`);
}
}
async function loadAgents() {
const [listData, metricsData] = await Promise.all([
api('agent/list'),