#132·Crucix

Rule-based idea engine is dead code — Ideas panel is permanently empty without an LLM key

Author: Chirag6722Created Aug 13, 2026Updated Aug 13, 2026

Problem

The rule-based trade-idea engine (generateIdeas() in dashboard/inject.mjs) is dead code. It is fully implemented (~140 lines, 11 cross-domain signal rules) and it is imported by server.mjs, but it is never called anywhere in the project.

$ grep -rn "generateIdeas" --include=*.mjs .
./dashboard/inject.mjs:6:// Exports synthesize(), generateIdeas(), fetchAllNews() for use by server.mjs
./dashboard/inject.mjs:260:export function generateIdeas(V2) {
./server.mjs:13:import { synthesize, generateIdeas } from './dashboard/inject.mjs';   <-- imported, never invoked

This has been true since the initial v2.0.0 release (ef2c647) — the import was added but the call site never was.

User-visible impact

In server.mjs (runSweepCycle, step 5) the ideas array is only ever populated by the LLM:

javascript
if (llmProvider?.isConfigured) {
  const llmIdeas = await generateLLMIdeas(...);
  if (llmIdeas) { synthesized.ideas = llmIdeas; synthesized.ideasSource = 'llm'; }
  else { synthesized.ideas = []; synthesized.ideasSource = 'llm-failed'; }
} else {
  synthesized.ideas = [];          // <-- rule engine exists but is never used
  synthesized.ideasSource = 'disabled';
}

Consequences:

  1. Every user without an LLM key gets a permanently empty "Leverageable Ideas" panel, showing only LLM NOT CONFIGURED. Crucix advertises the LLM layer as optional ("optional LLM layer" in package.json), yet one of the four dashboard panels is dead without it.
  2. Any transient LLM failure blanks the panel entirely. A timeout, a 429, or a malformed JSON response sets ideasSource = 'llm-failed' and ideas = [] — the user loses the panel for that sweep even though every input signal needed to produce deterministic ideas is already in synthesized. (Related: #87.)
  3. The same gap exists in the npm run inject CLI path (cliInject() in dashboard/inject.mjs), which also sets ideas = [] on llm-failed / disabled.
  4. generateIdeas() has no test coverage, so the regression was never caught and the function has silently drifted out of sync with the current synthesize() output shape.

Root cause

The call site was never wired up. generateIdeas() was presumably intended as the baseline layer with the LLM as an enhancement on top, but only the LLM branch was ever implemented. The unused import in server.mjs:13 is the fossil of the intended design.

Proposed solution

Restore generateIdeas() as the deterministic fallback so the Ideas panel is always populated:

  1. server.mjs — call generateIdeas(synthesized) whenever the LLM path does not yield ideas (provider not configured, provider returned nothing, or provider threw). Set ideasSource to a new 'rules' value so the UI can distinguish it from 'llm'.
  2. dashboard/inject.mjs — apply the same fallback in cliInject() so npm run inject behaves identically.
  3. Null-safetygenerateIdeas() currently assumes V2.fred, V2.tg.urgent, V2.energy.wtiRecent, V2.bls, V2.thermal, and V2.treasury are always present and dereferences them unguarded. It should tolerate partially-failed sweeps (a failed FRED/EIA/BLS source) rather than throwing and taking the whole sweep down. It also divides by oldest in the WTI-momentum rule without a zero check.
  4. UI — render a SIGNAL BASED badge for ideasSource === 'rules' in dashboard/public/jarvis.html, with en/fr locale strings, and keep the LLM NOT CONFIGURED empty state only for the genuine "no ideas at all" case.
  5. Tests — add unit coverage for generateIdeas(): each rule's trigger condition, the 8-idea cap, the returned shape (title / text / type / confidence / horizon — the fields jarvis.html reads), and graceful behaviour on an empty/degraded sweep.

Notes

The 9 existing test files under test/ are also never executed: package.json has no test script and the only workflow (docker-publish.yml) never runs them. A "test": "node --test \"test/*.test.mjs\"" script is needed for the new tests (and the existing 46) to be runnable — I'll include that minimal addition alongside the fix.