Commit graph

61 commits

Author SHA1 Message Date
hermes
ebdba13a41 chore: release 0.3.3
Patch release for the full-repo audit of 0.3.2: ownership query
correctness (changes, alias search, graph, investor lookup), snapshot
sync safety, request and lock time limits, the CLI output contract and
terminal-safe output, fundamental rating logic, publisher and installer
robustness, and zip off a yanked release (#51).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-18 02:16:18 +00:00
hermes
ec2e870c6a fix(cli): output contract, terminal-safe text, char-safe truncation, cache symlinks
From the full-repo Codex audit:
- Provider and KSEI text reached the terminal raw, so an escape sequence
  in a news title or insight could clear or rewrite the screen. Every
  table cell, insight line, and graph label now has control characters
  and bidirectional overrides replaced.
- Table rendering sliced strings by byte (a description at 200, a URL at
  72, an earnings period), panicking when a multi-byte character such as
  "’" or "é" fell on the cut. Truncate by character.
- In JSON mode, argument errors were clap's plain text and warnings were
  plain lines ahead of the JSON error. Argument errors are now the usual
  JSON error (INVALIDINPUT, exit 2) when JSON output is selected, and
  diagnostics are JSON lines; --help and --version are unchanged.
- `config` commands ignored the output format; they follow it like every
  other command (JSON when piped or with -o json, text on a terminal).
- Ownership argument mistakes (--limit 0, --format, missing or
  conflicting import sources, --family, an empty graph root) returned
  PARSEERROR / exit 1; they are INVALIDINPUT / exit 2 as documented.
- `cache clear` followed symlinked directories and deleted files outside
  the cache; it now removes links without following them.

Output change, intentional: goldens regenerated for the JSON argument
error and the five config commands, which run with stdout piped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-17 13:24:28 +00:00
hermes
523e1409d3 fix(sync): atomic install, stored-row validation, no local artifacts from remote manifests
From the full-repo Codex audit:
- Install moved the database to a backup, then renamed the snapshot in;
  a crash between the two left no database, and the next sync deleted the
  backup first. Replace it with a single rename (old file or new, never
  neither), and restore a backup an older version left behind.
- Validation trusted the release's row_count metadata, so a snapshot with
  an empty or missing holdings table passed and replaced good data. Also
  count the rows stored for the latest release.
- A manifest fetched over HTTP(S) could name a local download_url such as
  /dev/zero, read without a size limit. Remote manifests must give an
  HTTP(S) artifact; local reads take regular files only, within the size
  limit, and manifests are capped at 1 MiB.
- A same-date build with a different row count replaced the local file
  even when it did not list it in replaces_sqlite_sha256, so a replayed
  older build could undo a corrected one. Same-date rebuilds now go only
  through replaces_sqlite_sha256, which the publisher fills.

Verified against the published snapshot: fresh install, no-op re-sync,
and forced reinstall all succeed with no leftover files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-17 12:23:58 +00:00
hermes
4357a2ba73 docs: release notes format in the release order
Notes in the style of other open-source releases: an upgrade line when
urgent, New Features / Bug Fixes / Improvements with one user-facing
sentence and PR numbers per item, and a Full Changelog compare link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-16 10:01:47 +00:00
hermes
c6ec583edc chore: release 0.3.2
Patch release for the npm launcher fix (#41, #42): `idx` and `idx-mcp`
now install their binary on first run when the package manager skipped
postinstall, as npm 12 does under `npx` with its `allow-scripts` policy
(and pnpm 10+ and bun by default).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-16 09:54:31 +00:00
hermes
2d0bceade2 docs: news sources research: one publisher, Google News, greencloud test
- Why not a browser per user: a full engine per install and fetch, each
  user facing the challenge, IDX seeing thousands of scrapers; one
  scheduled publisher serving static files instead (diagram). Hosting
  left open (GitHub release, Cloudflare Pages/R2, a Worker).
- The IDX challenge is JavaScript-based: curl fails from a second
  datacenter (greencloud-vps, Singapore) too, so IP is not what curl
  lacks.
- Other sources measured: Google News RSS covers companies MSN misses
  (BREN 75/100 titles naming the ticker in 7 days), plain HTTP; Yahoo
  search answers 429.

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
d2ad5c142b docs: research IDX news and disclosures, propose a published feed
MSN has no company news for many tickers; IDX's disclosures cover every
issuer. idx.co.id is behind a Cloudflare challenge that plain curl and
curl-impersonate fail from the maintainer host, while the local Camofox
browser gets through. Record the working endpoints (per-issuer and
all-issuer disclosures, exchange news), their shapes and quirks, the
options, and a proposed design: a maintainer publisher writes static JSON
to a GitHub release, as the ownership snapshot does, and idx-core merges
it into news.

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
d2cd7115f1 fix(npm): no cache fallback without an absolute home directory
The release-gate Codex review found the relative-path fix incomplete:
with XDG_CACHE_HOME unset and HOME empty or relative, the fallback was
`.cache/...` under the current directory, so a checkout could plant the
binary the launcher runs. Use the home cache only when the home path is
absolute; otherwise the package's bin/ is the only location. npm smoke
plants such a binary and checks it is never run (it was, before this).

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
3041606916 fix(npm): absolute cache root, per-arch cache dir, SIGINT during install
From a second Codex review (gpt-6.1-sol, xhigh):
- A relative XDG_CACHE_HOME (invalid per the XDG spec) made the launcher
  look for, and run, a binary under the current directory, which an
  untrusted checkout could supply. Use it only when absolute.
- The cache dir now includes <platform>-<arch>, so a home directory shared
  across machines cannot hand an arm64 host an x64 binary.
- SIGINT sent to the launcher's PID alone during the first-run install is
  forwarded to the installer (it was ignored); the launcher exits 130.
- npm smoke runs the first-run install with an empty user cache and checks
  the package binary was created, so a cached binary cannot pass for it.

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
907223a838 fix(npm): install to a user cache when the package is read-only; signal-safe first run
- When the package's bin/ is not writable (a root-owned `sudo npm i -g`
  whose postinstall npm 12 skipped), install the binary to a per-user
  cache ($XDG_CACHE_HOME, ~/.cache, ~/Library/Caches on macOS, under
  <package>/<version>/); the launcher looks in both. Previously the first
  run failed with EACCES and advice to rerun npm install.
- Run the first-run installer asynchronously with SIGTERM/SIGHUP
  forwarded, so a supervisor stopping the launcher stops the download;
  the installer removes its partial file and the launcher exits by the
  same signal.
- Verify an IDX_BINARY_URL local file's checksum on the copy, not the
  source, so the installed bytes are the checked ones.
- npm smoke covers the read-only fallback.

The last three come from a Codex review.

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
1049ff3155 test(agents): run opencode 2 as well as opencode 1
opencode 2 (@opencode/cli) moved MCP servers to `mcp.servers`, turns
Code Mode on by default, replaced `permission` with ordered
`permissions` rules, and dropped `run --pure` for `--standalone` (runs
otherwise go through a shared background service). Pick the config and
flag from `opencode --version`.

Its `execute` tool stays allowed: with it denied, opencode 2.0.30 never
offers the idx tools to the model in `opencode run`, though the server
connects before the first step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-16 07:40:04 +00:00
hermes
855ec0cdc3 test(agents): opencode answers only through the idx tools
opencode kept its built-in tools, so it could route around the server
under test: on published 0.3.1 it answered a quote from web search, and
on the live `changes` case it queried the ownership SQLite file with
bash and timed out. Deny every tool but `idx_*`, as Claude, pi, and
Hermes already are.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-16 06:50:29 +00:00
hermes
7ebf63ed42 test(agents): a live variant of the ownership-changes case
The `changes` case names the fixture releases (2026-01-30, 2026-02-27),
which the live snapshot does not have, so under --live/--npx it failed
agents that correctly declined to answer about missing releases and
passed ones that called the tool anyway. A case's `live` fields now
replace the mock ones; this one asks about the two most recent releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
2026-10-16 06:09:00 +00:00
hermes
ca218ef2ea test(agents): run agents through npx -y idx-mcp, as users configure it
- agent-smoke `--npx <spec>` registers `npx -y <spec>` (the published package
  or an `npm pack` tarball) with a fresh npm cache, instead of the local build,
  so each agent's own npx handling and a cold install are tested.
- `nix develop .#agents-fhs` (Linux) runs agents in an FHS sandbox: Hermes
  replaces a bare `npx` with its own generic-Linux Node, which NixOS cannot
  load. `agent-fhs <cmd>` runs anything inside it.
- Hermes: only the idx toolset (with its terminal it repaired a broken
  server mid-run and passed), the Node toolchain fetched before the first
  case (`hermes -z` waits ~15s for MCP tools), JSON config.yaml (as profiles
  write it), linked plugins for plugin model providers, and a copy of its
  mcp-stderr.log per run.
- A failed run names its cause inline, since output is deleted without --keep.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WB13omP6h7YeyJx7GNtUnF
2026-10-16 04:40:40 +00:00
hermes
bdbcf62a7b fix(npm): install the binary on first run when postinstall was skipped
npm 12 blocks install scripts of unapproved packages under `npx` and
`npm install -g` (its allow-scripts policy). The postinstall download never
ran, so `npx -y idx-mcp` exited with "installed binary not found" and MCP
clients saw only "Connection closed". Hermes Agent hits this by default: it
runs a bare `npx` with its own npm 12.

The launcher now runs the installer itself when the binary is missing,
logging to stderr so the idx-mcp stdout channel stays clean. This also covers
pnpm 10+ and bun, which skip dependency scripts by default.

npm-smoke installs idx-mcp with --ignore-scripts and checks that the first
run still starts cleanly.

Fixes #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WB13omP6h7YeyJx7GNtUnF
2026-10-16 04:40:40 +00:00
hermes
aac30335fc chore: release 0.3.1
Patch release for the fixes from the Codex review of 0.3.0: ticker
validation and encoded cache file names (path traversal via a ticker),
cross-process ownership DB locking, history-preserving snapshot sync,
and MCP input limits. MSRV is now 1.89.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 03:14:45 +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
5a3ce462cc fix(agents): address agent smoke review
- Require bash 4.4+ up front, and match answer facts with jq regexes
  (Oniguruma, (?i) everywhere) instead of GNU-only grep -P.
- Unparseable agent output fails that run instead of aborting the
  matrix; check_run now also reports unparseable tool calls, which
  previously passed the tool check silently.
- Copy the Hermes model block with awk up to the next top-level key, so
  a final or differently ordered block is handled.
- --keep keeps run output but deletes the copied agent credentials.
- docs: full path to the tool golden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:16:06 +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
8e3323e4f7 chore: release 0.3.0 with idx-mcp
Bump the workspace and npm packages to 0.3.0. The release workflow
builds and attaches idx-mcp binaries next to idx, the npm launcher and
installer pick their binary from package.json so npm/idx-mcp reuses
them, and the npm smoke test covers both packages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:01:29 +00:00
hermes
ec17a1bd05 fix(ownership): update syncs when a same-date snapshot is rebuilt
A snapshot republished for the same as-of date (the TRUE ticker fix)
kept the date, latest release, and release count, so sync saw nothing
to do and existing installs kept the bad data without --force.

The manifest gains an optional snapshot.replaces_sqlite_sha256 listing
earlier builds for that date. Queries never write to a synced file, so
a local database byte-identical to a listed build is replaced; one that
matches nothing (local imports or edits) still needs --force. The
publisher fills the list on a same-date rebuild. Older clients ignore
the field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:52:13 +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
hermes
1d8b615857 refactor(core): move ownership queries, import, and sync into idx-core
Phase 3c of docs/DESIGN.md. `ownership` handlers are now argument
parsing, one idx-core call, and rendering.

- investor.rs: Investor (also Idx::investor()) with releases, ticker,
  entity, search, cross_holders, concentration, flow, changes, graph,
  unresolved, map_alias, merge_entities, discover, sync, and import.
  Import orchestration (input resolution, download, SHA-256 dedup,
  PDF/XLSX/archive parsing, entity resolution, release write) moves
  from the CLI unchanged.
- Typed inputs: ImportSource, HolderSource, ConcentrationRank, and
  parse_report_family, with the same error messages.
- CoreConfig gains an ownership_db override; db::open_db_at opens an
  explicit path. idx-cli no longer depends on rusqlite, sha2, or ureq
  at runtime.
- Behavior note: an invalid `ticker --source` or `concentration --by`
  is now rejected before the database file is opened (and created);
  the error and exit code are unchanged.
- Fix: building idx-cli without the ownership feature no longer warns
  about the ownership-only `blue` style.

All 143 goldens unchanged; 170 core + 7 CLI unit tests and 62 CLI tests
pass; clippy clean for default, release, testing, and no-default
feature sets. Live A/B against main: 16/16 stocks identical; ownership
discover got IDX 403 on both (exit 4 vs 1 is the phase 1 change).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:52:17 +00:00
hermes
86cb1b4b7c refactor(core): move stocks fetching, caching, and analysis into idx-core
Phase 3b of docs/DESIGN.md. `stocks` handlers are now argument parsing,
one idx-core call, and rendering.

- Idx (client.rs) is the entry point: Idx::new(CoreConfig) with
  market(), financial(), and analysis(). Live lookups return
  Fetched<T> { data, source, fetched_at, stale } and take a CachePolicy
  (Default | Offline | Refresh, from the --offline/--no-cache flags).
- One cached_fetch replaces the CLI's three cache flows; they differed
  only in whether a fresh entry is read via get or get_offline when
  offline, which yields the same data and diagnostics. Report-building
  errors (technical) still skip the stale-cache fallback.
- market.rs: quotes, history (history_source is resolved first so the
  CLI's fallback note keeps its position), and a typed screener
  (ScreenFilter/ScreenRegion with the same error messages).
- financial.rs: profile, statements, earnings, sentiment, insights,
  news, with StatementFilter/EarningsFilter.
- analysis: TechnicalReport moves to analysis::report; Analysis offers
  technical, growth, valuation, risk, fundamental, and compare.
- CoreConfig::load() owns provider/exchange/TTL settings from
  config.toml and IDX_*; the CLI's IdxConfig adds output and color.
  With two different invalid settings at once, the reported one may
  differ from before; every single-error message is unchanged.
- Cache gains fresh/any/offline accessors returning fetch metadata.

Unit tests moved with their code (core 157 -> 168, CLI 17 -> 7). All
143 goldens unchanged; live A/B against main: 16/16 stocks identical.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:41:31 +00:00
hermes
e78d9fcecd refactor(core): compile the mock provider only with a testing feature
Phase 3a of docs/DESIGN.md. MockProvider moves to api/mock.rs, and it,
the embedded MSN fixture bodies, and the IDX_MOCK_* hooks compile only
with idx-core's new `testing` feature. idx-cli enables it as a
dev-dependency, so `cargo test` keeps the fixture-backed provider while
release binaries no longer embed fixture data or honor
IDX_USE_MOCK_PROVIDER. Parser helpers used only by the mock are gated
the same way.

The packaged-cli flake check no longer relies on the mock; it checks
an offline cache miss (JSON CACHEMISS, exit 3) instead.

Goldens unchanged; clippy is clean with and without `testing`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:31:12 +00:00
hermes
15947de51a refactor: split into idx-core library and idx-cli binary workspace
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>
2026-10-14 09:13:46 +00:00
hermes
22b6dabaeb feat(cli): agent-native output, stdin symbols, and exit-code classes
Phase 1 of docs/DESIGN.md:

- Output format resolves -o > IDX_OUTPUT > general.output > auto; auto
  is a table on a terminal and JSON when stdout is piped or captured.
  `config init` now writes output = "auto".
- JSON is pretty only on a terminal and compact otherwise. New `-o
  jsonl` prints one array item per line with the same key order.
- No ANSI color when stdout is not a terminal (direct owo-colors calls
  now go through output::style).
- `stocks quote -` and `stocks compare -` read symbols from stdin.
- Exit codes by class: 2 invalid input/config, 3 not found or cache
  miss, 4 upstream unavailable, 5 unsupported, 1 other.

Golden changes are limited to those documented kinds and were verified
mechanically: JSON goldens are semantically identical, tables differ
only by removed ANSI codes and the column padding comfy-table had
reserved for them (cell contents identical), exit codes match each
error's class, and help/completions only gain the new --output values.
17 new golden cases cover auto, jsonl, stdin, and the new errors.

tests/cli.rs now pins IDX_OUTPUT=table in its harness (its tests assert
table text and run piped) and parses discover JSON instead of matching
pretty-printed whitespace.

Live A/B against main: 16/16 stocks cases identical; ownership discover
got IDX 403 on both binaries, now exiting 4 instead of 1 as intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:52:13 +00:00
hermes
fe627496dd test: add golden output parity gate and live A/B compare
Phase 0 of docs/DESIGN.md, before any refactor code:

- tests/golden.rs runs every case in tests/golden/cases.toml (126
  outputs across help, completions, config, cache, all stocks commands
  on MSN and Yahoo mocks, error paths, and ownership queries on a seeded
  fixture DB) and compares exit code, stdout, and stderr byte for byte.
  Regenerate only with UPDATE_GOLDEN=1 for a reviewed output change.
- scripts/live-smoke.sh --compare <old-idx> runs each live case with
  both binaries and fails on differing exit codes, added/missing/retyped
  JSON keys, or changed values for slow-moving data.
- Exclude golden files from the whitespace/EOF pre-commit fixers, which
  would otherwise rewrite pinned help output.

Both gates were fault-injected: a one-word table change fails exactly
the three history goldens, and a binary dropping profile.long_name fails
only the profile live comparison.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:33:25 +00:00
hermes
535ef64a0c docs: add idx-core / idx-mcp system design
Accepted design for extracting idx-core as the shared data layer, the
agent-native CLI output contract, idx-mcp tools, packaging, and the
parity gates (golden output tests plus live A/B check) that the
refactor must pass before merging to main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:18:17 +00:00
hermes
86c108f6eb test: replace mock smoke matrix with a live-only drift check
The 43-case mock smoke duplicated tests/cli.rs, which already runs the
real binary against fixture providers. Replace the flake's smoke-mock
check with a small packaged-cli check (version JSON, bash completions,
one mock quote), and cut scripts/live-smoke.sh from 672 to ~170 lines:
it now only runs shipped commands against live MSN/Yahoo/IDX and
validates output shape. Rewrite docs/SMOKE.md around the Nix gate and
the drift check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:45:51 +00:00
hermes
1631058b8c docs: prune stale planning docs and lean out agent guide
- Remove FEATURE_SPEC.md, TODO.md, and docs/AGENT_INTEGRATION.md; their
  core gaps have shipped. Open work moves to a short ROADMAP.md.
- Remove the CLAUDE.md symlink and rewrite AGENTS.md as a quick guide:
  navigation, rules, and the nix flake check gate.
- Merge OWNERSHIP_SELF_HOSTED.md into OWNERSHIP_PUBLISH.md and drop
  private host integration notes.
- ARCHITECTURE: add MSN endpoints, clarify Bing ingest is unsupported.
- OWNERSHIP_SYNC: note source.pdf_url also carries XLSX URLs.
- systemd example: --history 2 so snapshots stay under the 10 MiB
  client cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:39:21 +00:00
hermes
cd410708c0 docs(testing): document Nix gates and behavioral coverage
Describe canonical local and CI verification, cache reuse, output assertions, workflow boundaries, and observed regression results without treating test counts as coverage targets.
2026-10-03 16:23:16 +00:00
hermes
b4dcfceef6 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.
2026-10-03 16:21:35 +00:00
hermes
301e52ca83 fix: complete screener candidates and clean Nix merge artifacts 2026-10-02 16:51:14 +00:00
793d765e5a docs: add agent integration roadmap 2026-10-01 17:21:13 +07:00
hermes
3299093e18 docs: refresh README for v0.2.4
- Install: add npm/npx (with the pnpm/bun opt-in), platform coverage,
  pin example v0.2.4; Cargo builds from source.
- Runtime helpers as a table, corrected: curl-impersonate is needed for
  IDX ownership discover/import and Yahoo fundamentals
  (IDX_PROVIDER=yahoo), not for stocks history; mutool only for legacy
  PDF reports. Everyday use needs neither.
- Quick start and Features now cover ownership (sync, ticker, entity,
  search, changes, concentration, cross-holders, graph; XLSX source on
  the IDX Data Kepemilikan Saham page) and the full stocks command set
  and providers. Every example was run against v0.2.4.
- Documentation index; script test suites.
- docs: example versions -> v0.2.4; npm publish needs 2FA and a fresh
  version number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-25 23:39:25 +00:00
hermes
fea1a0e906 fix(ownership): bound snapshot download by manifest size; clean refused builds
Addresses review on #31:
- the snapshot download limit is the manifest's size_bytes (+1, since
  ureq fails once a body reaches its limit), capped at 1 GiB; a body
  larger than advertised fails during download. New CLI test; the
  >10 MiB test also caught the off-by-one.
- a refused (oversized) standalone build removes its sqlite and
  manifest from --output-dir.
- docs: downloads are buffered, not streamed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-25 22:31:40 +00:00
hermes
de58d04da9 fix(ownership): sync snapshots over 10 MiB; refuse oversized publishes
On 2026-09-25 a snapshot built with --history 5 (15.3 MB) was
published, and `idx ownership sync` failed for every client:
"the response body is larger than request limit: 10485760" (ureq's
default body cap). It was republished by hand with --history 2 (8.2 MB).

- snapshot download: raise the ureq body limit to 1 GiB; the file is
  still verified against the manifest's size and SHA-256. The PDF/XLSX
  report downloads get a 256 MiB limit for the same reason.
- CLI test: sync a >10 MiB snapshot over HTTP (fails without the fix
  with the exact production error).
- build-latest-ownership-snapshot.sh: --max-snapshot-bytes (default
  10485760, the limit of released clients <= v0.2.3); an oversized
  snapshot fails the build, so the published one stays untouched.
- publish-ownership-snapshot.sh passes --max-snapshot-bytes through;
  publisher tests cover the failure and that the release is unchanged.
- docs: size table and guidance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-25 22:22:48 +00:00
hermes
9f4ed5b4a5 fix(ownership): verify snapshot assets before publishing manifests 2026-09-25 10:37:32 +00:00
hermes
6628d3b2e9 fix(ownership): make publisher RESULT contract hold on every path
Addresses review on #29:
- drop errtrace: a failure inside $(...) printed two RESULT lines
- explicit failures go through fail(), which always prints
  RESULT: FAILED stage=...; idx check gets its own `preflight` stage;
  the never-failing published-manifest read is no longer a stage
- validate the built latest_as_of_date (`// empty` + date regex) so a
  corrupt manifest cannot compare as "newer"
- freshness: validate --now (exit 2) and latest_as_of_date (exit 3)
  before calling date
- flake: package an explicit script list instead of scripts/*.sh
- docs: GH_TOKEN prerequisite for the packaged publisher

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 21:50:51 +00:00
hermes
1f6f0ee02e feat(ownership): idempotent, packaged snapshot publisher
The self-hosted publish timer has not produced an upload since it was
enabled (manifest last updated 2026-07-14, a manual run), and failures
were invisible. Make the job safe to run daily and easy to deploy:

- publish-ownership-snapshot.sh: skip the upload when the published
  manifest already has the latest as-of date (pre-check via discover
  for XLSX sources, post-build check for legacy PDF); --force to
  override. Always ends with one line: RESULT: published|up-to-date
  <as-of> or RESULT: FAILED stage=<stage> (exit N). gh release create
  no longer requires a git checkout.
- check-ownership-snapshot-freshness.sh: exit 1 when the published
  latest_as_of_date is older than --max-age-days (default 40).
- flake: packages.ownership-publisher bundles the scripts with a
  pinned idx (curl-impersonate + mupdf) and jq/gh/sqlite/curl as
  bin/idx-ownership-publish and bin/idx-ownership-freshness, so hosts
  need no checkout, nix develop or cargo build at run time. Built in CI.
- contrib/systemd + docs: daily 06:00 UTC timer, run semantics table,
  freshness alarm, packaged usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 21:40:02 +00:00
hermes
c866dd7a65 fix(ownership): harden --history parsing per review
Addresses review on #27: canonicalize --history ("08" no longer hits
bash octal parsing), run the history jq in a plain command
substitution so a jq failure aborts under set -e, and restore the
schedule-guidance paragraph in OWNERSHIP_SELF_HOSTED.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 21:12:34 +00:00
hermes
c2d2734739 feat(ownership): build snapshots from XLSX with monthly history
- build-latest-ownership-snapshot.sh: discover with a wider limit and
  add --history <n> to import the n previous supported above-1% XLSX
  months (one per as-of date, oldest first) before the latest, so
  `idx ownership changes` works straight from a synced snapshot.
- publish-ownership-snapshot.sh: pass --history through (for the
  clan-private systemd timer via extraArgs).
- publish-ownership-snapshot.yml: `history` input (default 5).
- Docs: publish/self-hosted flow now describes the XLSX source,
  observed upload days (2nd-3rd of the month), and --history.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 21:12:05 +00:00
hermes
29e416109c feat(ownership): discover above-1% XLSX on the IDX data page
IDX stopped posting the monthly above-1% list as an announcement after
the 2026-05-29 report; June/July/August are only on the Data
Kepemilikan Saham page. `ownership discover` could not see them, and
IDX's Cloudflare now rejects curl_chrome142 on that page and on the
/Media/ XLSX files.

- remote: parse the page's Nuxt payload (Description/Prospectus pairs,
  / escapes), classify above-1% XLSX as supported and above-5% /
  investor-type workbooks as unsupported, derive as_of_date from the
  Indonesian description. Keep all above-1% entries, only the newest
  of the other series. Merge with the announcement results.
- discover JSON gains `format` (xlsx|pdf) and `as_of_date`; page
  entries use the as-of date as publish_date (the page shows none).
- import --url accepts .xlsx and downloads via the XLSX validator.
- curl_impersonate: run_owned_with_fallback tries several browser
  profiles until the body validates (JSON / page payload / %PDF / PK);
  IDX_CURL_IMPERSONATE_BIN still pins one.
- Tests: page fixture parsing, date parsing, payload validation, CLI
  discover via a local page server; existing discover tests now point
  IDX_OWNERSHIP_DATA_PAGE_URL at a closed port so no test hits IDX.
- Docs: ARCHITECTURE discovery section, AGENTS ownership inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 21:09:05 +00:00
hermes
2c85acb829 feat(ownership): import IDX above-1% XLSX holder register
Since June 2026 IDX publishes the monthly "Pemegang Saham di Atas 1%"
list as an XLSX workbook on the Data Kepemilikan Saham page instead of
a PDF announcement. Add a parser for it and accept `.xlsx` in
`ownership import --file`.

- src/ownership/xlsx.rs: zip + quick-xml reader (shared strings incl.
  rich text, sheet cells), exact 12-column header match so the daily
  above-5% and investor-type/classification workbooks are rejected,
  Excel serial dates, numeric shares, percent -> bps with float-noise
  rounding. Builds KseiHoldingDraft directly and reuses the entity
  normalizers, so investor types stay uppercase like the PDF path.
- Fixtures: trimmed excerpts of the real 2026-08-31 above-1% and
  2026-09-23 above-5% workbooks.
- Unit + CLI tests: import, ticker, releases, above-5% rejection.
- Docs: ARCHITECTURE parser pipeline, AGENTS ownership inputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 20:48:07 +00:00
hermes
a142a3718f feat: add curl | sh install script
- install.sh: POSIX sh installer that detects OS/arch (incl. Rosetta),
  resolves the newest vX.Y.Z release via the GitHub API (skipping
  ownership-snapshot-current), verifies SHA256SUMS, sanity-runs the
  binary, and installs atomically into ~/.local/bin without sudo.
  Supports --version/--dir and IDX_* env overrides; prints a PATH
  hint, a NixOS flake suggestion, and helper-tool install hints
  instead of calling package managers.
- scripts/install-sh-test.sh: fixture tests against a local fake
  release server (latest, pinned, piped, checksum mismatch, missing
  release, unsupported OS, PATH hint, reinstall).
- ci.yml: install-script job (ShellCheck, sh + dash on Linux, sh on
  macOS).
- release.yml: attach install.sh to each release.
- README + docs/INSTALL.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 19:29:52 +00:00
hermes
4f8c90a108 fix: harden npm distribution release and installer
- release.yml: build static musl Linux binaries (no host glibc
  requirement), add darwin-x64, publish SHA256SUMS, allow
  workflow_dispatch for an existing tag (v0.2.3 has no binaries yet),
  and run build-only on PRs that touch the workflow or installer
- install.js: verify downloads against the release SHA256SUMS
  (IDX_BINARY_SHA256 for overrides), add a 60s request timeout, use
  stream.pipeline so truncated downloads fail, and skip the download
  inside the source checkout
- idx.js: forward SIGTERM/SIGHUP to the binary and keep the signal
  exit status
- package.json: drop the prepare script (no Rust toolchain needed to
  pack/publish)
- npm-smoke.sh: read the tarball name from npm pack, use grep over rg
- .gitignore: ignore /bin/ and *.tgz
- docs: musl targets, checksums, pnpm/bun opt-in, manual release flow

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017a5Cv5Z1NzdYKgKnTiAF4E
2026-09-24 18:57:53 +00:00
hermes
221dc01397 feat: add npm distribution wrapper for idx-cli
Add an npm wrapper (package.json + postinstall downloader) so the Rust
binary can be installed via npm/npx. Include a local smoke test, a GitHub
Release cross-compile workflow, and nodejs in the dev shell.

- package.json: idx-cli v0.2.3, bin idx, engines node>=16
- scripts/install.js: dependency-free postinstall that downloads the
  platform-specific GitHub Release asset (or IDX_BINARY_URL override)
- scripts/idx.js: binary launcher preserving exit codes/signals
- scripts/npm-smoke.sh: npm pack -> install tarball -> run idx --help
  and idx config --help, locale, no publish required
- .github/workflows/release.yml: cross-compile linux-x64/arm64 and
  darwin-arm64 releases and attach binaries to the version tag
- flake.nix: add nodejs_22 to the dev shell
- docs/NPM_DISTRIBUTION.md: usage, local test, and release flow
2026-08-18 11:14:55 +00:00
a2d15d2125 feat: support explicit MSN chart history 2026-04-15 13:48:27 +07:00