@hanzlaa/rcode 2.3.1 → 2.3.2

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 (90) hide show
  1. package/CLAUDE.md +15 -0
  2. package/README.md +8 -8
  3. package/cli/postinstall.js +4 -4
  4. package/package.json +2 -2
  5. package/rihal/agents/rihal-executor.md +1 -0
  6. package/rihal/agents/rihal-phase-researcher.md +1 -0
  7. package/rihal/agents/rihal-planner.md +2 -1
  8. package/rihal/brain/best-practices/no-theoretical-suggestions.md +56 -0
  9. package/rihal/commands/add-phase.md +2 -2
  10. package/rihal/commands/cleanup.md +2 -2
  11. package/rihal/commands/pr-branch.md +2 -2
  12. package/rihal/commands/remove-phase.md +2 -2
  13. package/rihal/commands/research-phase.md +2 -2
  14. package/rihal/commands/ship.md +15 -3
  15. package/rihal/commands/validate-phase.md +1 -1
  16. package/rihal/commands/verify-phase.md +2 -2
  17. package/rihal/skills/actions/1-analysis/research/rihal-domain-research/SKILL.md +11 -0
  18. package/rihal/skills/actions/1-analysis/research/rihal-market-research/SKILL.md +11 -0
  19. package/rihal/skills/actions/1-analysis/research/rihal-technical-research/SKILL.md +13 -0
  20. package/rihal/skills/actions/1-analysis/rihal-document-project/SKILL.md +11 -0
  21. package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +2 -0
  22. package/rihal/skills/actions/1-analysis/rihal-product-brief/SKILL.md +7 -0
  23. package/rihal/skills/actions/2-plan/rihal-create-epics-and-stories/SKILL.md +12 -0
  24. package/rihal/skills/actions/2-plan/rihal-create-milestone/SKILL.md +18 -0
  25. package/rihal/skills/actions/2-plan/rihal-create-prd/SKILL.md +13 -0
  26. package/rihal/skills/actions/2-plan/rihal-create-story/SKILL.md +12 -0
  27. package/rihal/skills/actions/2-plan/rihal-create-ux-design/SKILL.md +12 -0
  28. package/rihal/skills/actions/2-plan/rihal-edit-prd/SKILL.md +11 -0
  29. package/rihal/skills/actions/2-plan/rihal-frontend-design/SKILL.md +13 -0
  30. package/rihal/skills/actions/2-plan/rihal-validate-prd/SKILL.md +12 -0
  31. package/rihal/skills/actions/3-solutioning/rihal-check-implementation-readiness/SKILL.md +12 -0
  32. package/rihal/skills/actions/3-solutioning/rihal-create-architecture/SKILL.md +14 -0
  33. package/rihal/skills/actions/3-solutioning/rihal-generate-project-context/SKILL.md +12 -0
  34. package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +6 -0
  35. package/rihal/skills/actions/4-implementation/rihal-code-review/SKILL.md +12 -0
  36. package/rihal/skills/actions/4-implementation/rihal-correct-course/SKILL.md +13 -0
  37. package/rihal/skills/actions/4-implementation/rihal-dev-story/SKILL.md +12 -0
  38. package/rihal/skills/actions/4-implementation/rihal-qa-generate-e2e-tests/SKILL.md +12 -0
  39. package/rihal/skills/actions/4-implementation/rihal-retrospective/SKILL.md +11 -0
  40. package/rihal/skills/actions/4-implementation/rihal-scaffold-project/SKILL.md +10 -0
  41. package/rihal/skills/actions/4-implementation/rihal-sprint-planning/SKILL.md +11 -0
  42. package/rihal/skills/actions/4-implementation/rihal-sprint-status/SKILL.md +10 -0
  43. package/rihal/skills/agents/ahmed-hassani-director/SKILL.md +13 -1
  44. package/rihal/skills/agents/fatima-qa/SKILL.md +14 -1
  45. package/rihal/skills/agents/haitham-frontend/SKILL.md +15 -1
  46. package/rihal/skills/agents/hanzla-engineer/SKILL.md +14 -1
  47. package/rihal/skills/agents/hussain-pm/SKILL.md +14 -1
  48. package/rihal/skills/agents/hussain-sm/SKILL.md +14 -1
  49. package/rihal/skills/agents/layla-designer/SKILL.md +15 -1
  50. package/rihal/skills/agents/majlis-council/SKILL.md +14 -1
  51. package/rihal/skills/agents/mariam-marketing/SKILL.md +15 -1
  52. package/rihal/skills/agents/nasser-eng-manager/SKILL.md +14 -1
  53. package/rihal/skills/agents/noor-writer/SKILL.md +14 -1
  54. package/rihal/skills/agents/raees-orchestrator/SKILL.md +13 -1
  55. package/rihal/skills/agents/sadiq-analyst/SKILL.md +15 -1
  56. package/rihal/skills/agents/waleed-architect/SKILL.md +16 -1
  57. package/rihal/skills/agents/yousef-backend/SKILL.md +16 -1
  58. package/rihal/skills/agents/zahra-branding/SKILL.md +15 -1
  59. package/rihal/skills/agents/zayd-ml/SKILL.md +17 -1
  60. package/rihal/skills/core/rihal-advanced-elicitation/SKILL.md +12 -0
  61. package/rihal/skills/core/rihal-brainstorming/SKILL.md +16 -0
  62. package/rihal/skills/core/rihal-clone-website/SKILL.md +21 -0
  63. package/rihal/skills/core/rihal-distillator/SKILL.md +8 -0
  64. package/rihal/skills/core/rihal-editorial-review-prose/SKILL.md +12 -0
  65. package/rihal/skills/core/rihal-editorial-review-structure/SKILL.md +18 -0
  66. package/rihal/skills/core/rihal-help/SKILL.md +12 -0
  67. package/rihal/skills/core/rihal-index-docs/SKILL.md +18 -0
  68. package/rihal/skills/core/rihal-init/SKILL.md +8 -0
  69. package/rihal/skills/core/rihal-party-mode/SKILL.md +14 -0
  70. package/rihal/skills/core/rihal-review-adversarial-general/SKILL.md +12 -0
  71. package/rihal/skills/core/rihal-review-edge-case-hunter/SKILL.md +18 -0
  72. package/rihal/skills/core/rihal-shard-doc/SKILL.md +18 -0
  73. package/rihal/team.yaml +205 -0
  74. package/rihal/workflows/autonomous.md +2 -2
  75. package/rihal/workflows/checkpoint-preview.md +1 -1
  76. package/rihal/workflows/document-project.md +1 -1
  77. package/rihal/workflows/help.md +1 -1
  78. package/rihal/workflows/prfaq.md +1 -1
  79. package/rihal/workflows/settings.md +18 -17
  80. package/rihal/workflows/ship.md +31 -1
  81. package/rihal/workflows/sprint-planning.md +2 -2
  82. package/server/dashboard.js +34 -575
  83. package/server/lib/api.js +123 -0
  84. package/server/lib/html/client.js +969 -0
  85. package/server/lib/html/css.js +416 -0
  86. package/server/lib/html/shell.js +230 -0
  87. package/server/lib/scanner.js +142 -0
  88. package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/SKILL.md +0 -148
  89. package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/methods.csv +0 -51
  90. package/rihal/skills/core/rihal-shard-doc/rihal-shard-doc/SKILL.md +0 -122
