@hanzlaa/rcode 3.4.32 → 3.5.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 (86) 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/agent.js +3 -2
  6. package/cli/doctor.js +87 -1
  7. package/cli/install.js +122 -31
  8. package/cli/lib/schemas.cjs +318 -0
  9. package/cli/postinstall.js +19 -3
  10. package/dist/rcode.js +318 -24
  11. package/package.json +8 -4
  12. package/rihal/agents/rihal-cross-platform-auditor.md +15 -0
  13. package/rihal/agents/rihal-dep-auditor.md +15 -0
  14. package/rihal/agents/rihal-docs-auditor.md +3 -145
  15. package/rihal/agents/rihal-i18n-auditor.md +16 -0
  16. package/rihal/agents/rihal-nyquist-auditor.md +4 -156
  17. package/rihal/agents/rihal-observability-auditor.md +16 -0
  18. package/rihal/agents/rihal-phase-researcher.md +1 -1
  19. package/rihal/agents/rihal-planner.md +1 -1
  20. package/rihal/bin/rihal-hooks.cjs +394 -4
  21. package/rihal/bin/rihal-tools.cjs +891 -24
  22. package/rihal/commands/create-prd.md +18 -0
  23. package/rihal/commands/execute-milestone.md +18 -0
  24. package/rihal/commands/plan-milestone.md +18 -0
  25. package/rihal/commands/scaffold-milestone.md +18 -0
  26. package/rihal/commands/scaffold-skill.md +18 -0
  27. package/rihal/references/REFERENCES_INDEX.md +49 -7
  28. package/rihal/references/agent-contracts.md +10 -0
  29. package/rihal/references/design-tokens.md +98 -0
  30. package/rihal/references/docs-auditor-playbook.md +148 -0
  31. package/rihal/references/git-preflight.md +117 -0
  32. package/rihal/references/iterative-retrieval.md +85 -0
  33. package/rihal/references/nyquist-auditor-playbook.md +157 -0
  34. package/rihal/references/workstream-flag.md +2 -2
  35. package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +9 -0
  36. package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +9 -0
  37. package/rihal/skills/actions/4-implementation/rihal-ci/SKILL.md +4 -0
  38. package/rihal/skills/actions/4-implementation/rihal-code-review/steps/step-02-review.md +7 -3
  39. package/rihal/skills/actions/4-implementation/rihal-harden/SKILL.md +4 -0
  40. package/rihal/skills/actions/4-implementation/rihal-migrate/SKILL.md +4 -0
  41. package/rihal/skills/agents/haitham-frontend/SKILL.md +2 -0
  42. package/rihal/skills/agents/majlis-council/SKILL.md +1 -1
  43. package/rihal/team.yaml +32 -0
  44. package/rihal/templates/settings-hooks.json +39 -0
  45. package/rihal/workflows/check-todos.md +4 -0
  46. package/rihal/workflows/code-review-fix.md +4 -3
  47. package/rihal/workflows/code-review.md +1 -1
  48. package/rihal/workflows/debug.md +1 -1
  49. package/rihal/workflows/dev-story.md +4 -0
  50. package/rihal/workflows/diff.md +2 -2
  51. package/rihal/workflows/do.md +16 -8
  52. package/rihal/workflows/docs-update.md +2 -2
  53. package/rihal/workflows/enable-hooks.md +6 -1
  54. package/rihal/workflows/execute-milestone.md +139 -0
  55. package/rihal/workflows/execute-regression-gates.md +1 -1
  56. package/rihal/workflows/execute-sprint.md +54 -2
  57. package/rihal/workflows/execute-verify-phase-goal.md +31 -4
  58. package/rihal/workflows/execute-waves.md +33 -5
  59. package/rihal/workflows/execute.md +40 -6
  60. package/rihal/workflows/help.md +1 -1
  61. package/rihal/workflows/import.md +1 -1
  62. package/rihal/workflows/lens-audit.md +39 -23
  63. package/rihal/workflows/list-workspaces.md +1 -1
  64. package/rihal/workflows/map-codebase.md +4 -4
  65. package/rihal/workflows/new-milestone.md +18 -1
  66. package/rihal/workflows/new-project-research.md +53 -1
  67. package/rihal/workflows/new-workspace.md +1 -1
  68. package/rihal/workflows/plan-milestone.md +105 -0
  69. package/rihal/workflows/plan-research-validation.md +1 -1
  70. package/rihal/workflows/plan-spawn-planner.md +1 -1
  71. package/rihal/workflows/plan.md +31 -3
  72. package/rihal/workflows/plant-seed.md +6 -0
  73. package/rihal/workflows/quick.md +11 -5
  74. package/rihal/workflows/research-phase.md +24 -0
  75. package/rihal/workflows/scaffold-milestone.md +60 -0
  76. package/rihal/workflows/scaffold-skill.md +137 -0
  77. package/rihal/workflows/scan.md +1 -1
  78. package/rihal/workflows/session-report.md +43 -3
  79. package/rihal/workflows/verify-work.md +3 -3
  80. package/server/dashboard.js +53 -6
  81. package/server/lib/api.js +7 -0
  82. package/server/lib/html/client.js +725 -13
  83. package/server/lib/html/css.js +2046 -466
  84. package/server/lib/html/shell.js +227 -134
  85. package/server/lib/scanner.js +33 -0
  86. package/server/orchestrator.js +438 -0
