mirror of
https://github.com/myronblair/jarvis
synced 2026-06-30 17:50:23 -05:00
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:
@@ -760,6 +760,21 @@ if (!$reply) {
|
||||
if ($ov > 0) $parts[] = $ov . ' overdue task' . ($ov > 1 ? 's' : '') . ' need attention';
|
||||
$ai = (int)($email_actions['cnt'] ?? 0);
|
||||
if ($ai > 0) $parts[] = $ai . ' email' . ($ai > 1 ? 's' : '') . ' require action';
|
||||
// Include active directive progress summary
|
||||
$active_dirs = JarvisDB::query(
|
||||
"SELECT d.title,
|
||||
COALESCE(SUM(kr.current_value),0) AS cur,
|
||||
COALESCE(SUM(kr.target_value),1) AS tgt
|
||||
FROM directives d
|
||||
LEFT JOIN directive_key_results kr ON kr.directive_id=d.id
|
||||
WHERE d.status='active'
|
||||
GROUP BY d.id
|
||||
ORDER BY d.priority DESC LIMIT 3"
|
||||
) ?? [];
|
||||
if ($active_dirs) {
|
||||
$dp = array_map(fn($d) => $d['title'] . ' (' . round($d['cur'] / max($d['tgt'],1) * 100) . '%)', $active_dirs);
|
||||
$parts[] = count($active_dirs) . ' active directive' . (count($active_dirs) > 1 ? 's' : '') . ': ' . implode(', ', $dp);
|
||||
}
|
||||
$reply = $parts
|
||||
? "Good morning, {$userAddr}. " . implode('. ', $parts) . '.'
|
||||
: "Good morning, {$userAddr}. Your schedule is clear — no tasks, appointments, or email actions pending today.";
|
||||
@@ -1355,6 +1370,31 @@ if (!$reply) {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tier 0.9i: Directives — review objectives, progress check ─────────────────
|
||||
if (!$reply) {
|
||||
$dirReviewPatterns = [
|
||||
'/^(?:jarvis[,\s]+)?(?:review\s+(?:my\s+)?(?:directives?|objectives?|goals?|OKRs?))/i',
|
||||
'/^(?:jarvis[,\s]+)?(?:how\s+am\s+i\s+doing\s+on\s+(?:my\s+)?(?:directives?|objectives?|goals?))/i',
|
||||
'/^(?:jarvis[,\s]+)?(?:directives?\s+(?:review|status|update|progress|briefing))/i',
|
||||
'/^(?:jarvis[,\s]+)?(?:OKR\s+(?:review|update|status))/i',
|
||||
'/^(?:jarvis[,\s]+)?(?:what(?:\'s|\s+is)\s+(?:my\s+)?(?:progress|status)\s+on\s+(?:my\s+)?(?:directives?|goals?|objectives?))/i',
|
||||
];
|
||||
foreach ($dirReviewPatterns as $pat) {
|
||||
if (preg_match($pat, $message)) {
|
||||
$arcRes = arcSubmitJob('directive_review', ['provider' => 'claude'], $sessionId);
|
||||
if (isset($arcRes['job_id'])) {
|
||||
$arcJobId = $arcRes['job_id'];
|
||||
$reply = "◈ DIRECTIVE REVIEW INITIATED (Job #{$arcJobId}). I'm analyzing your active objectives and key results now, {$userAddr}. Stand by for your progress briefing.";
|
||||
$source = 'arc:directive_review';
|
||||
} else {
|
||||
$reply = "Directive review is offline, {$userAddr}. Arc Reactor may be unavailable.";
|
||||
$source = 'arc:offline';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Tier 1: Intent Engine (instant, no LLM) ───────────────────────────────
|
||||
if (!$reply) {
|
||||
$matched = KBEngine::match($message);
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
// JARVIS Directives — OKR / mission goal tracking
|
||||
// Actions: list | get | save | delete | key_result_update | link | unlink | review
|
||||
|
||||
switch ($action) {
|
||||
|
||||
// GET directives/list — all active directives with key results + progress
|
||||
case 'list':
|
||||
$status = $_GET['status'] ?? 'active';
|
||||
$category = $_GET['category'] ?? '';
|
||||
$where = '1=1';
|
||||
$params = [];
|
||||
if ($status && $status !== 'all') { $where .= ' AND d.status=?'; $params[] = $status; }
|
||||
if ($category) { $where .= ' AND d.category=?'; $params[] = $category; }
|
||||
$rows = JarvisDB::query(
|
||||
"SELECT d.*,
|
||||
COUNT(kr.id) AS kr_count,
|
||||
COALESCE(SUM(kr.current_value),0) AS kr_current_sum,
|
||||
COALESCE(SUM(kr.target_value),0) AS kr_target_sum,
|
||||
(SELECT COUNT(*) FROM directive_links dl WHERE dl.directive_id=d.id) AS link_count
|
||||
FROM directives d
|
||||
LEFT JOIN directive_key_results kr ON kr.directive_id=d.id
|
||||
WHERE {$where}
|
||||
GROUP BY d.id
|
||||
ORDER BY d.priority DESC, d.target_date ASC, d.created_at DESC",
|
||||
$params
|
||||
) ?: [];
|
||||
// Compute progress pct per directive
|
||||
foreach ($rows as &$r) {
|
||||
$r['progress'] = ($r['kr_target_sum'] > 0)
|
||||
? (float)round($r['kr_current_sum'] / $r['kr_target_sum'] * 100, 1)
|
||||
: 0;
|
||||
}
|
||||
unset($r);
|
||||
echo json_encode(['directives' => $rows]);
|
||||
break;
|
||||
|
||||
// GET directives/get?id=X — full directive with key results and links
|
||||
case 'get':
|
||||
$id = (int)($_GET['id'] ?? 0);
|
||||
if (!$id) { echo json_encode(['error' => 'Missing id']); break; }
|
||||
$d = JarvisDB::single("SELECT * FROM directives WHERE id=?", [$id]);
|
||||
if (!$d) { echo json_encode(['error' => 'Not found']); break; }
|
||||
$krs = JarvisDB::query("SELECT * FROM directive_key_results WHERE directive_id=? ORDER BY id ASC", [$id]) ?: [];
|
||||
$links = JarvisDB::query(
|
||||
"SELECT dl.*, t.title AS task_title, a.title AS appt_title
|
||||
FROM directive_links dl
|
||||
LEFT JOIN tasks t ON dl.link_type='task' AND t.id=dl.link_id
|
||||
LEFT JOIN appointments a ON dl.link_type='appointment' AND a.id=dl.link_id
|
||||
WHERE dl.directive_id=?
|
||||
ORDER BY dl.created_at DESC",
|
||||
[$id]
|
||||
) ?: [];
|
||||
$sum_cur = array_sum(array_column($krs, 'current_value'));
|
||||
$sum_tgt = array_sum(array_column($krs, 'target_value'));
|
||||
$d['progress'] = ($sum_tgt > 0) ? round($sum_cur / $sum_tgt * 100, 1) : 0;
|
||||
$d['key_results'] = $krs;
|
||||
$d['links'] = $links;
|
||||
echo json_encode($d);
|
||||
break;
|
||||
|
||||
// POST directives/save — create or update directive + key results
|
||||
case 'save':
|
||||
$id = (int)($data['id'] ?? 0);
|
||||
$title = trim($data['title'] ?? '');
|
||||
$description = trim($data['description'] ?? '');
|
||||
$category = $data['category'] ?? 'work';
|
||||
$status = $data['status'] ?? 'active';
|
||||
$priority = (int)($data['priority'] ?? 5);
|
||||
$target_date = !empty($data['target_date']) ? $data['target_date'] : null;
|
||||
if (!$title) { echo json_encode(['error' => 'Title required']); break; }
|
||||
if ($id) {
|
||||
JarvisDB::execute(
|
||||
"UPDATE directives SET title=?,description=?,category=?,status=?,priority=?,target_date=?,updated_at=NOW() WHERE id=?",
|
||||
[$title,$description,$category,$status,$priority,$target_date,$id]
|
||||
);
|
||||
} else {
|
||||
$id = JarvisDB::insert(
|
||||
"INSERT INTO directives (title,description,category,status,priority,target_date) VALUES (?,?,?,?,?,?)",
|
||||
[$title,$description,$category,$status,$priority,$target_date]
|
||||
);
|
||||
}
|
||||
// Replace key results if provided
|
||||
if (isset($data['key_results']) && is_array($data['key_results'])) {
|
||||
JarvisDB::execute("DELETE FROM directive_key_results WHERE directive_id=?", [$id]);
|
||||
foreach ($data['key_results'] as $kr) {
|
||||
$krtitle = trim($kr['title'] ?? ''); if (!$krtitle) continue;
|
||||
JarvisDB::execute(
|
||||
"INSERT INTO directive_key_results (directive_id,title,current_value,target_value,unit) VALUES (?,?,?,?,?)",
|
||||
[$id, $krtitle, (float)($kr['current_value']??0), (float)($kr['target_value']??100), $kr['unit']??'%']
|
||||
);
|
||||
}
|
||||
}
|
||||
echo json_encode(['ok' => true, 'id' => $id]);
|
||||
break;
|
||||
|
||||
// POST directives/delete?id=X
|
||||
case 'delete':
|
||||
$id = (int)($_GET['id'] ?? $data['id'] ?? 0);
|
||||
if (!$id) { echo json_encode(['error' => 'Missing id']); break; }
|
||||
JarvisDB::execute("DELETE FROM directive_key_results WHERE directive_id=?", [$id]);
|
||||
JarvisDB::execute("DELETE FROM directive_links WHERE directive_id=?", [$id]);
|
||||
JarvisDB::execute("DELETE FROM directives WHERE id=?", [$id]);
|
||||
echo json_encode(['ok' => true]);
|
||||
break;
|
||||
|
||||
// POST directives/key_result_update — update a single KR's current value
|
||||
case 'key_result_update':
|
||||
$krid = (int)($data['id'] ?? 0);
|
||||
$value = (float)($data['current_value'] ?? 0);
|
||||
if (!$krid) { echo json_encode(['error' => 'Missing kr id']); break; }
|
||||
JarvisDB::execute(
|
||||
"UPDATE directive_key_results SET current_value=?, updated_at=NOW() WHERE id=?",
|
||||
[$value, $krid]
|
||||
);
|
||||
echo json_encode(['ok' => true]);
|
||||
break;
|
||||
|
||||
// POST directives/link — link a task or appointment to a directive
|
||||
case 'link':
|
||||
$did = (int)($data['directive_id'] ?? 0);
|
||||
$link_type = $data['link_type'] ?? 'task';
|
||||
$link_id = (int)($data['link_id'] ?? 0);
|
||||
$note = trim($data['note'] ?? '');
|
||||
if (!$did) { echo json_encode(['error' => 'Missing directive_id']); break; }
|
||||
JarvisDB::execute(
|
||||
"INSERT INTO directive_links (directive_id,link_type,link_id,note) VALUES (?,?,?,?)",
|
||||
[$did, $link_type, $link_id ?: null, $note]
|
||||
);
|
||||
echo json_encode(['ok' => true]);
|
||||
break;
|
||||
|
||||
// POST directives/unlink?id=X — remove a link
|
||||
case 'unlink':
|
||||
$lid = (int)($_GET['id'] ?? $data['id'] ?? 0);
|
||||
if (!$lid) { echo json_encode(['error' => 'Missing id']); break; }
|
||||
JarvisDB::execute("DELETE FROM directive_links WHERE id=?", [$lid]);
|
||||
echo json_encode(['ok' => true]);
|
||||
break;
|
||||
|
||||
// GET directives/summary — compact progress snapshot for chat/briefing injection
|
||||
case 'summary':
|
||||
$rows = JarvisDB::query(
|
||||
"SELECT d.id, d.title, d.category, d.target_date,
|
||||
COALESCE(SUM(kr.current_value),0) AS kr_cur,
|
||||
COALESCE(SUM(kr.target_value),1) AS kr_tgt
|
||||
FROM directives d
|
||||
LEFT JOIN directive_key_results kr ON kr.directive_id=d.id
|
||||
WHERE d.status='active'
|
||||
GROUP BY d.id
|
||||
ORDER BY d.priority DESC, d.target_date ASC
|
||||
LIMIT 10"
|
||||
) ?: [];
|
||||
$summary = [];
|
||||
foreach ($rows as $r) {
|
||||
$pct = round($r['kr_cur'] / max($r['kr_tgt'], 1) * 100, 0);
|
||||
$summary[] = [
|
||||
'id' => $r['id'],
|
||||
'title' => $r['title'],
|
||||
'category' => $r['category'],
|
||||
'target_date' => $r['target_date'],
|
||||
'progress' => $pct,
|
||||
];
|
||||
}
|
||||
echo json_encode(['directives' => $summary]);
|
||||
break;
|
||||
|
||||
default:
|
||||
http_response_code(404);
|
||||
echo json_encode(['error' => "Unknown directives action: {$action}"]);
|
||||
}
|
||||
Reference in New Issue
Block a user