@hanzlaa/rcode 3.4.4 → 3.4.6

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 (213) hide show
  1. package/AGENTS.md +1 -1
  2. package/CONTRIBUTING.md +63 -1
  3. package/README.md +9 -4
  4. package/cli/generate-command-skills.cjs +21 -9
  5. package/cli/index.js +0 -0
  6. package/cli/install.js +126 -7
  7. package/cli/lib/manifest.cjs +1 -1
  8. package/cli/uninstall.js +8 -0
  9. package/dist/rcode.js +1279 -2004
  10. package/package.json +16 -17
  11. package/rihal/agents/rihal-ahmed.md +2 -1
  12. package/rihal/agents/rihal-code-fixer.md +46 -0
  13. package/rihal/agents/rihal-code-reviewer.md +46 -1
  14. package/rihal/agents/rihal-deviation-analyzer.md +1 -0
  15. package/rihal/agents/rihal-docs-auditor.md +106 -1
  16. package/rihal/agents/rihal-edge-case-hunter.md +47 -1
  17. package/rihal/agents/rihal-executor.md +1 -1
  18. package/rihal/agents/rihal-khalid.md +40 -1
  19. package/rihal/agents/rihal-layla.md +2 -1
  20. package/rihal/agents/rihal-nasser.md +2 -1
  21. package/rihal/agents/rihal-noor.md +3 -2
  22. package/rihal/agents/rihal-nyquist-auditor.md +1 -1
  23. package/rihal/agents/rihal-phase-researcher.md +46 -1
  24. package/rihal/agents/rihal-planner.md +1 -1
  25. package/rihal/agents/rihal-profiler.md +45 -2
  26. package/rihal/agents/rihal-project-researcher.md +47 -0
  27. package/rihal/agents/rihal-remediation-planner.md +45 -0
  28. package/rihal/agents/rihal-roadmapper.md +46 -0
  29. package/rihal/agents/rihal-security-adversary.md +46 -1
  30. package/rihal/agents/rihal-security-auditor.md +45 -1
  31. package/rihal/agents/rihal-ui-auditor.md +44 -1
  32. package/rihal/agents/rihal-ux-designer.md +41 -1
  33. package/rihal/agents/rihal-zahra.md +2 -1
  34. package/rihal/agents/rihal-zayd.md +2 -1
  35. package/rihal/bin/lib/config.cjs +13 -1
  36. package/rihal/bin/lib/council-panel.cjs +185 -23
  37. package/rihal/bin/lib/roadmap.cjs +27 -2
  38. package/rihal/bin/rihal-tools.cjs +1837 -99
  39. package/rihal/commands/audit.md +2 -2
  40. package/rihal/commands/capture.md +12 -0
  41. package/rihal/commands/diagnose-issues.md +18 -0
  42. package/rihal/commands/discuss-phase-power.md +18 -0
  43. package/rihal/commands/feature-drift.md +18 -0
  44. package/rihal/commands/karpathy-audit.md +18 -0
  45. package/rihal/commands/lens-audit.md +70 -0
  46. package/rihal/commands/new-project-research.md +18 -0
  47. package/rihal/commands/new-project-roadmap.md +18 -0
  48. package/rihal/commands/phase.md +11 -0
  49. package/rihal/references/continuation-format.md +3 -3
  50. package/rihal/references/output-format.md +79 -0
  51. package/rihal/references/revision-loop.md +1 -1
  52. package/rihal/references/verb-dictionary.md +85 -28
  53. package/rihal/skills/actions/1-analysis/rihal-prfaq/SKILL.md +1 -1
  54. package/rihal/skills/actions/2-plan/rihal-create-epics-and-stories/SKILL.md +12 -2
  55. package/rihal/skills/actions/2-plan/rihal-create-epics-and-stories/steps/step-04-final-validation.md +12 -0
  56. package/rihal/skills/actions/2-plan/rihal-create-prd/SKILL.md +12 -2
  57. package/rihal/skills/actions/2-plan/rihal-create-story/SKILL.md +12 -2
  58. package/rihal/skills/actions/4-implementation/rihal-browser-verify/SKILL.md +1 -1
  59. package/rihal/skills/actions/4-implementation/rihal-checkpoint-preview/SKILL.md +1 -1
  60. package/rihal/skills/actions/4-implementation/rihal-ci/SKILL.md +1 -1
  61. package/rihal/skills/actions/4-implementation/rihal-code-review/SKILL.md +16 -4
  62. package/rihal/skills/actions/4-implementation/rihal-debug/SKILL.md +14 -1
  63. package/rihal/skills/actions/4-implementation/rihal-git-flow/SKILL.md +1 -1
  64. package/rihal/skills/actions/4-implementation/rihal-harden/SKILL.md +1 -1
  65. package/rihal/skills/actions/4-implementation/rihal-incremental/SKILL.md +1 -1
  66. package/rihal/skills/actions/4-implementation/rihal-migrate/SKILL.md +1 -1
  67. package/rihal/skills/actions/4-implementation/rihal-perf/SKILL.md +1 -1
  68. package/rihal/skills/actions/4-implementation/rihal-prove-it/SKILL.md +1 -1
  69. package/rihal/skills/actions/4-implementation/rihal-scaffold-project/steps/step-01-target.md +6 -0
  70. package/rihal/skills/actions/4-implementation/rihal-source-truth/SKILL.md +1 -1
  71. package/rihal/skills/actions/4-implementation/rihal-sprint-planning/SKILL.md +14 -3
  72. package/rihal/skills/actions/4-implementation/rihal-trim/SKILL.md +1 -1
  73. package/rihal/skills/agents/ahmed-hassani-director/SKILL.md +15 -1
  74. package/rihal/skills/agents/dalil-scout/SKILL.md +14 -2
  75. package/rihal/skills/agents/fatima-qa/SKILL.md +16 -1
  76. package/rihal/skills/agents/haitham-frontend/SKILL.md +13 -1
  77. package/rihal/skills/agents/hanzla-engineer/SKILL.md +13 -1
  78. package/rihal/skills/agents/hussain-pm/SKILL.md +16 -1
  79. package/rihal/skills/agents/hussain-sm/SKILL.md +14 -1
  80. package/rihal/skills/agents/layla-designer/SKILL.md +13 -1
  81. package/rihal/skills/agents/majlis-council/SKILL.md +16 -1
  82. package/rihal/skills/agents/mariam-marketing/SKILL.md +14 -1
  83. package/rihal/skills/agents/nasser-eng-manager/SKILL.md +16 -1
  84. package/rihal/skills/agents/noor-writer/SKILL.md +15 -1
  85. package/rihal/skills/agents/raees-orchestrator/SKILL.md +15 -1
  86. package/rihal/skills/agents/rihal-cross-platform-auditor/SKILL.md +162 -0
  87. package/rihal/skills/agents/rihal-dep-auditor/SKILL.md +151 -0
  88. package/rihal/skills/agents/rihal-deviation-analyzer/SKILL.md +78 -0
  89. package/rihal/skills/agents/rihal-i18n-auditor/SKILL.md +152 -0
  90. package/rihal/skills/agents/rihal-observability-auditor/SKILL.md +156 -0
  91. package/rihal/skills/agents/sadiq-analyst/SKILL.md +12 -2
  92. package/rihal/skills/agents/waleed-architect/SKILL.md +12 -2
  93. package/rihal/skills/agents/yousef-backend/SKILL.md +12 -2
  94. package/rihal/skills/agents/zahra-branding/SKILL.md +15 -1
  95. package/rihal/skills/agents/zayd-ml/SKILL.md +13 -1
  96. package/rihal/skills/core/rihal-advanced-elicitation/SKILL.md +2 -2
  97. package/rihal/skills/core/rihal-auth-audit/SKILL.md +1 -1
  98. package/rihal/skills/core/rihal-brainstorming/SKILL.md +13 -2
  99. package/rihal/skills/core/rihal-client-gate/SKILL.md +1 -1
  100. package/rihal/skills/core/rihal-clone-website/SKILL.md +11 -1
  101. package/rihal/skills/core/rihal-deploy-unify/SKILL.md +1 -1
  102. package/rihal/skills/core/rihal-distillator/SKILL.md +2 -2
  103. package/rihal/skills/core/rihal-editorial-review-prose/SKILL.md +1 -1
  104. package/rihal/skills/core/rihal-editorial-review-structure/SKILL.md +2 -2
  105. package/rihal/skills/core/rihal-help/SKILL.md +18 -1
  106. package/rihal/skills/core/rihal-incident-record/SKILL.md +1 -1
  107. package/rihal/skills/core/rihal-index-docs/SKILL.md +1 -1
  108. package/rihal/skills/core/rihal-memory-audit/SKILL.md +18 -1
  109. package/rihal/skills/core/rihal-memory-init/SKILL.md +13 -1
  110. package/rihal/skills/core/rihal-memory-update/SKILL.md +13 -1
  111. package/rihal/skills/core/rihal-mvp-graduate/SKILL.md +1 -1
  112. package/rihal/skills/core/rihal-ocr-consistency/SKILL.md +1 -1
  113. package/rihal/skills/core/rihal-rebrand/SKILL.md +1 -1
  114. package/rihal/skills/core/rihal-review-adversarial-general/SKILL.md +1 -1
  115. package/rihal/skills/core/rihal-review-edge-case-hunter/SKILL.md +17 -1
  116. package/rihal/skills/core/rihal-shard-doc/SKILL.md +1 -1
  117. package/rihal/skills/core/rihal-theme-system/SKILL.md +1 -1
  118. package/rihal/team.yaml +0 -7
  119. package/rihal/templates/RESEARCH.md +84 -0
  120. package/rihal/templates/VALIDATION.md +45 -0
  121. package/rihal/templates/memory/INDEX.md +1 -0
  122. package/rihal/templates/memory/project/design-system.md +128 -0
  123. package/rihal/templates/summary.md +33 -3
  124. package/rihal/workflows/add-tests.md +1 -1
  125. package/rihal/workflows/add-todo.md +6 -0
  126. package/rihal/workflows/analyze-dependencies.md +6 -0
  127. package/rihal/workflows/audit-fix.md +12 -0
  128. package/rihal/workflows/audit-milestone.md +2 -2
  129. package/rihal/workflows/audit.md +23 -14
  130. package/rihal/workflows/autonomous-smart-discuss.md +247 -0
  131. package/rihal/workflows/autonomous.md +54 -267
  132. package/rihal/workflows/capture.md +60 -0
  133. package/rihal/workflows/chain.md +1 -1
  134. package/rihal/workflows/code-review-fix.md +6 -3
  135. package/rihal/workflows/code-review.md +34 -10
  136. package/rihal/workflows/complete-milestone.md +17 -8
  137. package/rihal/workflows/correct-course.md +6 -0
  138. package/rihal/workflows/council.md +37 -23
  139. package/rihal/workflows/create-architecture.md +31 -0
  140. package/rihal/workflows/create-epics-and-stories.md +7 -1
  141. package/rihal/workflows/create-prd.md +25 -0
  142. package/rihal/workflows/dashboard.md +1 -1
  143. package/rihal/workflows/debug.md +8 -0
  144. package/rihal/workflows/decisions.md +1 -1
  145. package/rihal/workflows/diff.md +6 -0
  146. package/rihal/workflows/discuss-phase-discuss-areas.md +271 -0
  147. package/rihal/workflows/discuss-phase.md +27 -266
  148. package/rihal/workflows/do.md +51 -12
  149. package/rihal/workflows/docs-update.md +3 -0
  150. package/rihal/workflows/document-project.md +7 -1
  151. package/rihal/workflows/edit-prd.md +31 -0
  152. package/rihal/workflows/enable-hooks.md +1 -1
  153. package/rihal/workflows/execute-regression-gates.md +131 -0
  154. package/rihal/workflows/execute-sprint.md +31 -2
  155. package/rihal/workflows/execute-verify-phase-goal.md +136 -0
  156. package/rihal/workflows/execute-waves.md +404 -0
  157. package/rihal/workflows/execute.md +101 -642
  158. package/rihal/workflows/feature-drift.md +243 -0
  159. package/rihal/workflows/forensics.md +10 -2
  160. package/rihal/workflows/health.md +65 -16
  161. package/rihal/workflows/help.md +36 -9
  162. package/rihal/workflows/import.md +17 -3
  163. package/rihal/workflows/init.md +20 -10
  164. package/rihal/workflows/install.md +2 -10
  165. package/rihal/workflows/lens-audit.md +689 -0
  166. package/rihal/workflows/map-codebase.md +7 -1
  167. package/rihal/workflows/memory-audit.md +67 -5
  168. package/rihal/workflows/memory-distill.md +10 -0
  169. package/rihal/workflows/memory-init.md +4 -0
  170. package/rihal/workflows/memory-update.md +4 -0
  171. package/rihal/workflows/new-milestone.md +7 -1
  172. package/rihal/workflows/new-project-create-roadmap.md +176 -0
  173. package/rihal/workflows/new-project-define-requirements.md +160 -0
  174. package/rihal/workflows/new-project-research-decision.md +247 -0
  175. package/rihal/workflows/new-project.md +3 -557
  176. package/rihal/workflows/note.md +1 -1
  177. package/rihal/workflows/phase.md +54 -0
  178. package/rihal/workflows/plan-milestone-gaps.md +1 -1
  179. package/rihal/workflows/plan-prd-express.md +108 -0
  180. package/rihal/workflows/plan-research-validation.md +313 -0
  181. package/rihal/workflows/plan-spawn-planner.md +204 -0
  182. package/rihal/workflows/plan.md +91 -532
  183. package/rihal/workflows/plant-seed.md +1 -1
  184. package/rihal/workflows/pr-branch.md +1 -1
  185. package/rihal/workflows/profile-user.md +1 -1
  186. package/rihal/workflows/quick.md +3 -3
  187. package/rihal/workflows/remove-phase.md +6 -1
  188. package/rihal/workflows/remove-workspace.md +6 -0
  189. package/rihal/workflows/rerun.md +1 -1
  190. package/rihal/workflows/research-phase.md +4 -2
  191. package/rihal/workflows/resume-work.md +8 -3
  192. package/rihal/workflows/retrospective.md +31 -0
  193. package/rihal/workflows/review-adversarial.md +12 -0
  194. package/rihal/workflows/review.md +6 -0
  195. package/rihal/workflows/scaffold-project.md +31 -0
  196. package/rihal/workflows/scan.md +10 -0
  197. package/rihal/workflows/secure-phase.md +15 -2
  198. package/rihal/workflows/session-report.md +32 -7
  199. package/rihal/workflows/ship.md +7 -2
  200. package/rihal/workflows/show.md +6 -0
  201. package/rihal/workflows/sprint-status.md +4 -4
  202. package/rihal/workflows/status.md +2 -2
  203. package/rihal/workflows/ui-phase.md +1 -1
  204. package/rihal/workflows/undo.md +2 -3
  205. package/rihal/workflows/update.md +2 -2
  206. package/rihal/workflows/validate-phase.md +1 -1
  207. package/rihal/workflows/validate-prd.md +31 -0
  208. package/rihal/workflows/verify-phase.md +38 -5
  209. package/rihal/workflows/verify-work.md +25 -11
  210. package/rihal/workflows/workstream.md +20 -8
  211. package/server/lib/html/client.js +13 -63
  212. package/server/lib/html/shell.js +0 -1
  213. package/server/lib/scanner.js +33 -2
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Council panel selection — pure function that picks the right 3-5 agents
3
- * for a given strategic question.
2
+ * Council panel selection — pure function that picks the right agents
3
+ * for a given question.
4
4
  *
