@hanzlaa/rcode 4.5.0 → 4.7.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 (38) hide show
  1. package/README.md +47 -14
  2. package/package.json +1 -1
  3. package/rcode/agents/rcode-mariam.md +6 -0
  4. package/rcode/agents/rcode-sadiq.md +6 -0
  5. package/rcode/agents/rcode-waleed.md +6 -0
  6. package/rcode/bin/rcode-hooks.cjs +41 -5
  7. package/rcode/skills/agents/mariam-marketing/SKILL.md +1 -0
  8. package/rcode/skills/agents/sadiq-analyst/SKILL.md +1 -0
  9. package/rcode/skills/agents/waleed-architect/SKILL.md +1 -0
  10. package/rcode/workflows/council.md +29 -1
  11. package/server/dashboard.js +6 -3
  12. package/server/lib/html/client/components/App.js +16 -2
  13. package/server/lib/html/client/components/OrchPanel.js +2 -2
  14. package/server/lib/html/client/components/PhaseGraph.js +20 -12
  15. package/server/lib/html/client/components/Sidebar.js +1 -0
  16. package/server/lib/html/client/components/Topbar.js +3 -3
  17. package/server/lib/html/client/components/XtermPanel.js +98 -23
  18. package/server/lib/html/client/components/dashboard/Blockers.js +3 -3
  19. package/server/lib/html/client/components/dashboard/CompletedTasks.js +4 -2
  20. package/server/lib/html/client/components/dashboard/InProgress.js +4 -3
  21. package/server/lib/html/client/components/dashboard/ProgressTimeline.js +11 -6
  22. package/server/lib/html/client/components/dashboard/RecentDecisions.js +4 -3
  23. package/server/lib/html/client/components/shared.js +111 -2
  24. package/server/lib/html/client/orchestrator.js +28 -15
  25. package/server/lib/html/client/store.js +47 -0
  26. package/server/lib/html/client/util.js +1 -22
  27. package/server/lib/html/client/views/BacklogView.js +44 -0
  28. package/server/lib/html/client/views/DecisionsView.js +4 -3
  29. package/server/lib/html/client/views/FilesView.js +47 -34
  30. package/server/lib/html/client/views/KanbanView.js +8 -0
  31. package/server/lib/html/client/views/OrchestrationView.js +247 -169
  32. package/server/lib/html/client/views/PhasesView.js +8 -3
  33. package/server/lib/html/client/views/SprintsView.js +9 -0
  34. package/server/lib/html/client.js +1 -0
  35. package/server/lib/html/css.js +692 -246
  36. package/server/lib/html/shell.js +9 -3
  37. package/server/lib/scanner.js +21 -6
  38. package/server/orchestrator.js +3 -4
