@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.
- package/AGENTS.md +6 -6
- package/CONTRIBUTING.md +2 -0
- package/LICENSE +21 -0
- package/README.md +66 -403
- package/cli/doctor.js +87 -1
- package/cli/install.js +122 -31
- package/cli/lib/schemas.cjs +318 -0
- package/cli/postinstall.js +19 -3
- package/dist/rcode.js +316 -23
- package/package.json +14 -4
- package/rihal/agents/rihal-cross-platform-auditor.md +1 -1
- package/rihal/agents/rihal-dep-auditor.md +1 -1
- package/rihal/agents/rihal-docs-auditor.md +3 -145
- package/rihal/agents/rihal-i18n-auditor.md +1 -1
- package/rihal/agents/rihal-nyquist-auditor.md +4 -156
- package/rihal/agents/rihal-observability-auditor.md +1 -1
- package/rihal/bin/rihal-hooks.cjs +394 -4
- package/rihal/bin/rihal-tools.cjs +891 -24
- package/rihal/commands/create-prd.md +18 -0
- package/rihal/commands/execute-milestone.md +18 -0
- package/rihal/commands/plan-milestone.md +18 -0
- package/rihal/commands/scaffold-milestone.md +18 -0
- package/rihal/commands/scaffold-skill.md +18 -0
- package/rihal/references/REFERENCES_INDEX.md +49 -7
- package/rihal/references/agent-contracts.md +10 -0
- package/rihal/references/design-tokens.md +98 -0
- package/rihal/references/docs-auditor-playbook.md +148 -0
- package/rihal/references/git-preflight.md +117 -0
- package/rihal/references/iterative-retrieval.md +85 -0
- package/rihal/references/nyquist-auditor-playbook.md +157 -0
- package/rihal/references/workstream-flag.md +2 -2
- package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +9 -0
- package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +9 -0
- package/rihal/skills/actions/4-implementation/rihal-ci/SKILL.md +4 -0
- package/rihal/skills/actions/4-implementation/rihal-code-review/steps/step-02-review.md +2 -2
- package/rihal/skills/actions/4-implementation/rihal-harden/SKILL.md +4 -0
- package/rihal/skills/actions/4-implementation/rihal-migrate/SKILL.md +4 -0
- package/rihal/skills/agents/haitham-frontend/SKILL.md +2 -0
- package/rihal/templates/settings-hooks.json +39 -0
- package/rihal/workflows/check-todos.md +4 -0
- package/rihal/workflows/code-review-fix.md +4 -3
- package/rihal/workflows/code-review.md +1 -1
- package/rihal/workflows/debug.md +1 -1
- package/rihal/workflows/dev-story.md +4 -0
- package/rihal/workflows/diff.md +2 -2
- package/rihal/workflows/do.md +16 -8
- package/rihal/workflows/docs-update.md +2 -2
- package/rihal/workflows/enable-hooks.md +6 -1
- package/rihal/workflows/execute-milestone.md +139 -0
- package/rihal/workflows/execute-regression-gates.md +1 -1
- package/rihal/workflows/execute-sprint.md +54 -2
- package/rihal/workflows/execute-verify-phase-goal.md +31 -4
- package/rihal/workflows/execute-waves.md +33 -5
- package/rihal/workflows/execute.md +40 -6
- package/rihal/workflows/help.md +1 -1
- package/rihal/workflows/import.md +1 -1
- package/rihal/workflows/lens-audit.md +39 -23
- package/rihal/workflows/list-workspaces.md +1 -1
- package/rihal/workflows/map-codebase.md +4 -4
- package/rihal/workflows/new-milestone.md +18 -1
- package/rihal/workflows/new-project-research.md +53 -1
- package/rihal/workflows/new-workspace.md +1 -1
- package/rihal/workflows/plan-milestone.md +105 -0
- package/rihal/workflows/plan-research-validation.md +1 -1
- package/rihal/workflows/plan-spawn-planner.md +1 -1
- package/rihal/workflows/plan.md +31 -3
- package/rihal/workflows/plant-seed.md +6 -0
- package/rihal/workflows/quick.md +11 -5
- package/rihal/workflows/research-phase.md +24 -0
- package/rihal/workflows/scaffold-milestone.md +60 -0
- package/rihal/workflows/scaffold-skill.md +137 -0
- package/rihal/workflows/scan.md +1 -1
- package/rihal/workflows/session-report.md +43 -3
- package/rihal/workflows/verify-work.md +3 -3
- package/server/dashboard.js +154 -5
- package/server/lib/html/client/agents-data.js +27 -0
- package/server/lib/html/client/app.js +15 -0
- package/server/lib/html/client/components/App.js +211 -0
- package/server/lib/html/client/components/OrchPanel.js +293 -0
- package/server/lib/html/client/components/Sidebar.js +73 -0
- package/server/lib/html/client/components/Topbar.js +53 -0
- package/server/lib/html/client/components/XtermPanel.js +220 -0
- package/server/lib/html/client/components/shared.js +330 -0
- package/server/lib/html/client/icons-client.js +85 -0
- package/server/lib/html/client/orchestrator.js +279 -0
- package/server/lib/html/client/preact.js +34 -0
- package/server/lib/html/client/store.js +91 -0
- package/server/lib/html/client/util.js +186 -0
- package/server/lib/html/client/views/AgentsView.js +83 -0
- package/server/lib/html/client/views/DecisionsView.js +102 -0
- package/server/lib/html/client/views/FilesView.js +223 -0
- package/server/lib/html/client/views/KanbanView.js +236 -0
- package/server/lib/html/client/views/MemoryView.js +157 -0
- package/server/lib/html/client/views/MilestonesView.js +136 -0
- package/server/lib/html/client/views/OrchestrationView.js +167 -0
- package/server/lib/html/client/views/OverviewView.js +221 -0
- package/server/lib/html/client/views/PhasesView.js +184 -0
- package/server/lib/html/client/views/RoadmapView.js +238 -0
- package/server/lib/html/client/views/SprintsView.js +178 -0
- package/server/lib/html/client/views/TasksView.js +148 -0
- package/server/lib/html/client.js +42 -1064
- package/server/lib/html/css.js +2266 -466
- package/server/lib/html/icons.js +68 -0
- package/server/lib/html/shell.js +16 -210
- package/server/lib/scanner.js +109 -0
- 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 };
|
package/server/lib/html/shell.js
CHANGED
|
@@ -6,49 +6,11 @@ const { renderClientJs } = require('./client');
|
|
|
6
6
|
|
|
7
7
|
function esc(s) { return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); }
|
|
8
8
|
|
|
9
|
-
function renderHtml(state) {
|
|
10
|
-
const projectName
|
|
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
|
-
|
|
18
|
-
|
|
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://
|
|
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
|
-
·
|
|
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"> </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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
179
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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>`;
|
package/server/lib/scanner.js
CHANGED
|
@@ -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
|
|