Commit graph

8 commits

Author SHA1 Message Date
hermes
db7c90861c feat(mcp): page get_ownership_changes and summarize all changes
Between the two latest releases there are 1,568 holder changes. With only
`limit` (up to 2000, ~200 KB), agents that wanted them all overflowed
their tool output: Hermes then paged ticker by ticker (32 calls), and
opencode 1 and 2 did the same and timed out.

- `offset` pages through the rows; responses carry `offset` and
  `next_offset` (null after the last page). Pages cap at 300 rows
  (~40 KB); the over-limit error points to `offset` and `summary`.
- Rows come largest |delta| first, so the first page holds the moves
  that matter rather than tickers starting with A.
- `summary` covers every row: counts by change type, the number of
  tickers, and the ten tickers with the most changes.

The CLI's `ownership changes` is unchanged. tools.json and the website's
MCP pages are regenerated for the new description and parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-16 09:54:30 +00:00
hermes
1796e58105 feat(news): rank this company's news first and label each item
MSN's feed for a stock is often mostly general market news: of 10 items,
ADRO, BREN and CUAN had none about the company, BBRI one, yet they were
presented as the company's news. Fetch at least 50 items, label each
`relevance: company` (tagged by MSN with the stock's instrument id, or
naming the ticker or the company) or `market`, and return company items
first, newest first, trimmed to --limit.

Company names come from the quote's short and display names, best
effort: MSN tags Telkom articles with its NYSE ADR, not TLKM, so names
catch what tags miss. Classification stays a pure function in parse_news.

Output change, intentional: news JSON gains `relevance`, the table an
ABOUT column (goldens regenerated), and the get_company tool description
explains the field (tools.json and the website tools page regenerated).
News cache keys change so entries without the field are not reused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-16 09:54:30 +00:00
hermes
5e0831d8fe fix: shared reader locks, resolved lock paths, install-only sync lock
Third Codex (gpt-6.1-sol, xhigh) pass over 1e55138, each item verified
before fixing:

- readers now hold the ownership lock shared for the life of their
  connection, so a snapshot swap (exclusive) never happens under an
  open SQLite connection whose WAL cleanup could hit a later import.
- the lock file name uses the resolved database path, so a symlinked
  path shares the real database's lock; sync also installs at the
  resolved path, keeping a symlinked path a symlink (it used to be
  overwritten with a regular file).
- sync downloads without the lock and takes it only to recheck and
  install; snapshot downloads have connect (15 s) and total (10 min)
  timeouts. idx-mcp drops its in-process RwLock: idx-core's lock covers
  it across processes.
- XLSX parsing caps rows at 1,000,000 and checks a shared string's size
  before copying it.
- the temp snapshot's -wal/-shm files are removed instead of left in the
  data directory.

Remaining, by design: `changes` loads all rows of two releases from the
local database before filtering; its size is bounded by the snapshot.

CLI goldens unchanged. Live A/B vs 0.2.4: 16 passed; discover hits the
IDX 403 on both builds (exit 4 vs 1, intended).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:39:09 +00:00
hermes
1e551385d0 fix: close the remaining review findings (cross-process lock, partials)
Second Codex (gpt-6.1-sol, xhigh) pass over 60634d3, each item verified
before fixing:

- new: the sync recheck hashed an existing empty database before schema
  setup, so the first sync falsely reported an external change. Hash
  after setup; a published snapshot already has the schema and keeps
  its bytes.
- sync, import, and entity edits now hold a cross-process lock
  (<db>.lock, std File::lock), so a sync cannot replace the database
  under an import running in another process. Import validates and
  downloads before taking the lock, keeping its error order. MSRV
  1.85 -> 1.89 for File::lock.
- the row-count rebuild branch also never downgrades history.
- cache reads no longer delete corrupt entries (a concurrent writer may
  have just replaced them); the next put overwrites atomically.
- idx-mcp validates the changes limit before querying and runs each
  requested section or analysis once.
- XLSX parsing bounds total cell text, so one large shared string
  referenced from many cells cannot exhaust memory.
- PDF/archive percentages must be 0-100%; HHI uses saturating math.

CLI goldens unchanged. Live A/B vs 0.2.4: 16 passed, discover hits the
IDX 403 on both builds (exit 4 vs 1, intended).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 22:20:57 +00:00
hermes
60634d3aef fix: address final review (path traversal, sync races, MCP limits)
Findings from a Codex (gpt-6.1-sol, xhigh) review of v0.2.4..HEAD, each
verified in the code before fixing:

- high: ticker symbols reached cache file paths unchecked, so a symbol
  like `../../REPORT` read (and deleted, if unparseable) .json files
  outside the cache, reachable through MCP get_quotes. resolve_symbol now
  accepts only ticker characters, and cache keys are encoded as single
  safe path components.
- high: sync decided before downloading and replaced the database
  without rechecking; an import that landed meanwhile was erased. It now
  rechecks the local state before installing, and idx-mcp runs
  ownership_sync exclusively with ownership reads.
- replaces_sqlite_sha256 never downgrades history: the client requires
  the rebuild to keep at least the local release count, and the
  publisher lists a replaced build only then.
- cache writes are atomic (temp file + rename), so concurrent readers
  never see, and delete, a partial entry.
- idx-mcp caps symbols (20, deduplicated) and limits, and runs at most 4
  tool calls at once.
- each Yahoo client gets its own cookie jar, paired with its crumb.
- ZIP entries (XLSX, KSEI archives) are read with a 256 MiB bound;
  non-ASCII archive dates and huge percentages are parse errors instead
  of panics or overflow.
- agent-smoke --keep deletes every copied agent config, not only auth
  files.

CLI goldens unchanged; the MCP tool golden changes only to state the
new caps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:58:37 +00:00
hermes
9d8914b2c9 test(mcp): agent smoke test across five coding agents, tool snapshot
- tests/agents/agent-smoke.sh runs tests/agents/cases.json through
  Claude Code, Codex, pi, Hermes Agent, and opencode headless against a
  local idx-mcp build, each with an isolated config, and checks the
  tools called, their key arguments, and (mock data) facts in the
  answer. Opt-in; never part of the gate.
- crates/idx-mcp/tests/golden/tools.json pins tool names, descriptions,
  and input schemas, so changes to what agents see are reviewed.
- nix develop .#agents adds Claude Code, Codex, and opencode from
  nixpkgs after the host PATH.

First run: 30/30 (5 agents x 6 cases, mock data).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:59:56 +00:00
hermes
58d421028f fix: address release review (WAL-aware sync hash, empty MCP inputs)
- Sync hashed only the main database file, but local edits can sit in
  the -wal sidecar, so an edited database could match an earlier build
  and be replaced. Checkpoint the WAL before hashing; if changes remain,
  never take the rebuilt-snapshot path.
- MCP tools rejected nothing for empty symbols/sections/kinds and
  returned an empty success; they now fail with INVALIDINPUT, as the CLI
  requires input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:44:24 +00:00
hermes
cda12fe2db feat(mcp): add idx-mcp, a stdio MCP server over idx-core
Phase 4 of docs/DESIGN.md. Eleven grouped tools (quotes, history, screen,
company, analysis, compare, shareholders, investor holdings, search,
ownership changes, status); ownership_sync is listed only with
--allow-sync. Live results keep the Fetched envelope, errors use the CLI's
{error, code, message} body, and investor tools on an empty database
point at the sync path.

idx-core gains Period/Interval FromStr, Investor::status, a ticker
filter on changes, and Display for ErrorCode (now used by the CLI too).
CLI output and goldens are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:19:04 +00:00