@hanzlaa/rcode 3.4.33 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/AGENTS.md +6 -6
  2. package/CONTRIBUTING.md +2 -0
  3. package/LICENSE +21 -0
  4. package/README.md +66 -403
  5. package/cli/doctor.js +87 -1
  6. package/cli/install.js +122 -31
  7. package/cli/lib/schemas.cjs +318 -0
  8. package/cli/postinstall.js +19 -3
  9. package/dist/rcode.js +316 -23
  10. package/package.json +14 -4
  11. package/rihal/agents/rihal-cross-platform-auditor.md +1 -1
  12. package/rihal/agents/rihal-dep-auditor.md +1 -1
  13. package/rihal/agents/rihal-docs-auditor.md +3 -145
  14. package/rihal/agents/rihal-i18n-auditor.md +1 -1
  15. package/rihal/agents/rihal-nyquist-auditor.md +4 -156
  16. package/rihal/agents/rihal-observability-auditor.md +1 -1
  17. package/rihal/bin/rihal-hooks.cjs +394 -4
  18. package/rihal/bin/rihal-tools.cjs +891 -24
  19. package/rihal/commands/create-prd.md +18 -0
  20. package/rihal/commands/execute-milestone.md +18 -0
  21. package/rihal/commands/plan-milestone.md +18 -0
  22. package/rihal/commands/scaffold-milestone.md +18 -0
  23. package/rihal/commands/scaffold-skill.md +18 -0
  24. package/rihal/references/REFERENCES_INDEX.md +49 -7
  25. package/rihal/references/agent-contracts.md +10 -0
  26. package/rihal/references/design-tokens.md +98 -0
  27. package/rihal/references/docs-auditor-playbook.md +148 -0
  28. package/rihal/references/git-preflight.md +117 -0
  29. package/rihal/references/iterative-retrieval.md +85 -0
  30. package/rihal/references/nyquist-auditor-playbook.md +157 -0
  31. package/rihal/references/workstream-flag.md +2 -2
  32. package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +9 -0
  33. package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +9 -0
  34. package/rihal/skills/actions/4-implementation/rihal-ci/SKILL.md +4 -0
  35. package/rihal/skills/actions/4-implementation/rihal-code-review/steps/step-02-review.md +2 -2
  36. package/rihal/skills/actions/4-implementation/rihal-harden/SKILL.md +4 -0
  37. package/rihal/skills/actions/4-implementation/rihal-migrate/SKILL.md +4 -0
  38. package/rihal/skills/agents/haitham-frontend/SKILL.md +2 -0
  39. package/rihal/templates/settings-hooks.json +39 -0
  40. package/rihal/workflows/check-todos.md +4 -0
  41. package/rihal/workflows/code-review-fix.md +4 -3
  42. package/rihal/workflows/code-review.md +1 -1
  43. package/rihal/workflows/debug.md +1 -1
  44. package/rihal/workflows/dev-story.md +4 -0
  45. package/rihal/workflows/diff.md +2 -2
  46. package/rihal/workflows/do.md +16 -8
  47. package/rihal/workflows/docs-update.md +2 -2
  48. package/rihal/workflows/enable-hooks.md +6 -1
  49. package/rihal/workflows/execute-milestone.md +139 -0
  50. package/rihal/workflows/execute-regression-gates.md +1 -1
  51. package/rihal/workflows/execute-sprint.md +54 -2
  52. package/rihal/workflows/execute-verify-phase-goal.md +31 -4
  53. package/rihal/workflows/execute-waves.md +33 -5
  54. package/rihal/workflows/execute.md +40 -6
  55. package/rihal/workflows/help.md +1 -1
  56. package/rihal/workflows/import.md +1 -1
  57. package/rihal/workflows/lens-audit.md +39 -23
  58. package/rihal/workflows/list-workspaces.md +1 -1
  59. package/rihal/workflows/map-codebase.md +4 -4
  60. package/rihal/workflows/new-milestone.md +18 -1
  61. package/rihal/workflows/new-project-research.md +53 -1
  62. package/rihal/workflows/new-workspace.md +1 -1
  63. package/rihal/workflows/plan-milestone.md +105 -0
  64. package/rihal/workflows/plan-research-validation.md +1 -1
  65. package/rihal/workflows/plan-spawn-planner.md +1 -1
  66. package/rihal/workflows/plan.md +31 -3
  67. package/rihal/workflows/plant-seed.md +6 -0
  68. package/rihal/workflows/quick.md +11 -5
  69. package/rihal/workflows/research-phase.md +24 -0
  70. package/rihal/workflows/scaffold-milestone.md +60 -0
  71. package/rihal/workflows/scaffold-skill.md +137 -0
  72. package/rihal/workflows/scan.md +1 -1
  73. package/rihal/workflows/session-report.md +43 -3
  74. package/rihal/workflows/verify-work.md +3 -3
  75. package/server/dashboard.js +154 -5
  76. package/server/lib/html/client/agents-data.js +27 -0
  77. package/server/lib/html/client/app.js +15 -0
  78. package/server/lib/html/client/components/App.js +211 -0
  79. package/server/lib/html/client/components/OrchPanel.js +293 -0
  80. package/server/lib/html/client/components/Sidebar.js +73 -0
  81. package/server/lib/html/client/components/Topbar.js +53 -0
  82. package/server/lib/html/client/components/XtermPanel.js +220 -0
  83. package/server/lib/html/client/components/shared.js +330 -0
  84. package/server/lib/html/client/icons-client.js +85 -0
  85. package/server/lib/html/client/orchestrator.js +279 -0
  86. package/server/lib/html/client/preact.js +34 -0
  87. package/server/lib/html/client/store.js +91 -0
  88. package/server/lib/html/client/util.js +186 -0
  89. package/server/lib/html/client/views/AgentsView.js +83 -0
  90. package/server/lib/html/client/views/DecisionsView.js +102 -0
  91. package/server/lib/html/client/views/FilesView.js +223 -0
  92. package/server/lib/html/client/views/KanbanView.js +236 -0
  93. package/server/lib/html/client/views/MemoryView.js +157 -0
  94. package/server/lib/html/client/views/MilestonesView.js +136 -0
  95. package/server/lib/html/client/views/OrchestrationView.js +167 -0
  96. package/server/lib/html/client/views/OverviewView.js +221 -0
  97. package/server/lib/html/client/views/PhasesView.js +184 -0
  98. package/server/lib/html/client/views/RoadmapView.js +238 -0
  99. package/server/lib/html/client/views/SprintsView.js +178 -0
  100. package/server/lib/html/client/views/TasksView.js +148 -0
  101. package/server/lib/html/client.js +42 -1064
  102. package/server/lib/html/css.js +2266 -466
  103. package/server/lib/html/icons.js +68 -0
  104. package/server/lib/html/shell.js +16 -210
  105. package/server/lib/scanner.js +109 -0
  106. package/server/orchestrator.js +362 -0
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Inline SVG icon set (Lucide-style stroke icons).
3
+ *
4
+ * No CDN, no font — just path data rendered into a <svg>. Used server-side
5
+ * by shell.js for static chrome, and embedded as window.__ICONS__ so the
6
+ * client modules can render the same icons in dynamic markup.
7
+ *
8
+ * IMPORTANT: The ICONS map is duplicated in the ESM client counterpart:
9
+ * server/lib/html/client/icons-client.js (in sync with this file)
10
+ * Keep both files in sync when adding or changing icon paths. The duplication
11
+ * is the no-build-step cost — the browser cannot import a CJS module as ESM.
12
+ */
13
+
14
+ // name → inner SVG markup (viewBox 0 0 24 24, stroke = currentColor)
15
+ // Keep in sync with server/lib/html/client/icons-client.js
16
+ const ICONS = {
17
+ home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/>',
18
+ activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
19
+ map: '<polygon points="3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21"/><line x1="9" y1="3" x2="9" y2="18"/><line x1="15" y1="6" x2="15" y2="21"/>',
20
+ target: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
21
+ layers: '<polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/>',
22
+ zap: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
23
+ checkSquare: '<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>',
24
+ kanban: '<rect x="3" y="4" width="5" height="16" rx="1"/><rect x="10" y="4" width="5" height="11" rx="1"/><rect x="17" y="4" width="5" height="14" rx="1"/>',
25
+ file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>',
26
+ users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
27
+ scale: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/>',
28
+ database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/><path d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3"/>',
29
+ play: '<polygon points="6 3 20 12 6 21 6 3"/>',
30
+ terminal: '<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>',
31
+ square: '<rect x="6" y="6" width="12" height="12" rx="1"/>',
32
+ x: '<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
33
+ minimize: '<line x1="5" y1="14" x2="19" y2="14"/>',
34
+ maximize: '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>',
35
+ clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
36
+ eye: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>',
37
+ filePen: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h7"/><polyline points="14 2 14 8 20 8"/><path d="M18.4 12.6a2 2 0 0 1 3 3L17 20l-4 1 1-4z"/>',
38
+ hourglass: '<path d="M5 22h14M5 2h14M17 22v-4.17a2 2 0 0 0-.59-1.42L12 12l-4.41 4.41A2 2 0 0 0 7 17.83V22M7 2v4.17a2 2 0 0 0 .59 1.42L12 12l4.41-4.41A2 2 0 0 0 17 6.17V2"/>',
39
+
40
+ // Added in sprint 32.2 — emoji-to-SVG sweep
41
+ building: '<rect x="4" y="2" width="16" height="20" rx="2"/><path d="M9 22V12h6v10"/><path d="M8 7h.01M12 7h.01M16 7h.01M8 11h.01M12 11h.01M16 11h.01"/>',
42
+ link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
43
+ 'alert-triangle':'<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
44
+ brain: '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46 2.5 2.5 0 0 1-1.28-4.56A3 3 0 0 1 5 12c0-.56.15-1.1.42-1.57a2.5 2.5 0 0 1-.42-4.93V5.5A2.5 2.5 0 0 1 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46 2.5 2.5 0 0 0 1.28-4.56A3 3 0 0 0 19 12c0-.56-.15-1.1-.42-1.57a2.5 2.5 0 0 0 .42-4.93V5.5A2.5 2.5 0 0 0 14.5 2z"/>',
45
+ 'clipboard-list':'<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><line x1="12" y1="11" x2="12" y2="17"/><line x1="9" y1="14" x2="15" y2="14"/>',
46
+ flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/>',
47
+ monitor: '<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>',
48
+ 'file-text': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>',
49
+ copy: '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
50
+ lightbulb: '<line x1="9" y1="18" x2="15" y2="18"/><line x1="10" y1="22" x2="14" y2="22"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/>',
51
+ 'edit-3': '<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>',
52
+
53
+ // Added in sprint 32.3 — App/Topbar theme toggle icons
54
+ moon: '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>',
55
+ sun: '<circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.22" y1="4.22" x2="7.05" y2="7.05"/><line x1="16.95" y1="16.95" x2="19.78" y2="19.78"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.22" y1="19.78" x2="7.05" y2="16.95"/><line x1="16.95" y1="7.05" x2="19.78" y2="4.22"/>',
56
+ };
57
+
58
+ // Render an icon as an inline <svg>. size in px; cls adds extra classes.
59
+ function icon(name, size, cls) {
60
+ const p = ICONS[name];
61
+ if (!p) return '';
62
+ const s = size || 16;
63
+ return '<svg class="ic' + (cls ? ' ' + cls : '') + '" width="' + s + '" height="' + s +
64
+ '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
65
+ 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + p + '</svg>';
66
+ }
67
+
68
+ module.exports = { ICONS, icon };
@@ -6,49 +6,11 @@ const { renderClientJs } = require('./client');
6
6
 