@@ -6,8 +6,14 @@ 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, orchToken) {
9
+ function renderHtml(state, orchToken, orchPort) {
10
10
  const projectName = state.projectName || 'No project initialized';
11
+ // #969 — the client used to hardcode port 7718 for the orchestrator API.
12
+ // A dashboard started with ORCH_PORT set (e.g. to test in isolation from a
13
+ // production instance) would silently talk to the wrong orchestrator. The
14
+ // actual port is injected here — both into the CSP connect-src allowlist
15
+ // and into window.__ORCH_PORT__ for orchestrator.js to read at runtime.
16
+ const port = parseInt(orchPort, 10) || 7718;
11
17
 
12
18
  // Agent roster moved to server/lib/html/client/agents-data.js (Sprint 31.3).
13
19
  // AgentsView.js renders it client-side; shell.js no longer needs it.
@@ -17,7 +23,7 @@ function renderHtml(state, orchToken) {
17
23
  <head>
18
24
  <meta charset="UTF-8">
19
25
  <meta name="viewport" content="width=device-width, initial-scale=1">
20
- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data:; connect-src 'self' http://localhost:7718 http://127.0.0.1:7718 ws: wss:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'">
26
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data:; connect-src 'self' http://localhost:${port} http://127.0.0.1:${port} ws: wss:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'">
21
27
  <meta http-equiv="X-Content-Type-Options" content="nosniff">
22
28
  <meta name="referrer" content="strict-origin-when-cross-origin">
23
29
  <title>Majlis — ${esc(projectName)}</title>
@@ -26,7 +32,7 @@ function renderHtml(state, orchToken) {
26
32
  <script src="https://cdn.jsdelivr.net/npm/marked@18.0.4/lib/marked.umd.js" integrity="sha384-8RA8Ah4c9upJmKfg5nH01OgjZoQ3mRX+ngrKYWXQYj2dHYxFqYz8POSlii33f0wB" crossorigin="anonymous"><\/script>
27
33
  <script src="https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js" integrity="sha384-/nfmYPUzWMS6v2atn8hbljz7NE0EI1iGx34lJaNzyVjWGDzMv+ciUZUeJpKA3Glc" crossorigin="anonymous"><\/script>
28
34
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js" integrity="sha384-AQLWHRKAgdTxkolJcLOELg4E9rE89CPE2xMy3tIRFn08NcGKPTsELdvKomqji+DL" crossorigin="anonymous"><\/script>
29
- <script>window.__ORCH_TOKEN__ = ${JSON.stringify(orchToken || '')};<\/script>
35
+ <script>window.__ORCH_TOKEN__ = ${JSON.stringify(orchToken || '')}; window.__ORCH_PORT__ = ${port};<\/script>
30
36
  ${renderCss()}
31
37
  </head>
32
38
  <body>
@@ -195,7 +195,13 @@ function buildPhaseTree(projectDir, rawPhases, listCached, overrides) {
195
195
  : (p.status === 'active' || p.status === 'in_progress') ? 'in_progress'
196
196
  : 'planned';
197
197
 
198
- return { id: sid, number: num, goal: goal || `Sprint ${num}`, status, stories, dependsOn };
198
+ // Project-relative path to this sprint's SPRINT.md same shape as the
199
+ // sprintFile computed below for roadmap cards, so /api/file?path=...
200
+ // resolves it identically. Lets task rows (which point at a specific
201
+ // sprint, not just "latest file in the phase") open the right file.
202
+ const file = path.join('.planning', 'phases', dir.name, f).split(path.sep).join('/');
203
+
204
+ return { id: sid, number: num, goal: goal || `Sprint ${num}`, status, stories, dependsOn, file };
199
205
  });
200
206
 
201
207
  // Derive phase-level depends_on by aggregating sprint-level depends_on entries.
@@ -366,11 +372,15 @@ function buildDashboard(state) {
366
372
  const stories = Array.isArray(s.stories) ? s.stories : [];
367
373
  for (const st of stories) {
368
374
  if (/done|complete/i.test(st.status || '')) {
369
- completedTasks.push({ title: st.title || st.id, date: fmtISODate(p.completed || s.completed_at || p.created) });
375
+ completedTasks.push({
376
+ title: st.title || st.id,
377
+ date: fmtISODate(p.completed || s.completed_at || p.created),
378
+ file: s.file || null,
379
+ });
370
380
  } else if (p.state === 'active') {
371
381
  // No per-task progress tracking exists — pct stays null and the UI
372
382
  // omits the percent pill rather than inventing a number.
373
- inProgressTasks.push({ title: st.title || st.id, pct: null });
383
+ inProgressTasks.push({ title: st.title || st.id, pct: null, file: s.file || null });
374
384
  }
375
385
  }
376
386
  }
@@ -378,10 +388,10 @@ function buildDashboard(state) {
378
388
  // Fallbacks so the cards are never empty when stories are unregistered in state.json.
379
389
  if (!completedTasks.length) {
380
390
  phases.filter(p => p.state === 'done').slice(-6).forEach(p =>
381
- completedTasks.push({ title: p.name, date: fmtISODate(p.completed || p.created) }));
391
+ completedTasks.push({ title: p.name, date: fmtISODate(p.completed || p.created), file: null }));
382
392
  }
383
393
  if (!inProgressTasks.length && activePhase && activePhase.state === 'active') {
384
- inProgressTasks.push({ title: activePhase.name, pct: null });
394
+ inProgressTasks.push({ title: activePhase.name, pct: null, file: null });
385
395
  }
386
396
  const tasks = {
387
397
  completed: completedTasks.slice(-8).reverse(),
@@ -419,6 +429,11 @@ function buildDashboard(state) {
419
429
  .sort((a, b) => String(b.date).localeCompare(String(a.date)))
420
430
  .slice(0, 8);
421
431
 
432
+ // ---- backlog (phases not yet started — real state.json data, no invented rows) ----
433
+ const backlog = phases
434
+ .filter(p => p.state === 'todo')
435
+ .map(p => ({ id: p.id, name: p.name, range: p.range }));
436
+
422
437
  // ---- project (identity + current user) ----
423
438
  // User comes from config, else the real OS account running the server;
424
439
  // null when neither exists (UI greets generically, hides the profile row).
@@ -430,7 +445,7 @@ function buildDashboard(state) {
430
445
  user: { name: userName, email: cfg.user_email || '' },
431
446
  };
432
447
 
433
- return { project, progress, currentPhase, timeline: { launchDate, onTrack: blockers.length === 0, points }, tasks, blockers, health, decisions, phases };
448
+ return { project, progress, currentPhase, timeline: { launchDate, onTrack: blockers.length === 0, points }, tasks, blockers, health, decisions, phases, backlog };
434
449
  }
435
450
 
436
451
  function scanStateUncached(rcodeDir) {
@@ -791,10 +791,9 @@ const server = http.createServer(async (req, res) => {
791
791
  res.writeHead(404); res.end('Not found');
792
792
  });
793
793
 
794
- server.on('error', (err) => {
795
- console.error('[orchestrator] server error:', err.message);
796
- process.exit(1);
797
- });
794
+ // NOTE: the single server 'error' handler lives below the WebSocket block —
795
+ // a second handler here would fire first and exit(1) before the EADDRINUSE
796
+ // exit(2) path runs, re-triggering the dashboard's 3s respawn loop (#964).
798
797
 
799
798
  // WebSocket upgrade — authenticate, validate the storyId, then hand off.
800
799
  if (WebSocketServer) {