`release 00.5.0` passed the newer-version guard (its key equals 0.5.0's)
and the exact-string duplicate check, then wrote `## [00.5.0]` and deleted
the entries before `check` rejected the file (Pullfrog on #82).
- Versions take no leading zeros, in `release`, `notes` and the headings
`check` accepts.
- `release` checks the file it is about to write and changes nothing
unless it passes, so any other bad result also leaves CHANGELOG.md and
the entries as they were.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
The read-path test listed a hand-written movements path; it now also builds
paths with movement::request_path, so the test fails offline if the
allowlist and the builder drift apart (Pullfrog on #80).
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
Open offers are listed by the day they opened, so "from 2026-11-03 to
2026-11-12" sat above tenders dated in October (Pullfrog on #78). The title
adds "with offers opened earlier and still open" when such rows are there.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
`release 0.4.9` on a changelog topped by 0.5.0 wrote the section above it
with a backwards compare link and deleted the entries; `check` caught the
file only afterwards (Pullfrog on #82). The version is now compared before
anything is written.
Also: the #78 entry names the calendar's open offers and the session API's
read-path allowlist, added there since.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
The tools reference page described results as {data, source, fetched_at,
stale} and failures as {error, code, message}, without the `warnings` this
PR adds (Pullfrog on #81). The generator's intro says so; page regenerated.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
- The session client's new read-path allowlist (#78) gains
`/insider/company/majorholder?`, the one endpoint movements reads; without
it every live movements query would fail before leaving the process. The
mock does not go through the client, so its tests could not show this.
- docs/ARCHITECTURE.md described one cache entry per page: a query's pages
are read and cached together, keyed by the page count, and a failed later
page returns the rows before it with `incomplete` (Pullfrog on #80).
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
From Pullfrog's review of #78:
- A dividend in another currency keeps 6 decimals (USD 0.0012 was rounded
to 0.0 in JSON and "USD 0" in the table); rupiah stays to the sen.
- `--kind stock_dividend` and `--kind reverse_split` work on the CLI too,
as they already did over MCP.
- The session client GETs only the prefixes in READ_PATHS, as
docs/STOCKBIT.md's rule 1 already said: any other path (orders, PIN,
withdrawals) fails before a request or token refresh leaves the process.
- `stocks calendar` and get_corporate_calendar also list rights trading,
tender offers and IPO offers still open in the range, so an offer that
began earlier shows while it can be acted on. A warrant's years-long
windows do not count. The 2026-11-03..12 golden gains DOOH, NAYZ and AUTO.
- `stocks calendar --help` names the ex-date fallback, and docs/STOCKBIT.md
shows the record and surface as shipped.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
A block (UPSTREAMBLOCKED) was treated as a passing failure, so idx's browser
re-checked the same cookie every 5 s until the 15-minute timeout, warning
"does not retry a block ... retrying". A block holds for every session from
this network: the wait now ends with it at once.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
Release notes were written by hand on GitHub at tag time, so the contents
of a stacked release had to be pieced together from every PR, and output
changes that scripts and agents rely on were easy to miss.
- CHANGELOG.md backfills every published release (0.2.0 to 0.4.0) from its
GitHub notes, verbatim.
- A PR adds changes/unreleased/<branch-name>.md instead of editing
CHANGELOG.md, so stacked PRs never conflict. changes/README.md gives the
sections, including Output Changes for JSON, MCP and error-code changes.
- scripts/changelog.sh: `check` validates entries and CHANGELOG.md,
`release X.Y.Z` folds the entries into a new section (ordered by PR), and
`notes X.Y.Z` prints a section as GitHub release notes.
- The release workflow sets the GitHub notes from the tag's section.
- New flake check `changelog` runs scripts/changelog-test.sh; both scripts
are shellchecked.
- Entries for 0.5.0: #73, #74, #75, #78, #79, #80, #81 and this PR.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
idx-core reports what it works around (history falling back to yahoo when
Stockbit refuses the session, a stale cache served after a failed fetch, a
movements query cut short) as warnings on stderr. An agent never reads
idx-mcp's stderr, so it saw `source: "yahoo"` or `stale: true` with no reason.
- Each tool call collects its warnings with runtime::on_warning and returns
them as `warnings` on the answer or the error, when there are any.
- The stale-cache warning names the failure behind it.
- The mock's session API 403 is the real session API's block message; the
public page's block names IDX_PROVIDER=msn as well as `--provider msn`.
Goldens: error-intraday-blocked (json, table) carry the reworded block hint.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
JSON `kind` now uses the `--kind` names, so an agent that copies a kind from
an answer cached or read before that change (`public_expose`,
`tender_offer`) would get INVALIDINPUT. Both are accepted, as hidden `--kind`
aliases and by the MCP tools.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
A refused cookie in idx's browser was only a stderr warning, which nobody
reads under idx-mcp: stockbit_status showed `waiting` until the timeout, and
the timeout message did not mention the refusal.
- runtime::on_warning sends a thread's warnings to a sink as well as
stderr, until its guard drops.
- The background sign-in puts its warnings in `sign_in.warning`, cleared
once it signs in.
- The errors that end a browser wait (timeout, window closed, browser
exited) name a refusal it hit.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
- Each page was cached on its own, so a multi-page answer could join
pages read at different times from a feed that gains rows at the top,
repeating some rows and missing others. A query's pages are now read
together and cached as one entry.
- A failed later page threw away the pages already read. They are now
returned with incomplete saying why, and not cached.
- The library did not cap pages at MAX_PAGES itself; it clamps now.
- The hint said to raise --pages even at 10; at 10 it says to narrow the
dates. -o jsonl, which streams rows only, gives the hint on stderr.
- A duplicate match arm in the CLI was dead; it is gone.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
- JSON named kinds by their Rust names (public_expose, tender_offer,
stock_dividend, reverse_split) while --kind and MCP take expose, tender,
stock-dividend, reverse-split, so an output kind could not be passed
back. JSON now uses the input names; old cache entries still read, and
--kind also accepts underscores.
- {"dividend": null, "today": "..."} failed to parse and aborted the
whole command; a null list beside scalars now reads as none.
- An extreme --from panicked on the default 30 days; it is an error now.
The 366-day limit let 367 days through; it counts both ends now.
- An entitlement without a cum date sat on its ex date under a note that
calls DATE the cum date. The table marks it (ex) and says what that
means; JSON leaves cum_date out, as before, and the docs say so.
- A dividend's currency was ignored; one not in IDR now carries currency
and the table shows it instead of Rp.
- The live check no longer fails a quiet month's empty calendar.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
Logged-in quotes passed Stockbit's session state through verbatim
(STATE_NAME_SESSION_2), while public-page quotes say open or close, so a
consumer checking for "open" broke after login. Quotes now use the two
words either way; the order book keeps the state verbatim.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
auto history picks Stockbit when a session file exists, so an expired
session failed history and technical with AUTHERROR. auto now retries on
Yahoo with a warning; an explicit --history-provider stockbit still fails.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
- login restored the previous session on any check error, even after the
check had rotated the pasted session: a timeout then left no working
session. It now keeps a rotated session unless Stockbit refused it.
- The browser sign-in marked a cookie refused on any error, so a 429 or a
timeout held a signed-in user until the wait ran out. Passing errors
are retried after a pause; only a refusal is final.
- The local page checked concurrent pastes on their own threads, so a
later one could overwrite the session the first had just rotated.
Pastes are now checked one at a time, and none after one is accepted.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
stocks intraday never set quote.behind_session, so on a holiday or when
Stockbit lags, a past session's chart read as current. Mark it as quotes
do and print the same note under the header.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
GitHub retires the macos-14 images on 2026-11-02, and its brownouts
already cancel the darwin release builds and the install-script job
(seen on #74). Checked on macOS 15.7 with Apple clang and Rust 1.93.1:
install-sh-test.sh passes with the system's bash and BSD tools, and
both darwin targets build with --locked and run --help (x64 under
Rosetta).
Claude-Session: https://claude.ai/code/session_01RqPPVaoFxLToGAXXBEoCtH
`idx ownership movements [SYMBOL]` and the MCP tool
`get_shareholder_movements` list reported changes in insiders' and major
holders' stakes, newest first, from Stockbit's
/insider/company/majorholder: IDX insider disclosures (any size, with the
trade price) and KSEI changes of holders at 5% and above (Stockbit's
estimated price). Stockbit filters by date range, holder id, action and
source; each page is 50 rows and one request, up to 10 pages, cached an
hour per page under every filter.
Rows keep exact signed share counts and the stake in basis points as
Stockbit rounds it; unknown action labels are kept as `other` rather than
dropped. JSON is {movements, more}; JSONL is one movement per line. The
command lives under `ownership` but needs no import, and `ownership`
commands now take --offline / --no-cache for it.
The per-symbol summary score is the next PR; docs/STOCKBIT.md records the
endpoint's parameters and action types as checked on 2026-11-03.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
IDX's above-1% register as of 30 Oct 2026 (peng-2026-10-00029) inserts a
CONTROLLER_OR_AFFILIATE column after INVESTOR_CLASSIFICATION. The header
check rejected any column it did not know, so `ownership import` failed
with PARSEERROR and `releases`/`ticker` had nothing to show.
Accept that one column by name and skip it; any other unknown column is
still rejected, so the above-5% and investor-type workbooks stay out.
Storing the flag is a follow-up (ROADMAP).
The fixture is an 11-row excerpt of the real October workbook (disclaimer
rows, header, BBCA and ZYRX). The full file imports 7,149 rows across 961
tickers; live ownership smoke: 4 passed, 0 failed, no 403.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
Run only when `idx stockbit status` reports a session. They keep the
user's real config directory, which holds the session: an isolated copy
would refresh on its own and spend the user's rotating refresh token.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
`idx stocks actions SYM [--kind ...]`: one stock's cash and stock
dividends, bonus shares, rights, warrants, splits and reverse splits,
meetings, public exposes and tender offers (`--kind ipo` adds its IPO),
newest first. `idx stocks calendar [--from] [--to] [--kind ...]`: the
market's actions dated in the range (default today and 30 days ahead),
oldest first. MCP: get_corporate_actions, get_corporate_calendar.
Stockbit's `/corpaction/{type}` serves one kind per request; each type has
its own field prefix and list key, so entitlements are read by field
suffix. An action's `date` is the cum date for entitlements, else the
event's first day. Dividends keep their decimals. One request per kind,
cached 6 hours. AUTHERROR without a session.
Fixtures are the 2026-11-03 recordings (market-wide ones trimmed to
October-December). Goldens added; help-stocks and completions-bash gain
the two subcommands, MCP tools.json the two tools (regenerated with
UPDATE_GOLDEN=1). The MCP docs counted 17 tools without stockbit_login;
now 20.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
docs/STOCKBIT.md: every market-data endpoint in Stockbit's web client,
probed with a session on 2026-11-03 (corporate actions, shareholder
movements, composition, market detector, analyst consensus, hourly charts,
market states), the read-only rules all session features follow, and the
design of corporate actions and shareholder movements. Linked from
AGENTS.md and ROADMAP.md.
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
Observed in session on 2026-11-03:
- The regular board trades as STATE_NAME_SESSION_2, not the assumed
STATE_NAME_MARKET_OPEN, so session quotes hid bid/offer all day. Any
numbered STATE_NAME_SESSION_<n> now counts as trading; the lunch break
is STATE_NAME_BREAK.
- In session the order book's fbuy/fsell/fnet were the previous session's
(equal to IPOT's 2026-11-02 net for BBCA, BBRI and TLKM) and
has_foreign_bs was false. Foreign figures are kept only when that flag
is true.
Fixtures for the break, the trading state and an in-session book; the
table names the session ("market open (session 2)").
Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
`stockbit::signin` is now the single sign-in flow. `idx stockbit login`
waits on it in the foreground; the `stockbit_login` MCP tool runs it on a
thread and `stockbit_status` reports `sign_in.state`, so an agent knows when
to stop polling. Auto uses a browser unless the shell is over SSH or has no
display, and falls back to the local page only when the browser cannot start.
The browser way opens Stockbit's sign-in page in a profile only idx uses and
reads the session cookie over DevTools. What a real sign-in showed:
- The cookie is host-only on stockbit.com, so deleting by the assumed
.stockbit.com removed nothing and left live tokens in the profile. It is
now deleted by its own domain and path, with every other tab closed first,
and read back to confirm.
- A signed-out page writes the cookie without tokens; it no longer counts.
- A profile belongs to one browser, so it records the executable that made it.
- On macOS the config directory depends on XDG_CONFIG_HOME, so a shell and an
app started from the Dock disagreed; both default locations are now checked.
- A thread does not outlive main, so idx-mcp closes a waiting browser when
stdin closes or on SIGTERM or Ctrl-C.
Progress goes to stderr, which keeps `-o json` to one document on stdout.
Google refused its own sign-in in this window on 2026-10-31, most likely over
the debugging flag; password sign-in works, and the docs and roadmap say so.
Claude-Session: https://claude.ai/code/session_01RqPPVaoFxLToGAXXBEoCtH
The login page listens on 127.0.0.1, so a browser on the Mac driving a VM
could never reach it. Over SSH, idx now picks port 7878, skips opening a
browser on the VM, and prints the exact ssh -L to run where the browser
is.