@@ -6,7 +6,7 @@ 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) {
9
+ function renderHtml(state, orchToken) {
10
10
  const projectName = state.projectName || 'No project initialized';
11
11
  const currentPhase = state.currentPhase || '—';
12
12
  const currentSprint = state.currentSprint || null;
@@ -15,34 +15,32 @@ function renderHtml(state) {
15
15
  const artifactCount = state.planningFiles.length;
16
16
 
17
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' },
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
36
  ];
37
37
 
38
- // #305: separate real vs AI agents
39
38
  const realAgents = agents.filter(a => a.real);
40
- const aiAgents = agents.filter(a => !a.real);
39
+ const aiAgents = agents.filter(a => !a.real);
41
40
 
42
41
  function agentCard(a) {
43
42
  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();
43
+ const skillName = a.name.split(' ')[0].toLowerCase();
46
44
  return `<div class="agent-card" data-filter-text="${filterText}" onclick="viewAgentSkill('${skillName}')" style="cursor:pointer;">
47
45
  <div class="name">${esc(a.name)}${a.real ? ' <span class="real-badge">real</span>' : ''} <span class="type-badge">${esc(a.type)}</span></div>
48
46
  <div class="arabic">${a.arabic}</div>
@@ -57,37 +55,51 @@ function renderHtml(state) {
57
55
  <meta name="viewport" content="width=device-width, initial-scale=1">
58
56
  <title>Majlis — ${esc(projectName)}</title>
59
57
  <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">
58
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css">
61
59
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"><\/script>
60
+ <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js"><\/script>
61
+ <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js"><\/script>
62
+ <script>window.__ORCH_TOKEN__ = ${JSON.stringify(orchToken || '')};<\/script>
62
63
  ${renderCss()}
63
64
  </head>
64
65
  <body>
66
+
65
67
  <div class="app-shell">
66
- <aside class="sidebar">
68
+
69
+ <!-- ── Sidebar ─────────────────────────────────────────────── -->
70
+ <aside class="sidebar" id="sidebar">
67
71
  <div class="sidebar-project">
68
- <div class="project-label">Project</div>
69
- ${esc(projectName)}
72
+ <div class="project-label">Rihal</div>
73
+ <span>${esc(projectName)}</span>
70
74
  </div>
71
75
  <nav>
72
76
  <div class="nav-section">Overview</div>
73
77
  <button class="nav-link" data-view="overview">🏠 Overview</button>
74
78
  <button class="nav-link" data-view="roadmap">🗺 Roadmap</button>
79
+
75
80
  <div class="nav-section">Planning</div>
76
81
  <button class="nav-link" data-view="milestones">🎯 Milestones</button>
77
82
  <button class="nav-link" data-view="phases">📋 Phases</button>
78
83
  <button class="nav-link" data-view="sprints">⚡ Sprints</button>
79
84
  <button class="nav-link" data-view="tasks">✓ Tasks</button>
85
+ <button class="nav-link" data-view="kanban">🗂 Kanban</button>
86
+
80
87
  <div class="nav-section">Workspace</div>
81
88
  <button class="nav-link" data-view="files">📄 Files</button>
82
89
  <button class="nav-link" data-view="agents">🤝 Agents</button>
83
90
  <button class="nav-link" data-view="decisions">⚖ Decisions</button>
84
- <button class="nav-link" data-view="memory">🧠 Memory Bank</button>
91
+ <button class="nav-link" data-view="memory">🧠 Memory</button>
85
92
  </nav>
86
93
  </aside>
94
+
87
95
  <div id="sidebar-backdrop" onclick="closeSidebar()"></div>
96
+
97
+ <!-- ── Content area ────────────────────────────────────────── -->
88
98
  <div class="content-area" id="main-content">
99
+
100
+ <!-- Topbar -->
89
101
  <header>
90
- <div style="display:flex;align-items:center;gap:var(--space-3);">
102
+ <div style="display:flex;align-items:center;gap:12px;">
91
103
  <button class="hamburger-btn" id="hamburger-btn" onclick="toggleSidebar()" aria-label="Toggle menu">
92
104
  <span></span><span></span><span></span>
93
105
  </button>
@@ -100,138 +112,219 @@ ${renderCss()}
100
112
  </div>
101
113
  </div>
102
114
  <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;
115
+ <span class="live" id="live-dot" title="Live"></span>
116
+ <span id="updated-ago" style="font-size:11px;color:var(--text-muted);">just now</span>
106
117
  <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>
118
+ <button class="header-btn" id="theme-btn" onclick="toggleTheme()" title="Toggle theme">◑</button>
119
+ <button class="header-btn" onclick="copyUrl()" title="Copy URL">⎘ Link</button>
110
120
  </div>
111
121
  </header>
112
122
 
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>
123
+ <!-- Main scroll container -->
124
+ <div class="main-scroll" id="main-scroll">
125
+
126
+ ${state.rawParseError ? `<div id="parse-warning">⚠ <strong>state.json parse error:</strong> ${esc(state.rawParseError)}</div>` : ''}
127
+
128
+ ${state.blockers.length > 0 ? `
129
+ <div id="blocker-banner">
130
+ <span class="banner-title">🚧 ${state.blockers.length} blocker${state.blockers.length > 1 ? 's' : ''}</span>
131
+ <span class="banner-list">${state.blockers.map(b => esc(typeof b === 'string' ? b : (b.title || ''))).join(' · ')}</span>
132
+ <button class="banner-dismiss" onclick="dismissBlockers()">Dismiss</button>
133
+ </div>` : ''}
134
+
135
+ <!-- ── Overview ─────────────────────────────────────── -->
136
+ <div id="view-overview" class="view active">
137
+ ${!state.exists ? `
138
+ <div class="empty" style="padding:80px;background:var(--bg-elev-2);border:1px solid var(--border-subtle);border-radius:var(--radius-4);">
139
+ <h2 style="color:var(--accent-primary);margin-bottom:12px;font-size:20px;letter-spacing:-0.017em;">No .rihal/ directory found</h2>
140
+ <p style="color:var(--text-tertiary);">Run the kickoff workflow to initialize a project.</p>
141
+ <div class="empty-action">npx rcode install</div>
145
142
  </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>
143
+ ` : `
144
+ <div class="stats">
145
+ <div class="stat">
146
+ <div class="label">Current Phase</div>
147
+ <div class="value" style="font-size:22px;">${esc(currentPhase)}</div>
148
+ <div class="sub">${phaseCount} total${currentSprint ? ` · Sprint ${esc(currentSprint)}` : ''}</div>
149
+ </div>
150
+ <div class="stat">
151
+ <div class="label">Milestone</div>
152
+ <div class="value" style="font-size:16px;padding-top:6px;" id="stat-milestone">${esc(state.milestone || '—')}</div>
153
+ <div class="sub">&nbsp;</div>
154
+ </div>
155
+ <div class="stat">
156
+ <div class="label">Decisions</div>
157
+ <div class="value">${decisionCount}</div>
158
+ <div class="sub">Architecture records</div>
159
+ </div>
160
+ <div class="stat">
161
+ <div class="label">Planning Files</div>
162
+ <div class="value">${artifactCount}</div>
163
+ <div class="sub">SPRINT · CONTEXT · VERIFY</div>
164
+ </div>
165
+ ${state.blockers.length > 0 ? `
166
+ <div class="stat" style="border-left-color:var(--red);">
167
+ <div class="label" style="color:var(--red);">Blockers</div>
168
+ <div class="value" style="color:var(--red);">${state.blockers.length}</div>
169
+ <div class="sub">Active</div>
170
+ </div>` : ''}
171
+ ${state.councilSessions > 0 ? `
172
+ <div class="stat">
173
+ <div class="label">Council Sessions</div>
174
+ <div class="value">${state.councilSessions}</div>
175
+ <div class="sub">Recorded</div>
176
+ </div>` : ''}
150
177
  </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
178
 
165
- <div id="view-overview-dynamic"></div>
179
+ <div id="view-overview-dynamic"></div>
166
180
 
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>
181
+ <section>
182
+ <h2>🎯 Active Context</h2>
183
+ <div class="body">
184
+ ${state.context
185
+ ? `<pre class="ctx-pre">${esc(state.context.replace(/^#[^\n]*\n?/, '').trim())}</pre>`
186
+ : `<div class="empty">No active context.<div class="empty-action">Run /rihal-init to populate</div></div>`}
187
+ </div>
188
+ </section>
189
+ `}
190
+ </div>
177
191
 
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>
192
+ <!-- ── Dynamic views (rendered by JS) ─────────────────── -->
193
+ <div id="view-roadmap" class="view"></div>
194
+ <div id="view-milestones" class="view"></div>
195
+ <div id="view-phases" class="view"></div>
196
+ <div id="view-sprints" class="view"></div>
197
+ <div id="view-tasks" class="view"></div>
183
198
 
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>
199
+ <!-- Kanban -->
200
+ <div id="view-kanban" class="view"></div>
189
201
 
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')">
202
+ <!-- Files -->
203
+ <div id="view-files" class="view">
204
+ <div class="view-title">Files</div>
205
+ <div id="file-list-inline"></div>
206
+ <div id="file-view"></div>
194
207
  </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('')}
208
+
209
+ <!-- Agents -->
210
+ <div id="view-agents" class="view">
211
+ <div class="view-title">Agents</div>
212
+ <div class="body">
213
+ <div class="filter-bar">
214
+ <input class="filter-input" type="text" placeholder="Filter agents…" oninput="filterItems(this,'agents-list')">
215
+ </div>
216
+ <div id="agents-list" style="padding:12px;">
217
+ <div style="font-size:11px;font-weight:600;color:var(--amber);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.06em;">Team</div>
218
+ <div class="agents" style="padding:0;margin-bottom:20px;">
219
+ ${realAgents.map(agentCard).join('')}
220
+ </div>
221
+ <div style="font-size:11px;font-weight:600;color:var(--accent-primary);margin-bottom:10px;text-transform:uppercase;letter-spacing:0.06em;">AI Agents</div>
222
+ <div class="agents" style="padding:0;">
223
+ ${aiAgents.map(agentCard).join('')}
224
+ </div>
225
+ </div>
199
226
  </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('')}
227
+ </div>
228
+
229
+ <div id="view-decisions" class="view"></div>
230
+
231
+ <!-- Memory -->
232
+ <div id="view-memory" class="view">
233
+ <div id="view-memory-content">
234
+ <div class="empty" style="background:var(--bg-elev-2);border:1px solid var(--border-subtle);border-radius:var(--radius-4);">
235
+ <h2 style="color:var(--accent-primary);margin-bottom:12px;">Memory Bank</h2>
236
+ <p>Loading…</p>
237
+ </div>
203
238
  </div>
204
239
  </div>
240
+
241
+ </div><!-- /main-scroll -->
242
+ </div><!-- /content-area -->
243
+ </div><!-- /app-shell -->
244
+
245
+ <!-- ── Toast ──────────────────────────────────────────────── -->
246
+ <div class="toast" id="toast"></div>
247
+
248
+ <!-- ── Orchestrator side panel ─────────────────────────────── -->
249
+ <div id="orch-panel">
250
+ <div class="orch-panel-header">
251
+ <div class="orch-panel-title">
252
+ <span id="orch-panel-orch-dot" class="orch-status-dot"></span>
253
+ Agent Sessions
205
254
  </div>
255
+ <button class="orch-panel-close" onclick="closeOrchPanel()" title="Close">✕</button>
256
+ </div>
206
257
 
207
- <div id="view-decisions" class="view"></div>
258
+ <!-- Tab strip -->
259
+ <div class="orch-tabs" id="orch-tabs">
260
+ <div class="orch-term-empty" style="padding:6px 8px;font-size:11px;color:var(--text-muted);">No active sessions</div>
261
+ </div>
208
262
 
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>
263
+ <!-- Terminal body -->
264
+ <div class="orch-terminal">
265
+ <div class="orch-term-body" id="orch-term-body">
266
+ <div class="orch-term-empty">
267
+ <div>Select a session or run a story card</div>
268
+ </div>
269
+ </div>
270
+ <div class="orch-files" id="orch-files" style="display:none;">
271
+ <div class="orch-files-head">File changes</div>
211
272
  </div>
273
+ </div>
212
274
 
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>
275
+ <!-- Footer controls -->
276
+ <div class="orch-panel-footer">
277
+ <button class="orch-footer-btn stop" id="orch-stop-btn" onclick="stopActiveSession()" style="display:none;">■ Stop</button>
278
+ <button class="orch-footer-btn" onclick="clearActiveTerminal()">Clear</button>
279
+ <button class="orch-footer-btn" onclick="openCleanSessions()">Clean sessions…</button>
280
+ <div style="flex:1;"></div>
281
+ <span id="orch-session-status" style="font-size:11px;color:var(--text-muted);"></span>
217
282
  </div>
218
283
  </div>
219
- <div class="toast" id="toast"></div>
284
+
220
285
  <script>
221
- // #303: view agent skill file
222
286
  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
287
  navTo('files');
288
+ // Wait for inline file tree to render (fetched async), then find the agent's skill file
289
+ setTimeout(function() {
290
+ var items = document.querySelectorAll('.inline-file-entry');
291
+ for (var i = 0; i < items.length; i++) {
292
+ if ((items[i].dataset.path || '').toLowerCase().includes(name)) {
293
+ items[i].click();
294
+ return;
295
+ }
296
+ }
297
+ // No exact match — pre-fill the search so the user can see related files
298
+ var search = document.querySelector('#file-list-inline .filter-input');
299
+ if (search) {
300
+ search.value = name;
301
+ search.dispatchEvent(new Event('input'));
302
+ }
303
+ }, 400);
233
304
  }
234
305
  <\/script>
306
+
307
+ <!-- ── xterm Terminal Panel ───────────────────────────────────── -->
308
+ <div id="term-backdrop" class="term-backdrop"></div>
309
+ <div id="term-panel" class="term-panel">
310
+ <div class="term-header">
311
+ <div class="term-header-left">
312
+ <div class="term-status-dot" id="term-status-dot"></div>
313
+ <span class="term-title" id="term-title">Terminal</span>
314
+ </div>
315
+ <div class="term-header-right">
316
+ <button class="term-btn term-stop-btn" id="term-stop-btn" onclick="termStop()">■ Stop</button>
317
+ <button class="term-btn" onclick="closeTermPanel()">✕ Close</button>
318
+ </div>
319
+ </div>
320
+ <div id="term-container"></div>
321
+ <div class="term-input-row">
322
+ <span class="term-prompt">❯</span>
323
+ <input type="text" id="term-input" class="term-input-field" placeholder="Send message to agent… (Enter)">
324
+ <button class="term-send-btn" onclick="termSend()">Send ↑</button>
325
+ </div>
326
+ </div>
327
+
235
328
  ${renderClientJs(state)}
236
329
  </body>
237
330
  </html>`;
@@ -167,6 +167,39 @@ function scanState(rihalDir) {
167
167
  }
168
168
  if (fs.existsSync(planningDir)) walkPlanning(planningDir, '');
169
169
 
170
+ // #12 — surface pending handoff (.rihal/HANDOFF.json) and active context
171
+ // (.rihal/context/active.md) for the dashboard banner + memory-bank summary.
172
+ // Both are no-op when the files don't exist. View-only — no writes.
173
+ const handoffPath = path.join(rihalDir, 'HANDOFF.json');
174
+ if (fs.existsSync(handoffPath)) {
175
+ const ho = safeReadJson(handoffPath);
176
+ if (ho && !ho.__parseError) {
177
+ state.pendingHandoff = {
178
+ path: '.rihal/HANDOFF.json',
179
+ ts: ho.ts || ho.timestamp || null,
180
+ summary: ho.summary || ho.note || ho.what_was_happening || null,
181
+ phase: ho.phase || ho.current_phase || null,
182
+ sprint: ho.sprint || ho.current_sprint || null,
183
+ resume_hint: ho.resume_hint || ho.next || null,
184
+ };
185
+ }
186
+ }
187
+
188
+ const activeCtx = path.join(rihalDir, 'context', 'active.md');
189
+ if (fs.existsSync(activeCtx)) {
190
+ try {
191
+ const stat = fs.statSync(activeCtx);
192
+ const text = fs.readFileSync(activeCtx, 'utf8');
193
+ state.memoryBank = state.memoryBank || {};
194
+ state.memoryBank.active = {
195
+ path: '.rihal/context/active.md',
196
+ bytes: stat.size,
197
+ lines: text.split('\n').length,
198
+ updated: stat.mtime.toISOString(),
199
+ };
200
+ } catch { /* ignore */ }
201
+ }
202
+
170
203
  return state;
171
204
  }
172
205