5
5
  * This is the v2 version of the scorer, installed alongside rihal-tools.cjs
6
6
  * at {project-root}/.rihal/bin/lib/council-panel.cjs. The helper binary
@@ -16,6 +16,8 @@
16
16
  * - Auditable: `explainSelection()` returns per-agent scores so users
17
17
  * running with `--explain` can see why each agent was picked.
18
18
  * - Cheap: zero LLM calls before the council starts.
19
+ * - Intent-matched: domain-specific panels. FE question → Haitham leads.
20
+ * BE question → Yousef leads. No strategic padding for technical work.
19
21
  * - Optional team.yaml: If team.yaml exists at project root, reads
20
22
  * domain_keywords from it. Fallback: hardcoded keywords below.
21
23
  *
@@ -25,16 +27,17 @@
25
27
  * else use hardcoded KEYWORDS below.
26
28
  * 2. Normalize the question (lowercase, strip punctuation).
27
29
  * 3. For each agent, sum the weight of every keyword that appears.
28
- * 4. Apply priority boosts (Sadiq for strategic triggers, Hussain-PM
29
- * for scope triggers).
30
+ * 4. Apply priority boosts (Sadiq for strategic triggers, domain
31
+ * boosts for FE/BE/ML/deploy questions).
30
32
  * 5. Named-agent mentions get +20 (overrides topic score).