7
7
  function esc(s) { return String(s || '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }
8
8
 
9
- function renderHtml(state) {
10
- const projectName = state.projectName || 'No project initialized';
11
- const currentPhase = state.currentPhase || '—';
12
- const currentSprint = state.currentSprint || null;
13
- const phaseCount = (state.raw?.phases || []).length;
14
- const decisionCount = (state.raw?.decisions || []).length;
15
- const artifactCount = state.planningFiles.length;
9
+ function renderHtml(state, orchToken) {
10
+ const projectName = state.projectName || 'No project initialized';
16
11
 
17
- const agents = [
18
- { name: 'Sadiq Damani', arabic: 'صادق', role: 'Director of Strategy', real: true, type: 'leadership' },
19
- { name: 'Waleed Al Harthi', arabic: 'وليد', role: 'CTO', real: true, type: 'leadership' },
20
- { name: 'Ahmed Al Hassani', arabic: 'أحمد الحسني', role: 'Technology & Development Director', real: true, type: 'leadership' },
21
- { name: 'Nasser', arabic: 'ناصر', role: 'Engineering Manager', real: true, type: 'leadership' },
22
- { name: 'Hussain', arabic: 'حسين', role: 'PM + Scrum Master', type: 'product' },
23
- { name: 'Layla', arabic: 'ليلى', role: 'Lead UX Designer', type: 'design' },
24
- { name: 'Zahra', arabic: 'زهرة', role: 'Branding & Creative Director', type: 'design' },
25
- { name: 'Omar', arabic: 'عمر', role: 'Full-Stack Engineer', type: 'engineering' },
26
- { name: 'Haitham Al Khamiyasi', arabic: 'هيثم', role: 'Senior Frontend', real: true, type: 'engineering' },
27
- { name: 'Yousef', arabic: 'يوسف', role: 'Senior Backend', type: 'engineering' },
28
- { name: 'Zayd', arabic: 'زيد', role: 'ML Engineer', type: 'engineering' },
29
- { name: 'Fatima', arabic: 'فاطمة', role: 'QA Lead', type: 'quality' },
30
- { name: 'Khalid', arabic: 'خالد', role: 'DevOps', type: 'engineering' },
31
- { name: 'Noor', arabic: 'نور', role: 'Scribe', type: 'support' },
32
- { name: 'Mariam', arabic: 'مريم', role: 'Marketing Lead', type: 'product' },
33
- { name: 'Raees', arabic: 'رئيس', role: 'Orchestration Director', type: 'system' },
34
- { name: 'Majlis', arabic: 'مجلس', role: 'Consulting Council', type: 'system' },
35
- { name: 'Diwan', arabic: 'ديوان', role: 'Dashboard Registry', type: 'system' },
36
- ];
37
-
38
- // #305: separate real vs AI agents
39
- const realAgents = agents.filter(a => a.real);
40
- const aiAgents = agents.filter(a => !a.real);
41
-
42
- function agentCard(a) {
43
- const filterText = (a.name + ' ' + a.role + ' ' + a.arabic + ' ' + a.type).toLowerCase();
44
- // #303: link to SKILL.md
45
- const skillName = a.name.split(' ')[0].toLowerCase();
46
- return `<div class="agent-card" data-filter-text="${filterText}" onclick="viewAgentSkill('${skillName}')" style="cursor:pointer;">
47
- <div class="name">${esc(a.name)}${a.real ? ' <span class="real-badge">real</span>' : ''} <span class="type-badge">${esc(a.type)}</span></div>
48
- <div class="arabic">${a.arabic}</div>
49
- <div class="role">${esc(a.role)}</div>
50
- </div>`;
51
- }
12
+ // Agent roster moved to server/lib/html/client/agents-data.js (Sprint 31.3).
13
+ // AgentsView.js renders it client-side; shell.js no longer needs it.
52
14
 
53
15
  return `<!DOCTYPE html>
54
16
  <html lang="en" dir="ltr">
@@ -57,181 +19,25 @@ function renderHtml(state) {
57
19
  <meta name="viewport" content="width=device-width, initial-scale=1">
58
20
  <title>Majlis — ${esc(projectName)}</title>
59
21
  <link rel="preconnect" href="https://fonts.googleapis.com">
60
- <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
61
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"><\/script>
22
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
23
+ <script src="https://cdn.jsdelivr.net/npm/marked@15.0.7/marked.min.js"><\/script>
24
+ <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"><\/script>
25
+ <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"><\/script>
26
+ <script>window.__ORCH_TOKEN__ = ${JSON.stringify(orchToken || '')};<\/script>
62
27
  ${renderCss()}
63
28
  </head>
64
29
  <body>
65
- <div class="app-shell">
66
- <aside class="sidebar">
67
- <div class="sidebar-project">
68
- <div class="project-label">Project</div>
69
- ${esc(projectName)}
70
- </div>
71
- <nav>
72
- <div class="nav-section">Overview</div>
73
- <button class="nav-link" data-view="overview">🏠 Overview</button>
74
- <button class="nav-link" data-view="roadmap">🗺 Roadmap</button>
75
- <div class="nav-section">Planning</div>
76
- <button class="nav-link" data-view="milestones">🎯 Milestones</button>
77
- <button class="nav-link" data-view="phases">📋 Phases</button>
78
- <button class="nav-link" data-view="sprints">⚡ Sprints</button>
79
- <button class="nav-link" data-view="tasks">✓ Tasks</button>
80
- <div class="nav-section">Workspace</div>
81
- <button class="nav-link" data-view="files">📄 Files</button>
82
- <button class="nav-link" data-view="agents">🤝 Agents</button>
83
- <button class="nav-link" data-view="decisions">⚖ Decisions</button>
84
- <button class="nav-link" data-view="memory">🧠 Memory Bank</button>
85
- </nav>
86
- </aside>
87
- <div id="sidebar-backdrop" onclick="closeSidebar()"></div>
88
- <div class="content-area" id="main-content">
89
- <header>
90
- <div style="display:flex;align-items:center;gap:var(--space-3);">
91
- <button class="hamburger-btn" id="hamburger-btn" onclick="toggleSidebar()" aria-label="Toggle menu">
92
- <span></span><span></span><span></span>
93
- </button>
94
- <div class="brand">
95
- <div class="icon">🕌</div>
96
- <div>
97
- <h1>Majlis — The Council</h1>
98
- <div class="arabic">مجلس · ${esc(projectName)}</div>
99
- </div>
100
- </div>
101
- </div>
102
- <div class="header-actions">
103
- <span class="live" id="live-dot"></span>
104
- <span id="updated-ago" style="font-size:var(--text-sm);color:var(--text-secondary);">just now</span>
105
- &nbsp;·&nbsp;
106
- <button class="header-btn" id="refresh-btn" onclick="manualRefresh()">↺ Refresh</button>
107
- <button class="header-btn" id="theme-btn" onclick="toggleTheme()" title="Toggle dark/light">☀️</button>
108
- <button class="header-btn" onclick="copyUrl()" title="Copy URL">🔗</button>
109
- <button class="header-btn" onclick="exportSnapshot()" title="Export snapshot">📥</button>
110
- </div>
111
- </header>
112
-
113
- ${state.rawParseError ? `<div id="parse-warning">⚠️ <strong>state.json parse error:</strong> ${esc(state.rawParseError)} — Dashboard showing partial data.</div>` : ''}
114
-
115
- ${state.blockers.length > 0 ? `
116
- <div id="blocker-banner">
117
- <span class="banner-title">🚧 ${state.blockers.length} Blocker${state.blockers.length > 1 ? 's' : ''}</span>
118
- <span class="banner-list">${state.blockers.map(b => esc(typeof b === 'string' ? b : (b.title || ''))).join(' · ')}</span>
119
- <button class="banner-dismiss" onclick="dismissBlockers()">Dismiss</button>
120
- </div>` : ''}
121
-
122
- <div id="view-overview" class="view active">
123
- ${!state.exists ? `
124
- <div class="empty" style="padding:80px;background:var(--bg-card);border-radius:var(--radius-lg);">
125
- <h2 style="color:var(--rihal-gold);margin-bottom:16px;">No .rihal/ directory found</h2>
126
- <p>Run the <code>*kickoff</code> workflow to initialize a project.</p>
127
- <div class="empty-action">npx rcode install</div>
128
- </div>
129
- ` : `
130
- <div class="stats">
131
- <div class="stat">
132
- <div class="label">Current Phase</div>
133
- <div class="value">${esc(currentPhase)}</div>
134
- <div class="sub">${phaseCount} total phases${currentSprint ? ` · Sprint ${esc(currentSprint)}` : ''}</div>
135
- </div>
136
- <div class="stat">
137
- <div class="label">Milestone</div>
138
- <div class="value" style="font-size:16px;padding-top:6px;" id="stat-milestone">${esc(state.milestone || '—')}</div>
139
- <div class="sub">&nbsp;</div>
140
- </div>
141
- <div class="stat">
142
- <div class="label">Decisions (ADRs)</div>
143
- <div class="value">${decisionCount}</div>
144
- <div class="sub">Architecture records</div>
145
- </div>
146
- <div class="stat">
147
- <div class="label">Planning Files</div>
148
- <div class="value">${artifactCount}</div>
149
- <div class="sub">SPRINT, CONTEXT, VERIFY, RESEARCH</div>
150
- </div>
151
- ${state.blockers.length > 0 ? `
152
- <div class="stat" style="border-left-color:var(--accent-red);">
153
- <div class="label" style="color:var(--accent-red);">Blockers</div>
154
- <div class="value" style="color:var(--accent-red);">${state.blockers.length}</div>
155
- <div class="sub">Active blockers</div>
156
- </div>` : ''}
157
- ${state.councilSessions > 0 ? `
158
- <div class="stat">
159
- <div class="label">Council Sessions</div>
160
- <div class="value">${state.councilSessions}</div>
161
- <div class="sub">Recorded sessions</div>
162
- </div>` : ''}
163
- </div>
164
-
165
- <div id="view-overview-dynamic"></div>
166
30
 
167
- <section>
168
- <h2>🎯 Active Context</h2>
169
- <div class="body">
170
- ${state.context
171
- ? `<div class="item-preview" style="max-height:none;">${esc(state.context)}</div>`
172
- : `<div class="empty">No active context.<div class="empty-action">Run context-build workflow</div></div>`}
173
- </div>
174
- </section>
175
- `}
176
- </div>
31
+ <!-- ── Preact app mount ────────────────────────────────────────────────── -->
32
+ <!-- App renders: sidebar, topbar, and all 12 Preact views (sprint 31.4). -->
33
+ <div id="app-root"></div>
177
34
 
178
- <div id="view-roadmap" class="view"></div>
179
- <div id="view-milestones" class="view"></div>
180
- <div id="view-phases" class="view"></div>
181
- <div id="view-sprints" class="view"></div>
182
- <div id="view-tasks" class="view"></div>
183
-
184
- <div id="view-files" class="view">
185
- <div class="view-title">Files</div>
186
- <div id="file-list-inline"></div>
187
- <div id="file-view"></div>
188
- </div>
189
-
190
- <div id="view-agents" class="view">
191
- <div class="view-title">Agents</div>
192
- <div class="filter-bar">
193
- <input class="filter-input" type="text" placeholder="Filter…" oninput="filterItems(this,'agents-list')">
194
- </div>
195
- <div id="agents-list">
196
- <div style="font-size:var(--text-sm);font-weight:600;color:var(--rihal-gold);margin-bottom:var(--space-3);">Team Members</div>
197
- <div class="agents" style="margin-bottom:var(--space-6);">
198
- ${realAgents.map(agentCard).join('')}
199
- </div>
200
- <div style="font-size:var(--text-sm);font-weight:600;color:var(--accent-blue);margin-bottom:var(--space-3);">AI Agents</div>
201
- <div class="agents">
202
- ${aiAgents.map(agentCard).join('')}
203
- </div>
204
- </div>
205
- </div>
206
-
207
- <div id="view-decisions" class="view"></div>
35
+ <!-- ── Toast ──────────────────────────────────────────────── -->
36
+ <div class="toast" id="toast"></div>
208
37
 
209
- <div id="view-memory" class="view">
210
- <div id="view-memory-content"><div class="empty" style="padding:80px;background:var(--bg-card);border-radius:var(--radius-lg);"><h2 style="color:var(--rihal-gold);margin-bottom:16px;">Memory Bank</h2><p>Loading…</p></div></div>
211
- </div>
38
+ <!-- Xterm and orchestrator panels are now rendered by Preact (Sprint 31.4).
39
+ Static panel markup removed XtermPanel.js + OrchPanel.js own the DOM. -->
212
40
 
213
- <footer>
214
- <div class="arabic">رحلة البناء · The Journey of Building</div>
215
- <div>Rihal Code · View-Only Dashboard · <kbd>R</kbd> refresh · <kbd>1-9</kbd> switch views · <kbd>F</kbd> filter</div>
216
- </footer>
217
- </div>
218
- </div>
219
- <div class="toast" id="toast"></div>
220
- <script>
221
- // #303: view agent skill file
222
- function viewAgentSkill(name) {
223
- // Try to find matching file in file tree
224
- var items = document.querySelectorAll('.file-tree-item');
225
- for (var i = 0; i < items.length; i++) {
226
- if ((items[i].dataset.path || '').toLowerCase().includes(name)) {
227
- items[i].click();
228
- return;
229
- }
230
- }
231
- // Fallback
232
- navTo('files');
233
- }
234
- <\/script>
235
41
  ${renderClientJs(state)}
236
42
  </body>
237
43
  </html>`;
@@ -31,6 +31,80 @@ function parseSimpleYaml(text) {
31
31
  return out;
32
32
  }
33
33
 
34
+ /**
35
+ * Derive the phase → sprint → story tree from the .planning/phases/ filesystem,
36
+ * which is the committed source of truth. state.json sprint/story records are
37
+ * often incomplete (planner agents write SPRINT.md files without registering
38
+ * sprint entries), so the dashboard derives counts from disk instead of trusting
39
+ * state.json. When a phase has a directory with *-SPRINT.md files, those win;
40
+ * otherwise the raw state.json sprints array is kept as-is.
41
+ *
42
+ * @param {string} projectDir repo root
43
+ * @param {Array} rawPhases state.raw.phases
44
+ * @returns {Array|null} phases with a populated `sprints` array each
45
+ */
46
+ function buildPhaseTree(projectDir, rawPhases) {
47
+ if (!Array.isArray(rawPhases)) return null;
48
+ const phasesDir = path.join(projectDir, '.planning', 'phases');
49
+ let dirs;
50
+ try {
51
+ dirs = fs.readdirSync(phasesDir, { withFileTypes: true }).filter(d => d.isDirectory());
52
+ } catch { return rawPhases; }
53
+
54
+ return rawPhases.map(p => {
55
+ const intId = String(p.id || p.number || '').split('.')[0];
56
+ if (!intId) return p;
57
+ const dir = dirs.find(d => d.name.startsWith(intId + '-') ||
58
+ d.name.startsWith(intId.padStart(2, '0') + '-'));
59
+ if (!dir) return p;
60
+
61
+ let files;
62
+ try { files = fs.readdirSync(path.join(phasesDir, dir.name)); } catch { return p; }
63
+ const sprintFiles = files.filter(f => /-SPRINT\.md$/i.test(f)).sort();
64
+ if (!sprintFiles.length) return p;
65
+
66
+ const phaseComplete = /complete|done/i.test(p.status || '');
67
+ const sprints = sprintFiles.map(f => {
68
+ const m = f.match(/^(\d+)-(\d+)-SPRINT\.md$/i);
69
+ const num = m ? parseInt(m[2], 10) : 0;
70
+ const sid = m ? `${parseInt(m[1], 10)}.${num}` : f.replace(/-SPRINT\.md$/i, '');
71
+ const text = safeReadText(path.join(phasesDir, dir.name, f)) || '';
72
+
73
+ // Sprint goal: frontmatter `goal:`, else first line of <objective>.
74
+ const fm = parseSimpleYaml((text.match(/^---\n([\s\S]*?)\n---/) || [])[1] || '');
75
+ let goal = fm.goal || '';
76
+ if (!goal) {
77
+ const obj = (text.match(/<objective>\s*([\s\S]*?)<\/objective>/) || [])[1] || '';
78
+ goal = (obj.trim().split('\n').map(s => s.trim()).filter(Boolean)[0] || '').slice(0, 160);
79
+ }
80
+
81
+ // Stories: one per <task> block; title from <title>.
82
+ const stories = [];
83
+ const taskRe = /<task\b([^>]*)>([\s\S]*?)<\/task>/g;
84
+ let tm;
85
+ while ((tm = taskRe.exec(text))) {
86
+ const idM = tm[1].match(/id="([^"]+)"/);
87
+ const titleM = tm[2].match(/<title>([\s\S]*?)<\/title>/);
88
+ stories.push({
89
+ id: idM ? idM[1] : `${sid}-task-${stories.length + 1}`,
90
+ title: titleM ? titleM[1].trim() : `Task ${stories.length + 1}`,
91
+ status: phaseComplete ? 'done' : 'todo',
92
+ });
93
+ }
94
+
95
+ // Status: a *-SUMMARY.md sibling means the sprint shipped.
96
+ const hasSummary = files.includes(f.replace(/-SPRINT\.md$/i, '-SUMMARY.md'));
97
+ const status = hasSummary ? 'complete'
98
+ : (p.status === 'active' || p.status === 'in_progress') ? 'in_progress'
99
+ : 'planned';
100
+
101
+ return { id: sid, number: num, goal: goal || `Sprint ${num}`, status, stories };
102
+ });
103
+
104
+ return { ...p, sprints };
105
+ });
106
+ }
107
+
34
108
  function scanState(rihalDir) {
35
109
  const projectDir = path.dirname(rihalDir);
36
110
  const state = {
@@ -167,6 +241,41 @@ function scanState(rihalDir) {
167
241
  }
168
242
  if (fs.existsSync(planningDir)) walkPlanning(planningDir, '');
169
243
 
244
+ // #12 — surface pending handoff (.rihal/HANDOFF.json) and active context
245
+ // (.rihal/context/active.md) for the dashboard banner + memory-bank summary.
246
+ // Both are no-op when the files don't exist. View-only — no writes.
247
+ const handoffPath = path.join(rihalDir, 'HANDOFF.json');
248
+ if (fs.existsSync(handoffPath)) {
249
+ const ho = safeReadJson(handoffPath);
250
+ if (ho && !ho.__parseError) {
251
+ state.pendingHandoff = {
252
+ path: '.rihal/HANDOFF.json',
253
+ ts: ho.ts || ho.timestamp || null,
254
+ summary: ho.summary || ho.note || ho.what_was_happening || null,
255
+ phase: ho.phase || ho.current_phase || null,
256
+ sprint: ho.sprint || ho.current_sprint || null,
257
+ resume_hint: ho.resume_hint || ho.next || null,
258
+ };
259
+ }
260
+ }
261
+
262
+ const activeCtx = path.join(rihalDir, 'context', 'active.md');
263
+ if (fs.existsSync(activeCtx)) {
264
+ try {
265
+ const stat = fs.statSync(activeCtx);
266
+ const text = fs.readFileSync(activeCtx, 'utf8');
267
+ state.memoryBank = state.memoryBank || {};
268
+ state.memoryBank.active = {
269
+ path: '.rihal/context/active.md',
270
+ bytes: stat.size,
271
+ lines: text.split('\n').length,
272
+ updated: stat.mtime.toISOString(),
273
+ };
274
+ } catch { /* ignore */ }
275
+ }
276
+
277
+ state.phaseTree = buildPhaseTree(projectDir, state.raw && state.raw.phases);
278
+
170
279
  return state;
171
280
  }
172
281