fix(cli): keep total-failure JSON errors parseable

Remove obsolete unsupported-import diagnostics and defer comparison warnings until partial success. Preserve per-symbol diagnostics and quiet behavior when results remain available.
This commit is contained in:
hermes 2026-10-03 16:21:35 +00:00
commit b4dcfceef6
3 changed files with 20 additions and 30 deletions

View file

@ -137,6 +137,7 @@ CLI flags > environment variables > config file > defaults
- Table mode: human-readable error on stderr - Table mode: human-readable error on stderr
- JSON mode: `{"error": true, "code": "...", "message": "..."}` - JSON mode: `{"error": true, "code": "...", "message": "..."}`
- Exit code 0 on success, non-zero on failure - Exit code 0 on success, non-zero on failure
- A totally failed `stocks compare` emits one structured error in JSON mode; per-symbol warnings are retained for partial success, not printed ahead of a total-failure error.
## CLI Output and Offline Cache ## CLI Output and Offline Cache
- `version`, `cache info`, `cache clear`, and `ownership resolve map|merge` emit - `version`, `cache info`, `cache clear`, and `ownership resolve map|merge` emit

View file

@ -18,7 +18,6 @@ use crate::ownership::types::{
ChangeType, FlowSignal, HolderRow, KseiHolding, OwnershipRelease, OwnershipSource, ChangeType, FlowSignal, HolderRow, KseiHolding, OwnershipRelease, OwnershipSource,
}; };
use crate::ownership::{archive, db, entities, graph, parser, remote, search, snapshot, xlsx}; use crate::ownership::{archive, db, entities, graph, parser, remote, search, snapshot, xlsx};
use crate::runtime;
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[command( #[command(
@ -786,29 +785,16 @@ fn handle_import(args: &ImportArgs, config: &IdxConfig) -> Result<(), IdxError>
)); ));
} }
if let Some(symbols) = &args.fetch_bing { if args.fetch_bing.as_ref().is_some_and(|symbols| {
let clean: Vec<String> = symbols symbols
.iter() .iter()
.flat_map(|v| v.split(',')) .flat_map(|value| value.split(','))
.map(str::trim) .any(|value| !value.trim().is_empty())
.filter(|s| !s.is_empty()) }) {
.map(|s| s.to_uppercase())
.collect();
if !clean.is_empty() {
runtime::info(format!(
"--fetch-bing requested for {} symbol(s), implementation deferred for Sprint 6",
clean.len()
));
for symbol in &clean {
runtime::info(format!(" - {symbol}"));
}
return Err(IdxError::Unsupported( return Err(IdxError::Unsupported(
"--fetch-bing import is not implemented yet".to_string(), "--fetch-bing import is not implemented yet".to_string(),
)); ));
} }
}
let Some(import_input) = resolve_import_input(args)? else { let Some(import_input) = resolve_import_input(args)? else {
return Ok(()); return Ok(());

View file

@ -647,7 +647,7 @@ pub fn handle(
} }
StocksSubcommand::Compare { symbols } => { StocksSubcommand::Compare { symbols } => {
let mut reports: Vec<FundamentalReport> = Vec::new(); let mut reports: Vec<FundamentalReport> = Vec::new();
let mut last_error = None; let mut failures = Vec::new();
for sym in symbols.iter().flat_map(|s| s.split(',')) { for sym in symbols.iter().flat_map(|s| s.split(',')) {
let resolved = crate::api::resolve_symbol(sym, &config.exchange)?; let resolved = crate::api::resolve_symbol(sym, &config.exchange)?;
@ -664,21 +664,24 @@ pub fn handle(
analyze_fundamental, analyze_fundamental,
) { ) {
Ok(report) => reports.push(report), Ok(report) => reports.push(report),
Err(err) => { Err(err) => failures.push((resolved, err)),
runtime::warn(format!(
"failed to fetch fundamentals for {resolved}: {err}"
));
last_error = Some(err);
}
} }
} }
if reports.is_empty() { if reports.is_empty() {
return Err(last_error.unwrap_or_else(|| { return Err(failures.pop().map(|(_, err)| err).unwrap_or_else(|| {
IdxError::CacheMiss("fundamental/no symbols could be compared".to_string()) IdxError::CacheMiss("fundamental/no symbols could be compared".to_string())
})); }));
} }
// Partial success needs diagnostics; total failure must leave stderr
// available for the single structured error emitted by the caller.
for (resolved, err) in failures {
runtime::warn(format!(
"failed to fetch fundamentals for {resolved}: {err}"
));
}
render_compare(&reports, &config.output, config.no_color) render_compare(&reports, &config.output, config.no_color)
} }
} }