`idx stocks flow SYMBOL` shows what foreign investors bought and sold of a
stock, session by session, from IDX's own daily trading summary: foreign
buy, sell and net volume, net value at the session's average price, and
the share of value traded outside the regular board, with range totals.
Latest 10 sessions by default (--days, at most 60) or --from/--to. The MCP
server exposes it as get_foreign_flow.
- api/idx: pure parser for GetStockSummary (rows dated another session
are dropped) and a provider over curl-impersonate's profile fallback
with a 30 s limit; Cloudflare passes only some fingerprints.
- flow: each session is cached whole by date, so other stocks' flows for
the same sessions need no request. Weekends are never requested.
- Idx::idx_code is shared by broker and flow for the .JK-only check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
`idx stocks bandar SYMBOL...` reads the broker summary: for the top 1, 3,
5 and 10 brokers per side, buy value minus sell value in basis points of
value traded, with a signal from big accumulation to big distribution on
the headline tier (--top, default 5). Several symbols give a screen ranked
by that figure (at most 100, one request each), which pipes from
`stocks screen`. The MCP server exposes it as analyze_bandar (20 symbols).
- analysis/bandar.rs is pure and versioned (`top_concentration_v1`):
integer basis points, no figure missing counted as zero, buy/sell VWAP
from exact averages, and the brokers on both sides listed because their
gross figures overstate their direction.
- Thresholds (±5%/±15%) follow fzn0x/idx (MIT); docs call them heuristics.
- Invalid input fails a screen at once; other errors fail one symbol and
are warned about on stderr.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
With fd=F or fd=D, IPOT's footer covers only that side's buying: BBCA's
foreign/regular summary for 2026-10-16 gives T. Val 1.6 T while its top
seller alone sold 2.0 T. Passing it through as the range total gave
consumers a denominator smaller than one listed broker, so total_value,
total_lots and average_price are now null for an investor-filtered summary.
The page-to-summary step is a pure function, tested on both fixtures.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
Adds a BrokerSummaryProvider capability served by Indo Premier's public
broker summary page: the top 10 buyers and sellers of a stock by lots over
a date range, with totals. No login or key.
- api/ipot: pure parser (decimal scaling of `1.6 M`-style figures), a
client that retries the server's spurious 404s, and a fixture mock.
- broker: Broker::summary/latest over the cache (7 days for closed ranges,
5 minutes for today), range checks, and IDX member names bundled from
GetBrokerCodeList.
- foreign_net_value is null for filtered summaries: the source does not
apply the filters to it.
The endpoint's behavior was first mapped by fzn0x/idx (MIT).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
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
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
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
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
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
- 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
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
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
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
- 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
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
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
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
- 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
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
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>
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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
Describe canonical local and CI verification, cache reuse, output assertions, workflow boundaries, and observed regression results without treating test counts as coverage targets.
- 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
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
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
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
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
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
- 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
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
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