Rule-based idea engine is dead code — Ideas panel is permanently empty without an LLM key
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 invokedThis 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:
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:
- 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" inpackage.json), yet one of the four dashboard panels is dead without it. - Any transient LLM failure blanks the panel entirely. A timeout, a 429, or a malformed JSON response sets
ideasSource = 'llm-failed'andideas = []— the user loses the panel for that sweep even though every input signal needed to produce deterministic ideas is already insynthesized. (Related: #87.) - The same gap exists in the
npm run injectCLI path (cliInject()indashboard/inject.mjs), which also setsideas = []onllm-failed/disabled. generateIdeas()has no test coverage, so the regression was never caught and the function has silently drifted out of sync with the currentsynthesize()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:
server.mjs— callgenerateIdeas(synthesized)whenever the LLM path does not yield ideas (provider not configured, provider returned nothing, or provider threw). SetideasSourceto a new'rules'value so the UI can distinguish it from'llm'.dashboard/inject.mjs— apply the same fallback incliInject()sonpm run injectbehaves identically.- Null-safety —
generateIdeas()currently assumesV2.fred,V2.tg.urgent,V2.energy.wtiRecent,V2.bls,V2.thermal, andV2.treasuryare 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 byoldestin the WTI-momentum rule without a zero check. - UI — render a
SIGNAL BASEDbadge forideasSource === 'rules'indashboard/public/jarvis.html, withen/frlocale strings, and keep theLLM NOT CONFIGUREDempty state only for the genuine "no ideas at all" case. - Tests — add unit coverage for
generateIdeas(): each rule's trigger condition, the 8-idea cap, the returned shape (title/text/type/confidence/horizon— the fieldsjarvis.htmlreads), 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.
Source: calesthio/Crucix