31
- * 6. Sort by score desc. Tiebreaker: STRATEGIC_PADDING_ORDER for
32
- * strategic questions, AGENT_IDS for others.
33
- * 7. Take top K (default maxPanel = 5). Pad to minPanel (default 3) if
34
- * fewer agents scored non-zero, using STRATEGIC_PADDING_ORDER (or
35
- * AGENT_IDS for non-strategic) as the fill pool.
36
- * 8. If opts.full, return AGENT_IDS (canonical order).
37
- * 9. If opts.agents, return that exact list (validated).
33
+ * 6. Detect domain from top-scoring agents: fe / be / ml / deploy /
34
+ * quality / strategic / market.
35
+ * 7. Sort by score desc. Tiebreaker: domain-specific padding order.
36
+ * 8. maxPanel=3 for technical domains (fe/be/ml/deploy/quality),
37
+ * maxPanel=4 for strategic/market. minPanel=1 always no forced
38
+ * lame padding for specific technical questions.
39
+ * 9. If opts.full, return AGENT_IDS (canonical order).
40
+ * 10. If opts.agents, return that exact list (validated).
38
41
  *
39
42
  * The orchestrator is responsible for filtering the result to installed
40
43
  * agents. This module returns the "ideal" panel; the workflow validates
@@ -307,6 +310,75 @@ const STRATEGIC_PADDING_ORDER = [
307
310
  'zahra', 'zayd', 'mariam', 'noor',
308
311
  ];