@@ -8,584 +8,29 @@
8
8
  *
9
9
  * VIEW-ONLY by design. No CRUD. No database. Source of truth is files.
10
10
  *
11
+ * Architecture:
12
+ * server/dashboard.js - HTTP server + routing (this file)
13
+ * server/lib/scanner.js - State scanning from .rihal/
14
+ * server/lib/api.js - API route handlers
15
+ * server/lib/html/shell.js - HTML page composition
16
+ * server/lib/html/css.js - All CSS styles
17
+ * server/lib/html/client.js - Client-side JS (routing, rendering, etc.)
18
+ *
11
19
  * Run: node server/dashboard.js
12
20
  * Stop: kill $(lsof -t -i:7717)
13
21
  */
14
22
 
15
23
  const http = require('http');
16
- const fs = require('fs');
17
24
  const path = require('path');
18
25
 
26
+ const { scanState } = require('./lib/scanner');
27
+ const { handleApiState, handleApiFiles, handleApiFile, handleApiHierarchy } = require('./lib/api');
28
+ const { renderHtml } = require('./lib/html/shell');
29
+
19
30
  // ---------- Configuration ----------
20
- const PORT = 7717;
31
+ const PORT = parseInt(process.env.PORT || '7717', 10);
21
32
  const RIHAL_DIR = process.env.RIHAL_DIR || path.join(process.cwd(), '.rihal');
