@hanzlaa/rcode 2.3.1 → 2.3.3
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.
- package/CLAUDE.md +15 -0
- package/README.md +3 -3
- package/cli/postinstall.js +4 -4
- package/package.json +3 -2
- package/rihal/agents/rihal-executor.md +1 -0
- package/rihal/agents/rihal-phase-researcher.md +1 -0
- package/rihal/agents/rihal-planner.md +2 -1
- package/rihal/brain/best-practices/no-theoretical-suggestions.md +56 -0
- package/rihal/commands/add-phase.md +2 -2
- package/rihal/commands/cleanup.md +2 -2
- package/rihal/commands/pr-branch.md +2 -2
- package/rihal/commands/remove-phase.md +2 -2
- package/rihal/commands/research-phase.md +2 -2
- package/rihal/commands/ship.md +15 -3
- package/rihal/commands/validate-phase.md +1 -1
- package/rihal/commands/verify-phase.md +2 -2
- package/rihal/skills/actions/1-analysis/research/rihal-domain-research/SKILL.md +11 -0
- package/rihal/skills/actions/1-analysis/research/rihal-market-research/SKILL.md +11 -0
- package/rihal/skills/actions/1-analysis/research/rihal-technical-research/SKILL.md +13 -0
- package/rihal/skills/actions/1-analysis/rihal-document-project/SKILL.md +11 -0
- package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +2 -0
- package/rihal/skills/actions/1-analysis/rihal-product-brief/SKILL.md +7 -0
- package/rihal/skills/actions/2-plan/rihal-create-epics-and-stories/SKILL.md +12 -0
- package/rihal/skills/actions/2-plan/rihal-create-milestone/SKILL.md +18 -0
- package/rihal/skills/actions/2-plan/rihal-create-prd/SKILL.md +13 -0
- package/rihal/skills/actions/2-plan/rihal-create-story/SKILL.md +12 -0
- package/rihal/skills/actions/2-plan/rihal-create-ux-design/SKILL.md +12 -0
- package/rihal/skills/actions/2-plan/rihal-edit-prd/SKILL.md +11 -0
- package/rihal/skills/actions/2-plan/rihal-frontend-design/SKILL.md +13 -0
- package/rihal/skills/actions/2-plan/rihal-validate-prd/SKILL.md +12 -0
- package/rihal/skills/actions/3-solutioning/rihal-check-implementation-readiness/SKILL.md +12 -0
- package/rihal/skills/actions/3-solutioning/rihal-create-architecture/SKILL.md +14 -0
- package/rihal/skills/actions/3-solutioning/rihal-generate-project-context/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +6 -0
- package/rihal/skills/actions/4-implementation/rihal-code-review/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-correct-course/SKILL.md +13 -0
- package/rihal/skills/actions/4-implementation/rihal-dev-story/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-qa-generate-e2e-tests/SKILL.md +12 -0
- package/rihal/skills/actions/4-implementation/rihal-retrospective/SKILL.md +11 -0
- package/rihal/skills/actions/4-implementation/rihal-scaffold-project/SKILL.md +10 -0
- package/rihal/skills/actions/4-implementation/rihal-sprint-planning/SKILL.md +11 -0
- package/rihal/skills/actions/4-implementation/rihal-sprint-status/SKILL.md +10 -0
- package/rihal/skills/agents/ahmed-hassani-director/SKILL.md +13 -1
- package/rihal/skills/agents/fatima-qa/SKILL.md +14 -1
- package/rihal/skills/agents/haitham-frontend/SKILL.md +15 -1
- package/rihal/skills/agents/hanzla-engineer/SKILL.md +14 -1
- package/rihal/skills/agents/hussain-pm/SKILL.md +14 -1
- package/rihal/skills/agents/hussain-sm/SKILL.md +14 -1
- package/rihal/skills/agents/layla-designer/SKILL.md +15 -1
- package/rihal/skills/agents/majlis-council/SKILL.md +14 -1
- package/rihal/skills/agents/mariam-marketing/SKILL.md +15 -1
- package/rihal/skills/agents/nasser-eng-manager/SKILL.md +14 -1
- package/rihal/skills/agents/noor-writer/SKILL.md +14 -1
- package/rihal/skills/agents/raees-orchestrator/SKILL.md +13 -1
- package/rihal/skills/agents/sadiq-analyst/SKILL.md +15 -1
- package/rihal/skills/agents/waleed-architect/SKILL.md +16 -1
- package/rihal/skills/agents/yousef-backend/SKILL.md +16 -1
- package/rihal/skills/agents/zahra-branding/SKILL.md +15 -1
- package/rihal/skills/agents/zayd-ml/SKILL.md +17 -1
- package/rihal/skills/core/rihal-advanced-elicitation/SKILL.md +12 -0
- package/rihal/skills/core/rihal-brainstorming/SKILL.md +16 -0
- package/rihal/skills/core/rihal-clone-website/SKILL.md +21 -0
- package/rihal/skills/core/rihal-distillator/SKILL.md +8 -0
- package/rihal/skills/core/rihal-editorial-review-prose/SKILL.md +12 -0
- package/rihal/skills/core/rihal-editorial-review-structure/SKILL.md +18 -0
- package/rihal/skills/core/rihal-help/SKILL.md +12 -0
- package/rihal/skills/core/rihal-index-docs/SKILL.md +18 -0
- package/rihal/skills/core/rihal-init/SKILL.md +8 -0
- package/rihal/skills/core/rihal-party-mode/SKILL.md +14 -0
- package/rihal/skills/core/rihal-review-adversarial-general/SKILL.md +12 -0
- package/rihal/skills/core/rihal-review-edge-case-hunter/SKILL.md +18 -0
- package/rihal/skills/core/rihal-shard-doc/SKILL.md +18 -0
- package/rihal/state.json +22 -0
- package/rihal/team.yaml +205 -0
- package/rihal/workflows/autonomous.md +2 -2
- package/rihal/workflows/chain.md +1 -1
- package/rihal/workflows/checkpoint-preview.md +1 -1
- package/rihal/workflows/council.md +1 -1
- package/rihal/workflows/discuss.md +2 -2
- package/rihal/workflows/document-project.md +1 -1
- package/rihal/workflows/enable-hooks.md +1 -1
- package/rihal/workflows/help.md +1 -1
- package/rihal/workflows/prfaq.md +1 -1
- package/rihal/workflows/settings.md +18 -17
- package/rihal/workflows/ship.md +31 -1
- package/rihal/workflows/sprint-planning.md +2 -2
- package/server/dashboard.js +34 -575
- package/server/lib/api.js +123 -0
- package/server/lib/html/client.js +969 -0
- package/server/lib/html/css.js +416 -0
- package/server/lib/html/shell.js +230 -0
- package/server/lib/scanner.js +142 -0
- package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/SKILL.md +0 -148
- package/rihal/skills/core/rihal-advanced-elicitation/rihal-advanced-elicitation/methods.csv +0 -51
- package/rihal/skills/core/rihal-shard-doc/rihal-shard-doc/SKILL.md +0 -122
package/server/dashboard.js
CHANGED
|
@@ -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, '<').replace(/>/g, '>')
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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:
|
|
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',
|
|
91
|
+
process.on('SIGINT', () => server.close(() => process.exit(0)));
|