309
312
 
313
+ // Domain-specific padding orders — used when a technical domain is clearly detected.
314
+ // These put the right specialists first and keep PM/strategy out unless asked.
315
+ const FRONTEND_PADDING_ORDER = [
316
+ 'haitham', 'layla', 'zahra', 'yousef', 'waleed',
317
+ 'fatima', 'sadiq', 'hussain-pm', 'zayd', 'khalid',
318
+ 'nasser', 'ahmed-hassani', 'mariam', 'noor',
319
+ ];
320
+
321
+ const BACKEND_PADDING_ORDER = [
322
+ 'yousef', 'waleed', 'khalid', 'fatima', 'haitham',
323
+ 'zayd', 'ahmed-hassani', 'sadiq', 'hussain-pm', 'layla',
324
+ 'nasser', 'zahra', 'mariam', 'noor',
325
+ ];
326
+
327
+ const ML_PADDING_ORDER = [
328
+ 'zayd', 'yousef', 'waleed', 'fatima', 'khalid',
329
+ 'haitham', 'sadiq', 'hussain-pm', 'ahmed-hassani', 'layla',
330
+ 'nasser', 'zahra', 'mariam', 'noor',
331
+ ];
332
+
333
+ const DEPLOY_PADDING_ORDER = [
334
+ 'khalid', 'waleed', 'yousef', 'ahmed-hassani', 'fatima',
335
+ 'sadiq', 'haitham', 'zayd', 'hussain-pm', 'nasser',
336
+ 'layla', 'zahra', 'mariam', 'noor',
337
+ ];
338
+
339
+ const QUALITY_PADDING_ORDER = [
340
+ 'fatima', 'waleed', 'yousef', 'haitham', 'khalid',
341
+ 'sadiq', 'zayd', 'ahmed-hassani', 'hussain-pm', 'layla',
342
+ 'nasser', 'zahra', 'mariam', 'noor',
343
+ ];
344
+
345
+ // Domain trigger arrays — when these fire, the question is clearly technical
346
+ // and should NOT be padded with PM/strategy agents.
347
+ const FE_TRIGGERS = [
348
+ 'react', 'component', 'frontend', 'front-end', 'next.js', 'nextjs',
349
+ 'tailwind', 'css', 'html', 'tsx', 'jsx', 'rtl', 'a11y', 'accessibility',
350
+ 'ui ', 'ux ', 'layout', 'responsive', 'animation', 'hydration',
351
+ 'bundle size', 'lighthouse', 'cls', 'lcp', 'tbt',
352
+ // Roman Urdu FE signals
353
+ 'fe ', 'front end', 'button', 'page ', 'screen ', 'form ',
354
+ ];
355
+
356
+ const BE_TRIGGERS = [
357
+ 'backend', 'back-end', 'api', 'endpoint', 'server', 'prisma', 'database',
358
+ 'query', 'schema', 'migration', 'queue', 'webhook', 'rest', 'graphql',
359
+ 'n+1', 'index', 'latency', 'timeout', 'caching', 'redis', 'postgres',
360
+ 'mysql', 'mongodb', 'bullmq', 'celery', 'worker', 'job', 'cron',
361
+ // Roman Urdu BE signals
362
+ 'be ', 'db ', 'api call', 'server side',
363
+ ];
364
+
365
+ const ML_TRIGGERS = [
366
+ 'llm', 'model', 'embedding', 'rag', 'retrieval', 'vector', 'ocr',
367
+ 'prompt', 'inference', 'fine-tun', 'dataset', 'eval', 'nlp',
368
+ 'openai', 'anthropic', 'gemini', 'gpt', 'claude', 'mistral',
369
+ ];
370
+
371
+ const DEPLOY_TRIGGERS = [
372
+ 'deploy', 'docker', 'kubernetes', 'k8s', 'ci/cd', 'cicd', 'pipeline',
373
+ 'rollback', 'incident', 'outage', 'monitoring', 'alert', 'sre',
374
+ 'infra', 'cloud', 'aws', 'gcp', 'azure', 'vps',
375
+ ];
376
+
377
+ const QUALITY_TRIGGERS = [
378
+ 'test coverage', 'qa ', 'regression', 'flaky', 'production ready',
379
+ 'ready to ship', 'release ready', 'perf test', 'load test', 'benchmark',
380
+ ];
381
+
310
382
  const AGENT_NAMES = {
311
383
  sadiq: ['sadiq'],
312
384
  'hussain-pm': ['hussain', 'hussain-pm', 'hussain pm'],
@@ -357,9 +429,64 @@ function applyPriorityBoosts(scores, normalizedQuestion) {
357
429
  scores.mariam = (scores.mariam || 0) + 6; // Mariam leads market questions
358
430
  scores['hussain-pm'] = (scores['hussain-pm'] || 0) + 3; // PM follows for scoping
359
431
  }
432
+ // Domain boosts — lift the right technical expert when signal is clear
433
+ if (FE_TRIGGERS.some((t) => normalizedQuestion.includes(t))) {
434
+ scores.haitham = (scores.haitham || 0) + 4;
435
+ }
436
+ if (BE_TRIGGERS.some((t) => normalizedQuestion.includes(t))) {
437
+ scores.yousef = (scores.yousef || 0) + 4;
438
+ }
439
+ if (ML_TRIGGERS.some((t) => normalizedQuestion.includes(t))) {
440
+ scores.zayd = (scores.zayd || 0) + 4;
441
+ }
442
+ if (DEPLOY_TRIGGERS.some((t) => normalizedQuestion.includes(t))) {
443
+ scores.khalid = (scores.khalid || 0) + 4;
444
+ }
445
+ if (QUALITY_TRIGGERS.some((t) => normalizedQuestion.includes(t))) {
446
+ scores.fatima = (scores.fatima || 0) + 4;
447
+ }
360
448
  return scores;
361
449
  }
