Phase 2 of docs/DESIGN.md, no behavior change:
- crates/idx-core (library): api, analysis, cache, config file handling
and provider selection, curl_impersonate, error, ownership, runtime,
plus the shared test fixtures. clap derives are behind an optional
`clap` feature so the library does not require clap.
- crates/idx-cli (binary `idx`): cli/, output/, and IdxConfig (output
format and color resolution). Package and binary names are unchanged.
- config.rs is split: file read/write and validation move to core; a
CLI test keeps core's OUTPUT_VALUES in sync with OutputSetting.
- ownership::db::open_db/db_path drop their unused IdxConfig argument.
- One workspace version; shared dependency versions; no third-party
version changes in Cargo.lock. LICENSE is symlinked into each crate.
- flake: workspace version, new source paths, and a per-crate package
check (idx-cli is built from its tarball against packaged idx-core,
since idx-core is not on crates.io yet). CI cache key, release path
filter, and the golden whitespace-hook exclusion follow the new layout.
Parity: all 143 golden outputs match byte for byte without
regeneration; 157 core + 17 CLI unit tests (173 before plus the new
OUTPUT_VALUES sync test), 62 CLI tests pass. Live A/B against main:
16/16 stocks cases identical; ownership discover got IDX 403 on both
(exit 4 vs 1 is the phase 1 exit-code change).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StatementSection.values was a HashMap, so `stocks financials -o json`
printed line items in a different order on every run. Table output
already sorted them alphabetically; JSON now matches via BTreeMap.
Values are unchanged, and older cache entries deserialize as before.
Found while capturing golden output, which needs deterministic bytes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
change, change_pct and volume were `unwrap_or(0)` when the provider
omitted them (MSN quotes and screener rows; Yahoo with no previous
close), so "unknown" read as "unchanged, zero volume". They are now
Option fields that serialize as null when missing. The table shows "-",
and the performers screens sort unknown changes last in both
directions. Present values are unchanged and cached entries still load.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ld3fDSDiBzAaQvgZJX5NKM
Quotes had no timestamp, so a suspended stock's last price looked
current: Yahoo returns WIKA at 204 with regularMarketTime 2025-02-17.
Add `as_of` (RFC 3339 in WIB) from MSN `timeLastTraded` / Yahoo
`regularMarketTime`, also on screener rows. The table prints a note
under any quote whose last trade is over a week old. The field is
additive and defaults to null, so existing cache entries still load.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ld3fDSDiBzAaQvgZJX5NKM
MSN returns suspended names (21 of 896 on 2026-10-02, e.g. WIKA, SUPR)
with symbol, name and market cap but no price. These were reported as
SYMBOLNOTFOUND, which tells an agent the ticker does not exist. Add a
NoMarketData error for this case; unknown tickers stay SYMBOLNOTFOUND.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ld3fDSDiBzAaQvgZJX5NKM
MSN SentimentBrowser returns `[]` for every IDX ticker checked live
(BBCA, BBRI, TLKM), so `stocks sentiment` always failed with
PARSEERROR. Return empty statistics instead; the table prints a
one-line notice and JSON gets `"statistics": []` with exit 0.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ld3fDSDiBzAaQvgZJX5NKM
MSN stamps each IDX trading day at local midnight (17:00Z the previous
day), so every MSN candle was labelled one day early. Yahoo weekly and
monthly bars have the same stamp and were labelled Sunday / last day of
the previous month. Take the date in the exchange offset instead: Yahoo's
meta.gmtoffset when present, else WIB (UTC+7).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ld3fDSDiBzAaQvgZJX5NKM
- sanitize_current_ratio: and_then if/else -> filter
- screener/graph sorts: sort_by with reversed cmp -> sort_by_key(Reverse)
- insert_ksei_holdings_rows: manual Result match -> ?
Fixes the Clippy step on ubuntu-latest (rust 1.97) that the nix
toolchain (1.93.1) did not flag.
MSN Finance/Charts does not serve OHLCV data for XIDX stocks (returns 404).
Following the FP principle of not exposing capabilities a provider cannot fulfil:
- Remove impl HistoryProvider for MsnProvider entirely
- Remove fetch_charts from MsnClient
- Remove parse_chart_history, resample_history, trim_history_to_period from map.rs
- Remove MsnChart, ChartSeries, RawChart from raw_types.rs
- Remove parse_history_from_str, parse_close_only_history from parse.rs
- Decouple HistoryProvider from MarketDataProvider trait bound
- Add history_provider() factory: returns None for MSN, Some(Yahoo) for Yahoo
- CLI gates History/Technical on history_provider(), fails fast for MSN
- MSN mock returns Err(Unsupported); tests verify the behaviour explicitly
- financials: fix incomeStatement serde rename (was incomeStatements, API sends singular)
- financials: flatten nested sub-objects (income/revenue/expense/cash) in parse_statement_section
- insights: rewrite RawInsight to match actual API shape ({insights:[{insightName,insightStatement,category}]})
- insights: group insight items into highlights (non-risk) and risks by category
- screener: build Quote directly in parse_screener_results, skip stocks with no price
- profile: use short_name fallback when long_name is null, hide empty fields
- history: map Finance/Charts 404 to Unsupported with clear IDX-specific message
- tests: update MSN history/technical tests to use mock provider