22
-
23
- // ---------- State scanner ----------
24
- // All reads go through safe wrappers that return null on any failure —
25
- // the dashboard is view-only and must never crash on malformed project
26
- // state. Since rihal-code v0.2.0 ("BMAD-style pivot"), state files are
27
- // written by Claude directly via the Write tool, not by the CLI, so we
28
- // can no longer rely on CLI-level schema validation. The wrappers log a
29
- // single-line warning when parsing fails so broken files are visible.
30
- function safeReadJson(filepath) {
31
- let raw;
32
- try {
33
- raw = fs.readFileSync(filepath, 'utf8');
34
- } catch {
35
- return null;
36
- }
37
- try {
38
- return JSON.parse(raw);
39
- } catch (err) {
40
- console.warn(`[dashboard] malformed JSON at ${filepath}: ${err.message}`);
41
- return null;
42
- }
43
- }
44
-
45
- function safeReadText(filepath) {
46
- try {
47
- return fs.readFileSync(filepath, 'utf8');
48
- } catch {
49
- return null;
50
- }
51
- }
52
-
53
- function listDir(dir) {
54
- try {
55
- return fs.readdirSync(dir, { withFileTypes: true });
56
- } catch {
57
- return [];
58
- }
59
- }
60
-
61
- function scanState() {
62
- const state = {
63
- exists: fs.existsSync(RIHAL_DIR),
64
- project: null,
65
- phases: [],
66
- decisions: [],
67
- progress: [],
68
- artifacts: [],
69
- context: null,
70
- lastScanned: new Date().toISOString(),
71
- };
72
-
73
- if (!state.exists) return state;
74
-
75
- // Project state
76
- state.project = safeReadJson(path.join(RIHAL_DIR, 'state.json'));
77
-
78
- // Active context
79
- state.context = safeReadText(path.join(RIHAL_DIR, 'context', 'active.md'));
80
-
81
- // Phases
82
- const phasesDir = path.join(RIHAL_DIR, 'phases');
83
- for (const entry of listDir(phasesDir)) {
84
- if (!entry.isDirectory()) continue;
85
- const phaseDir = path.join(phasesDir, entry.name);
86
- const phase = {
87
- id: entry.name,
88
- brief: safeReadText(path.join(phaseDir, 'brief.md')),
89
- sprints: safeReadText(path.join(phaseDir, 'sprints.md')),
90
- stories: listDir(path.join(phaseDir, 'stories')).filter(e => e.isFile()).map(e => e.name),
91
- tasks: listDir(path.join(phaseDir, 'tasks')).filter(e => e.isFile()).map(e => e.name),
92
- };
93
- state.phases.push(phase);
94
- }
95
-
96
- // Decisions (ADRs)
97
- for (const entry of listDir(path.join(RIHAL_DIR, 'decisions'))) {
98
- if (entry.isFile() && entry.name.endsWith('.md')) {
99
- state.decisions.push({
100
- name: entry.name,
101
- content: safeReadText(path.join(RIHAL_DIR, 'decisions', entry.name)),
102
- });
103
- }
104
- }
105
-
106
- // Progress (latest 10)
107
- const progressFiles = listDir(path.join(RIHAL_DIR, 'progress'))
108
- .filter(e => e.isFile() && e.name.endsWith('.md'))
109
- .sort((a, b) => b.name.localeCompare(a.name))
110
- .slice(0, 10);
111
- for (const entry of progressFiles) {
112
- state.progress.push({
113
- name: entry.name,
114
- content: safeReadText(path.join(RIHAL_DIR, 'progress', entry.name)),
115
- });
116
- }
117
-
118
- // Artifacts
119
- function walkArtifacts(dir, prefix = '') {
120
- for (const entry of listDir(dir)) {
121
- const full = path.join(dir, entry.name);
122
- const rel = path.join(prefix, entry.name);
123
- if (entry.isDirectory()) {
124
- walkArtifacts(full, rel);
125
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
126
- state.artifacts.push({
127
- path: rel,
128
- content: safeReadText(full),
129
- });
130
- }
131
- }
132
- }
133
- walkArtifacts(path.join(RIHAL_DIR, 'artifacts'));
134
-
135
- return state;
136
- }
137
-
138
- // ---------- Simple markdown → HTML ----------
139
- function mdToHtml(md) {
140
- if (!md) return '';
141
- return md
142
- .replace(/</g, '&lt;').replace(/>/g, '&gt;')
143
- .replace(/^### (.*$)/gm, '<h3>$1</h3>')
144
- .replace(/^## (.*$)/gm, '<h2>$1</h2>')
145
- .replace(/^# (.*$)/gm, '<h1>$1</h1>')
146
- .replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
147
- .replace(/\*(.+?)\*/g, '<em>$1</em>')
148
- .replace(/`(.+?)`/g, '<code>$1</code>')
149
- .replace(/^- (.+)$/gm, '<li>$1</li>')
150
- .replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>')
151
- .replace(/\n\n/g, '</p><p>')
152
- .replace(/^(?!<)/gm, '<p>')
153
- .replace(/<p><\/p>/g, '');
154
- }
155
-
156
- // ---------- HTML Renderer ----------
157
- function renderHtml(state) {
158
- const projectName = state.project?.project_name || 'No project initialized';
159
- const currentPhase = state.project?.current_phase || '—';
160
- const activeAgents = state.project?.active_agents || [];
161
- const phaseCount = state.phases.length;
162
- const decisionCount = state.decisions.length;
163
- const artifactCount = state.artifacts.length;
164
-
165
- return `<!DOCTYPE html>
166
- <html lang="en" dir="ltr">
167
- <head>
168
- <meta charset="UTF-8">
169
- <meta name="viewport" content="width=device-width, initial-scale=1">
170
- <title>Majlis — ${projectName}</title>
171
- <style>
172
- :root {
173
- --rihal-blue: #1e3a8a;
174
- --rihal-gold: #f59e0b;
175
- --bg: #0a0e1a;
176
- --card: #131828;
177
- --border: #1f2937;
178
- --text: #e5e7eb;
179
- --muted: #9ca3af;
180
- --accent: #3b82f6;
181
- }
182
- * { box-sizing: border-box; margin: 0; padding: 0; }
183
- body {
184
- font-family: -apple-system, "Segoe UI", "Inter", sans-serif;
185
- background: var(--bg);
186
- color: var(--text);
187
- line-height: 1.6;
188
- }
189
- header {
190
- background: linear-gradient(135deg, var(--rihal-blue), #312e81);
191
- border-bottom: 3px solid var(--rihal-gold);
192
- padding: 24px 32px;
193
- display: flex;
194
- justify-content: space-between;
195
- align-items: center;
196
- flex-wrap: wrap;
197
- gap: 16px;
198
- }
199
- .brand {
200
- display: flex;
201
- align-items: center;
202
- gap: 16px;
203
- }
204
- .brand .icon {
205
- font-size: 40px;
206
- }
207
- .brand h1 {
208
- font-size: 24px;
209
- font-weight: 700;
210
- }
211
- .brand .arabic {
212
- color: var(--rihal-gold);
213
- font-size: 18px;
214
- margin-top: 2px;
215
- }
216
- .header-meta {
217
- text-align: right;
218
- color: #cbd5e1;
219
- font-size: 13px;
220
- }
221
- .header-meta .live {
222
- display: inline-block;
223
- width: 8px;
224
- height: 8px;
225
- background: #10b981;
226
- border-radius: 50%;
227
- margin-right: 6px;
228
- animation: pulse 2s infinite;
229
- }
230
- @keyframes pulse {
231
- 0%, 100% { opacity: 1; }
232
- 50% { opacity: 0.4; }
233
- }
234
- main {
235
- max-width: 1400px;
236
- margin: 0 auto;
237
- padding: 32px;
238
- }
239
- .stats {
240
- display: grid;
241
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
242
- gap: 16px;
243
- margin-bottom: 32px;
244
- }
245
- .stat {
246
- background: var(--card);
247
- border: 1px solid var(--border);
248
- border-left: 4px solid var(--rihal-gold);
249
- padding: 20px 24px;
250
- border-radius: 8px;
251
- }
252
- .stat .label {
253
- color: var(--muted);
254
- font-size: 12px;
255
- text-transform: uppercase;
256
- letter-spacing: 0.05em;
257
- margin-bottom: 8px;
258
- }
259
- .stat .value {
260
- font-size: 28px;
261
- font-weight: 700;
262
- color: var(--text);
263
- }
264
- .stat .sub {
265
- color: var(--muted);
266
- font-size: 13px;
267
- margin-top: 4px;
268
- }
269
- section {
270
- background: var(--card);
271
- border: 1px solid var(--border);
272
- border-radius: 12px;
273
- margin-bottom: 24px;
274
- overflow: hidden;
275
- }
276
- section > h2 {
277
- background: rgba(245, 158, 11, 0.08);
278
- padding: 16px 24px;
279
- font-size: 14px;
280
- text-transform: uppercase;
281
- letter-spacing: 0.1em;
282
- color: var(--rihal-gold);
283
- border-bottom: 1px solid var(--border);
284
- display: flex;
285
- align-items: center;
286
- gap: 10px;
287
- }
288
- section .body {
289
- padding: 24px;
290
- }
291
- .agents {
292
- display: grid;
293
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
294
- gap: 12px;
295
- }
296
- .agent-card {
297
- background: rgba(59, 130, 246, 0.05);
298
- border: 1px solid var(--border);
299
- border-radius: 8px;
300
- padding: 16px;
301
- transition: transform 0.2s;
302
- }
303
- .agent-card:hover {
304
- transform: translateY(-2px);
305
- border-color: var(--rihal-gold);
306
- }
307
- .agent-card .name {
308
- font-weight: 600;
309
- font-size: 15px;
310
- margin-bottom: 4px;
311
- }
312
- .agent-card .arabic {
313
- color: var(--rihal-gold);
314
- font-size: 14px;
315
- }
316
- .agent-card .role {
317
- color: var(--muted);
318
- font-size: 12px;
319
- margin-top: 6px;
320
- }
321
- .agent-card.active {
322
- background: rgba(16, 185, 129, 0.1);
323
- border-color: #10b981;
324
- }
325
- .real-badge {
326
- display: inline-block;
327
- background: rgba(16, 185, 129, 0.2);
328
- color: #10b981;
329
- padding: 1px 6px;
330
- border-radius: 8px;
331
- font-size: 9px;
332
- text-transform: uppercase;
333
- letter-spacing: 0.05em;
334
- vertical-align: middle;
335
- margin-left: 4px;
336
- }
337
- .phase-list, .decision-list, .progress-list {
338
- display: flex;
339
- flex-direction: column;
340
- gap: 12px;
341
- }
342
- .item {
343
- background: rgba(255, 255, 255, 0.02);
344
- border: 1px solid var(--border);
345
- border-left: 3px solid var(--accent);
346
- padding: 16px 20px;
347
- border-radius: 6px;
348
- }
349
- .item .item-title {
350
- font-weight: 600;
351
- margin-bottom: 6px;
352
- color: var(--text);
353
- }
354
- .item .item-meta {
355
- color: var(--muted);
356
- font-size: 12px;
357
- margin-bottom: 8px;
358
- }
359
- .item .item-preview {
360
- color: #cbd5e1;
361
- font-size: 13px;
362
- max-height: 120px;
363
- overflow: hidden;
364
- position: relative;
365
- }
366
- .item .item-preview::after {
367
- content: '';
368
- position: absolute;
369
- bottom: 0; left: 0; right: 0;
370
- height: 40px;
371
- background: linear-gradient(transparent, var(--card));
372
- }
373
- .empty {
374
- color: var(--muted);
375
- text-align: center;
376
- padding: 32px;
377
- font-style: italic;
378
- }
379
- .tag {
380
- display: inline-block;
381
- background: rgba(245, 158, 11, 0.15);
382
- color: var(--rihal-gold);
383
- padding: 2px 10px;
384
- border-radius: 12px;
385
- font-size: 11px;
386
- text-transform: uppercase;
387
- letter-spacing: 0.05em;
388
- margin-right: 6px;
389
- }
390
- footer {
391
- text-align: center;
392
- padding: 32px;
393
- color: var(--muted);
394
- font-size: 13px;
395
- border-top: 1px solid var(--border);
396
- margin-top: 48px;
397
- }
398
- footer .arabic {
399
- color: var(--rihal-gold);
400
- font-size: 16px;
401
- margin-bottom: 8px;
402
- }
403
- code {
404
- background: rgba(255, 255, 255, 0.05);
405
- padding: 2px 6px;
406
- border-radius: 4px;
407
- font-size: 13px;
408
- font-family: "SF Mono", Monaco, Consolas, monospace;
409
- }
410
- h1, h2, h3 { line-height: 1.3; }
411
- p { margin-bottom: 10px; }
412
- ul { margin-left: 20px; margin-bottom: 10px; }
413
- </style>
414
- </head>
415
- <body>
416
-
417
- <header>
418
- <div class="brand">
419
- <div class="icon">🕌</div>
420
- <div>
421
- <h1>Majlis — The Council</h1>
422
- <div class="arabic">مجلس · ${projectName}</div>
423
- </div>
424
- </div>
425
- <div class="header-meta">
426
- <div><span class="live"></span>Live · Auto-refresh 5s</div>
427
- <div>Last scanned: ${new Date(state.lastScanned).toLocaleTimeString()}</div>
428
- <div>Source: <code>${RIHAL_DIR.replace(process.env.HOME || '', '~')}</code></div>
429
- </div>
430
- </header>
431
-
432
- <main>
433
-
434
- ${!state.exists ? `
435
- <div class="empty" style="padding:80px;background:var(--card);border-radius:12px;">
436
- <h2 style="color:var(--rihal-gold);margin-bottom:16px;">No .rihal/ directory found</h2>
437
- <p>Run the <code>*kickoff</code> workflow to initialize a project.</p>
438
- </div>
439
- ` : `
440
-
441
- <div class="stats">
442
- <div class="stat">
443
- <div class="label">Current Phase</div>
444
- <div class="value">${currentPhase}</div>
445
- <div class="sub">${phaseCount} total phases</div>
446
- </div>
447
- <div class="stat">
448
- <div class="label">Active Agents</div>
449
- <div class="value">${activeAgents.length}</div>
450
- <div class="sub">${activeAgents.join(', ') || '—'}</div>
451
- </div>
452
- <div class="stat">
453
- <div class="label">Decisions (ADRs)</div>
454
- <div class="value">${decisionCount}</div>
455
- <div class="sub">Architecture records</div>
456
- </div>
457
- <div class="stat">
458
- <div class="label">Artifacts</div>
459
- <div class="value">${artifactCount}</div>
460
- <div class="sub">Plans, reviews, research</div>
461
- </div>
462
- </div>
463
-
464
- <section>
465
- <h2>🎯 Active Context</h2>
466
- <div class="body">
467
- ${state.context ? `<div class="item-preview" style="max-height:none;">${mdToHtml(state.context)}</div>` : `<div class="empty">No active context. Run context-build workflow.</div>`}
468
- </div>
469
- </section>
470
-
471
- <section>
472
- <h2>👥 Team Roster</h2>
473
- <div class="body">
474
- <div class="agents">
475
- ${[
476
- { name: 'Sadiq Damani', arabic: 'صادق', role: 'Director of Strategy', real: true },
477
- { name: 'Waleed Al Harthi', arabic: 'وليد', role: 'CTO', real: true },
478
- { name: 'Ahmed Al Hassani', arabic: 'أحمد الحسني', role: 'Technology & Development Director', real: true },
479
- { name: 'Nasser', arabic: 'ناصر', role: 'Engineering Manager', real: true },
480
- { name: 'Hussain', arabic: 'حسين', role: 'PM + Scrum Master' },
481
- { name: 'Layla', arabic: 'ليلى', role: 'Lead UX Designer' },
482
- { name: 'Zahra', arabic: 'زهرة', role: 'Branding & Creative Director' },
483
- { name: 'Omar', arabic: 'عمر', role: 'Full-Stack Engineer' },
484
- { name: 'Haitham Al Khamiyasi', arabic: 'هيثم', role: 'Senior Frontend', real: true },
485
- { name: 'Yousef', arabic: 'يوسف', role: 'Senior Backend' },
486
- { name: 'Zayd', arabic: 'زيد', role: 'ML Engineer' },
487
- { name: 'Fatima', arabic: 'فاطمة', role: 'QA Lead' },
488
- { name: 'Khalid', arabic: 'خالد', role: 'DevOps' },
489
- { name: 'Noor', arabic: 'نور', role: 'Scribe' },
490
- { name: 'Mariam', arabic: 'مريم', role: 'Marketing Lead' },
491
- { name: 'Raees', arabic: 'رئيس', role: 'Orchestration Director' },
492
- { name: 'Majlis', arabic: 'مجلس', role: 'Consulting Council' },
493
- { name: 'Diwan', arabic: 'ديوان', role: 'Dashboard Registry' },
494
- ].map(a => `
495
- <div class="agent-card ${activeAgents.includes(a.name.split(' ')[0].toLowerCase()) ? 'active' : ''}">
496
- <div class="name">${a.name}${a.real ? ' <span class="real-badge">real</span>' : ''}</div>
497
- <div class="arabic">${a.arabic}</div>
498
- <div class="role">${a.role}</div>
499
- </div>
500
- `).join('')}
501
- </div>
502
- </div>
503
- </section>
504
-
505
- <section>
506
- <h2>📂 Phases</h2>
507
- <div class="body">
508
- ${state.phases.length === 0 ? '<div class="empty">No phases yet. Run *kickoff.</div>' : `
509
- <div class="phase-list">
510
- ${state.phases.map(p => `
511
- <div class="item">
512
- <div class="item-title">${p.id}</div>
513
- <div class="item-meta">
514
- <span class="tag">${p.stories.length} stories</span>
515
- <span class="tag">${p.tasks.length} tasks</span>
516
- </div>
517
- ${p.brief ? `<div class="item-preview">${mdToHtml(p.brief.slice(0, 500))}</div>` : ''}
518
- </div>
519
- `).join('')}
520
- </div>
521
- `}
522
- </div>
523
- </section>
524
-
525
- <section>
526
- <h2>⚖️ Decisions (ADRs)</h2>
527
- <div class="body">
528
- ${state.decisions.length === 0 ? '<div class="empty">No decisions recorded yet.</div>' : `
529
- <div class="decision-list">
530
- ${state.decisions.map(d => `
531
- <div class="item">
532
- <div class="item-title">${d.name}</div>
533
- <div class="item-preview">${mdToHtml((d.content || '').slice(0, 400))}</div>
534
- </div>
535
- `).join('')}
536
- </div>
537
- `}
538
- </div>
539
- </section>
540
-
541
- <section>
542
- <h2>📈 Progress Log</h2>
543
- <div class="body">
544
- ${state.progress.length === 0 ? '<div class="empty">No progress entries yet.</div>' : `
545
- <div class="progress-list">
546
- ${state.progress.map(p => `
547
- <div class="item">
548
- <div class="item-title">${p.name}</div>
549
- <div class="item-preview">${mdToHtml((p.content || '').slice(0, 400))}</div>
550
- </div>
551
- `).join('')}
552
- </div>
553
- `}
554
- </div>
555
- </section>
556
-
557
- <section>
558
- <h2>📎 Artifacts</h2>
559
- <div class="body">
560
- ${state.artifacts.length === 0 ? '<div class="empty">No artifacts yet.</div>' : `
561
- <div class="phase-list">
562
- ${state.artifacts.map(a => `
563
- <div class="item">
564
- <div class="item-title">${a.path}</div>
565
- <div class="item-preview">${mdToHtml((a.content || '').slice(0, 300))}</div>
566
- </div>
567
- `).join('')}
568
- </div>
569
- `}
570
- </div>
571
- </section>
572
- `}
573
-
574
- </main>
575
-
576
- <footer>
577
- <div class="arabic">رحلة البناء · The Journey of Building</div>
578
- <div>Rihal Code · View-Only Dashboard · Read from files, no database.</div>
579
- </footer>
580
-
581
- <script>
582
- // Auto-refresh every 5 seconds
583
- setTimeout(() => location.reload(), 5000);
584
- </script>
585
-
586
- </body>
587
- </html>`;
588
- }
33
+ const PROJECT_ROOT = path.dirname(RIHAL_DIR);
589
34
 
590
35
  // ---------- HTTP Server ----------
591
36
  const server = http.createServer((req, res) => {
@@ -598,14 +43,27 @@ const server = http.createServer((req, res) => {
598
43
  }
599
44
 
600
45
  if (url === '/api/state') {
601
- const state = scanState();
602
- res.writeHead(200, { 'Content-Type': 'application/json' });
603
- res.end(JSON.stringify(state, null, 2));
46
+ handleApiState(req, res, RIHAL_DIR);
47
+ return;
48
+ }
49
+
50
+ if (url === '/api/files') {
51
+ handleApiFiles(req, res, PROJECT_ROOT);
52
+ return;
53
+ }
54
+
55
+ if (url.startsWith('/api/file')) {
56
+ handleApiFile(req, res, PROJECT_ROOT);
57
+ return;
58
+ }
59
+
60
+ if (url === '/api/hierarchy') {
61
+ handleApiHierarchy(req, res, RIHAL_DIR);
604
62
  return;
605
63
  }
606
64
 
607
65
  if (url === '/' || url === '/index.html') {
608
- const state = scanState();
66
+ const state = scanState(RIHAL_DIR);
609
67
  const html = renderHtml(state);
610
68
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
611
69
  res.end(html);
@@ -622,11 +80,12 @@ server.listen(PORT, () => {
622
80
  console.log(` Mode: view-only`);
623
81
  console.log(` URL: http://localhost:${PORT}`);
624
82
  console.log(` Scanning: ${RIHAL_DIR}`);
625
- console.log(` Refresh: 5s (client-side)`);
83
+ console.log(` Refresh: 30s soft poll`);
84
+ console.log(` Keys: R=refresh 1-9=views F=filter`);
626
85
  console.log(` Stop: kill $(lsof -t -i:${PORT})`);
627
86
  console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
628
87
  });
629
88
 
630
89
  // Graceful shutdown
631
90
  process.on('SIGTERM', () => server.close(() => process.exit(0)));
632
- process.on('SIGINT', () => server.close(() => process.exit(0)));
91
+ process.on('SIGINT', () => server.close(() => process.exit(0)));