362
450
 
451
+ /**
452
+ * Detect the primary domain of a question from its normalized text and scores.
453
+ * Returns: 'fe' | 'be' | 'ml' | 'deploy' | 'quality' | 'market' | 'strategic' | 'general'
454
+ */
455
+ function detectDomain(normalizedQuestion, scores) {
456
+ const isMarket = MARKET_TRIGGERS.some((t) => normalizedQuestion.includes(t));
457
+ if (isMarket) return 'market';
458
+
459
+ const isStrategic = SADIQ_TRIGGERS.some((t) => normalizedQuestion.includes(t));
460
+
461
+ const feTrigger = FE_TRIGGERS.some((t) => normalizedQuestion.includes(t));
462
+ const beTrigger = BE_TRIGGERS.some((t) => normalizedQuestion.includes(t));
463
+ const mlTrigger = ML_TRIGGERS.some((t) => normalizedQuestion.includes(t));
464
+ const deployTrigger = DEPLOY_TRIGGERS.some((t) => normalizedQuestion.includes(t));
465
+ const qualityTrigger = QUALITY_TRIGGERS.some((t) => normalizedQuestion.includes(t));
466
+
467
+ // Multiple technical domains present — fall back to top-scoring agent
468
+ const technicalCount = [feTrigger, beTrigger, mlTrigger, deployTrigger, qualityTrigger].filter(Boolean).length;
469
+ if (technicalCount >= 2) {
470
+ // Resolve by whichever technical expert scored highest
471
+ const techLeaders = [
472
+ { domain: 'fe', agent: 'haitham', score: scores.haitham || 0 },
473
+ { domain: 'be', agent: 'yousef', score: scores.yousef || 0 },
474
+ { domain: 'ml', agent: 'zayd', score: scores.zayd || 0 },
475
+ { domain: 'deploy', agent: 'khalid', score: scores.khalid || 0 },
476
+ { domain: 'quality', agent: 'fatima', score: scores.fatima || 0 },
477
+ ].sort((a, b) => b.score - a.score);
478
+ if (techLeaders[0].score > 0) return techLeaders[0].domain;
479
+ }
480
+
481
+ if (feTrigger) return 'fe';
482
+ if (beTrigger) return 'be';
483
+ if (mlTrigger) return 'ml';
484
+ if (deployTrigger) return 'deploy';
485
+ if (qualityTrigger) return 'quality';
486
+ if (isStrategic) return 'strategic';
487
+ return 'general';
488
+ }
489
+
363
490
  function validateAgents(agents) {
364
491
  const bad = agents.filter((id) => !AGENT_IDS.includes(id));
365
492
  if (bad.length > 0) {
@@ -368,34 +495,59 @@ function validateAgents(agents) {
368
495
  return agents;
369
496
  }
370
497
 
498
+ const DOMAIN_PADDING = {
499
+ fe: FRONTEND_PADDING_ORDER,
500
+ be: BACKEND_PADDING_ORDER,
501
+ ml: ML_PADDING_ORDER,
502
+ deploy: DEPLOY_PADDING_ORDER,
503
+ quality: QUALITY_PADDING_ORDER,
504
+ market: MARKET_PADDING_ORDER,
505
+ strategic: STRATEGIC_PADDING_ORDER,
506
+ general: STRATEGIC_PADDING_ORDER,
507
+ };
508
+
509
+ // Technical domains keep panels small — 1 right expert beats 3 wrong ones.
510
+ const DOMAIN_MAX_PANEL = {
511
+ fe: 3, be: 3, ml: 3, deploy: 3, quality: 3,
512
+ market: 4, strategic: 4, general: 3,
513
+ };
514
+
515
+ // minPanel=1: never force-pad with irrelevant agents.
516
+ const DOMAIN_MIN_PANEL = {
517
+ fe: 1, be: 1, ml: 1, deploy: 1, quality: 1,
518
+ market: 2, strategic: 2, general: 1,
519
+ };
520
+
371
521
  function selectPanel(question, opts = {}) {
372
522
  if (opts.full) return [...AGENT_IDS];
373
523
  if (opts.agents && opts.agents.length > 0) return validateAgents(opts.agents);
374
524
 
375
- const maxPanel = opts.maxPanel || 5;
376
- const minPanel = opts.minPanel || 3;
377
525
  const normalized = normalize(question);
378
- if (!normalized) return STRATEGIC_PADDING_ORDER.slice(0, minPanel);
526
+ if (!normalized) return STRATEGIC_PADDING_ORDER.slice(0, 2);
379
527
 
380
528
  const scores = {};
381
529
  for (const agentId of AGENT_IDS) scores[agentId] = scoreAgent(agentId, normalized);
382
530
  applyPriorityBoosts(scores, normalized);
383
531
 
384
- const isStrategic = SADIQ_TRIGGERS.some((t) => normalized.includes(t));
385
- const isMarket = MARKET_TRIGGERS.some((t) => normalized.includes(t));
386
- const tiebreakOrder = isMarket ? MARKET_PADDING_ORDER : isStrategic ? STRATEGIC_PADDING_ORDER : AGENT_IDS;
532
+ const domain = detectDomain(normalized, scores);
533
+ const maxPanel = opts.maxPanel || DOMAIN_MAX_PANEL[domain];
534
+ const minPanel = opts.minPanel || DOMAIN_MIN_PANEL[domain];
535
+ const paddingPool = DOMAIN_PADDING[domain];
536
+
387
537
  const ranked = [...AGENT_IDS]
388
538
  .map((id) => ({ id, score: scores[id] }))
389
539
  .sort((a, b) => {
390
540
  if (b.score !== a.score) return b.score - a.score;
391
- return tiebreakOrder.indexOf(a.id) - tiebreakOrder.indexOf(b.id);
541
+ return paddingPool.indexOf(a.id) - paddingPool.indexOf(b.id);
392
542
  });
393
543
 
394
544
  const scored = ranked.filter((a) => a.score > 0).slice(0, maxPanel);
545
+
546
+ // If we have at least minPanel scored agents, return them — no padding needed.
395
547
  if (scored.length >= minPanel) return scored.map((a) => a.id);
396
548
 
549
+ // Pad only up to minPanel using domain-appropriate agents.
397
550
  const alreadyPicked = new Set(scored.map((a) => a.id));
398
- const paddingPool = isMarket ? MARKET_PADDING_ORDER : isStrategic ? STRATEGIC_PADDING_ORDER : AGENT_IDS;
399
551
  const padding = [];
400
552
  for (const id of paddingPool) {
401
553
  if (alreadyPicked.has(id)) continue;
@@ -410,11 +562,16 @@ function explainSelection(question, opts = {}) {
410
562
  const scores = {};
411
563
  for (const agentId of AGENT_IDS) scores[agentId] = scoreAgent(agentId, normalized);
412
564
  applyPriorityBoosts(scores, normalized);
565
+ const domain = detectDomain(normalized, scores);
413
566
  const panel = selectPanel(question, opts);
414
567
  return {
415
- question, normalized, scores, panel,
568
+ question, normalized, scores, panel, domain,
416
569
  sadiq_triggered: SADIQ_TRIGGERS.some((t) => normalized.includes(t)),
417
570
  pm_triggered: PM_TRIGGERS.some((t) => normalized.includes(t)),
571
+ fe_triggered: FE_TRIGGERS.some((t) => normalized.includes(t)),
572
+ be_triggered: BE_TRIGGERS.some((t) => normalized.includes(t)),
573
+ ml_triggered: ML_TRIGGERS.some((t) => normalized.includes(t)),
574
+ deploy_triggered: DEPLOY_TRIGGERS.some((t) => normalized.includes(t)),
418
575
  };
419
576
  }
420
577
 
@@ -494,8 +651,13 @@ function loadTeamConfig(projectRoot) {
494
651
  }
495
652
 
496
653
  module.exports = {
497
- AGENT_IDS, KEYWORDS, SADIQ_TRIGGERS, PM_TRIGGERS, MARKET_TRIGGERS, AGENT_NAMES,
654
+ AGENT_IDS, KEYWORDS, AGENT_NAMES,
655
+ SADIQ_TRIGGERS, PM_TRIGGERS, MARKET_TRIGGERS,
656
+ FE_TRIGGERS, BE_TRIGGERS, ML_TRIGGERS, DEPLOY_TRIGGERS, QUALITY_TRIGGERS,
498
657
  STRATEGIC_PADDING_ORDER, MARKET_PADDING_ORDER,
499
- normalize, scoreAgent, applyPriorityBoosts, selectPanel, explainSelection,
500
- loadTeamConfig,
658
+ FRONTEND_PADDING_ORDER, BACKEND_PADDING_ORDER, ML_PADDING_ORDER,
659
+ DEPLOY_PADDING_ORDER, QUALITY_PADDING_ORDER,
660
+ DOMAIN_PADDING, DOMAIN_MAX_PANEL, DOMAIN_MIN_PANEL,
661
+ normalize, scoreAgent, applyPriorityBoosts, detectDomain,
662
+ selectPanel, explainSelection, loadTeamConfig,
501
663
  };
@@ -19,7 +19,11 @@ function roadmapPathFor(projectRoot) {
19
19
  * number, name, goal, section (raw markdown slice), headerIndex, sectionEnd
20
20
  */
21
21
  function extractPhases(content) {
22
- const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
22
+ // Accept any of: ":", "—" (em-dash), "-" (hyphen) between phase number and name.
23
+ // Pre-#464 the regex required ":" only, which silently rejected heading-style
24
+ // ROADMAP using em-dash ("## Phase 6 — Name") and broke roadmap list-phases
25
+ // and roadmap get-phase. Same drift family as #455.
26
+ const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*[—\-:]\s*([^\n]+)/gi;
23
27
  const hits = [];
24
28
  let m;
25
29
  while ((m = phasePattern.exec(content)) !== null) {
@@ -32,10 +36,26 @@ function extractPhases(content) {
32
36
  const section = content.slice(h.headerIndex, end).trim();
33
37
  const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
34
38
  const goal = goalMatch ? goalMatch[1].trim() : null;
39
+
40
+ // Status parsing (Phase 10 / #466 / closes secondary part of #464).
41
+ // Maps the literal **Status:** line to a canonical enum.
42
+ const statusMatch = section.match(/\*\*Status(?::\*\*|\*\*:)\s*([^\n]+)/i);
43
+ const statusRaw = statusMatch ? statusMatch[1].trim() : null;
44
+ let status = 'unknown';
45
+ if (statusRaw) {
46
+ const s = statusRaw.toLowerCase();
47
+ if (s.startsWith('complete')) status = 'complete';
48
+ else if (s.startsWith('active') || s.startsWith('in progress') || s.includes('sprint')) status = 'active';
49
+ else if (s.startsWith('planned')) status = 'planned';
50
+ else if (s.startsWith('closed')) status = 'closed';
51
+ }
52
+
35
53
  phases.push({
36
54
  number: h.number,
37
55
  name: h.name,
38
56
  goal,
57
+ status,
58
+ status_raw: statusRaw,
39
59
  section,
40
60
  headerIndex: h.headerIndex,
41
61
  sectionEnd: end,
@@ -129,10 +149,15 @@ function cmdListPhases(projectRoot) {
129
149
  const rp = roadmapPathFor(projectRoot);
130
150
  if (!fs.existsSync(rp)) return [];
131
151
  const content = fs.readFileSync(rp, 'utf8');
152
+ // Phase 10 / #466 — prefer the parsed Status field from extractPhases over
153
+ // the legacy phaseStatus() heuristic, which only matched literal "completed"
154
+ // in the header and missed our **Status:** Complete convention. Fall back to
155
+ // phaseStatus() only when extractPhases couldn't parse a Status line.
132
156
  return extractPhases(content).map((p) => ({
133
157
  number: p.number,
134
158
  name: p.name,
135
- status: phaseStatus(p.section),
159
+ status: p.status === 'unknown' ? phaseStatus(p.section) : p.status,
160
+ status_raw: p.status_raw,
136
161
  }));
137
162
  }
138
163