This document records the stable coding and validation rules for this repository.
Keep the CLI easy to extend without mixing command logic, TradingView internals, downstream workflow helpers, and JSON contract decisions.
Before adding a command, record why it belongs in the Rust CLI:
- which user, downstream, or operator workflow it unblocks
- whether it is old CLI migration parity, Rust-specific cleanup surface, or a new Rust-native capability
- what safety constraints apply
- what practical old CLI information must remain available
- how automated tests and live smoke will verify it
Do not implement a command only because it existed in the old JavaScript CLI. Newly discovered old commands are migration backlog unless a durable decision excludes them.
This project uses Rust 2024.
- Do not introduce
mod.rs. - Keep shared package metadata such as version, edition, license, and publish
policy in the workspace root
[workspace.package]table. - Keep dependency versions and internal crate paths in the workspace root
[workspace.dependencies]table. Member crates should normally useworkspace = truein their own[dependencies]or[dev-dependencies]entries and add only crate-specific feature selections there. - Prefer facade files with same-named submodule directories for large capabilities.
- Keep top-level CLI package module declarations in
crates/cli/src/lib.rs. - Keep
crates/cli/src/cli.rsfocused on command and argument shape. - Keep operation adapter implementations under
crates/cli/src/ops/by capability. - Put shared I/O-free command model logic in
crates/model/. Thetradingview-modelcrate owns validation, request interpretation, selector and target resolution, payload normalization/shaping, and fallback policy decisions. It must not depend on clap command enums, CDP runtime objects, HTTP clients, page-session execution, or UI automation. Accepted examples aretradingview_model::watchlist,alert,replay,drawing, andscreener. - Let
crates/cli/src/app/dispatch.rscalltradingview_model::*directly for pure validation, request interpretation, target resolution, and payload shaping. Useops::*from dispatch only for executable TradingView operations or adapter-specific request types. Do not re-export model helpers throughops.rssolely for dispatch convenience. - When an operation adapter grows too large, split it behind a facade file and
a same-named directory before creating a new workspace crate.
screeneris the current model: stable public adapter exports at the facade, sub-surface implementation modules underneath, and shared runtime/page-session helpers in a narrow common module. - Prefer moving CDP-free input boundaries before runtime/storage/UI code.
Screener is the larger example: validation, target resolution, and storage
payload shaping live in
tradingview_model::screener, while page-session storage fetch/save and UI operations remain inops/screener. - Storage-backed sub-surfaces are the next-best split candidates once
validation is isolated. Screener columns live in
crates/cli/src/ops/screener/columns.rs; Screener filters and screens now also own their operation bodies while shared open-state, storage fetch, click dispatch, and JavaScript helper expansion remain inengine.rs. - Keep mixed page-session adapters split by user-visible sub-surface before
extracting crates. Alert is the current model: list, normal create,
indicator-alert create, delete, and public-safe payload normalization live
under
crates/cli/src/ops/alert/, whilealert.rspreserves the adapter exports used by dispatch. - Keep historical adapter names as facades when that avoids churn. Layout is
now a facade over
crates/cli/src/ops/layout/watchlist.rsandcrates/cli/src/ops/layout/pane.rs; do not mix new watchlist and pane implementation bodies back into the facade file. - Keep CDP-dependent Pine Editor operations in the CLI package, but split them
by Editor sub-surface.
crates/cli/src/ops/pine/editor.rsis now a facade overruntime,source,scripts, andcompilemodules. Desktop-free Pine static analysis and facade checks still belong incrates/pine/. - Keep medium adapters behind the same facade pattern once they mix validation,
reads, mutation, and payload shaping. Drawing, Replay, and chart-dependent
Market now use same-named implementation directories under
crates/cli/src/ops/. Do not gather new Drawing/Replay/Market operation bodies back into the facade files. - Treat selected-chart historical export as an explicit Desktop-backed
operation.
tv export chart-barsmay orchestratetv rangeand selected chart OHLCV readback, but it must not become a fallback inside Desktop-freetv bars. Keep the payload public-safe and source-labeled:export_chart_bars.v1,requested_visible_range,range_operation,chart_context,returned_bars_range, andselected_chart_range_matchare diagnostics, not trading judgments. - Keep bounded visible-range history paging inside the selected-chart adapter.
Shared finite/order validation and I/O-free paging/viewport policy belong in
tradingview-model; CDP history inspection, sequentialrequestMoreData(1000), absolute deadlines, and viewport application belong incrates/cli/src/ops/chart/visible_range.rs. No-argumenttv rangeremains a read. The bounded setter must not call Desktop-free bars, OHLCV, export, or Replay as a fallback. - Treat Strategy Tester panel screenshots as visual evidence, not structured
strategy data.
tv screenshot --region strategymay locate and capture the visible Strategy Tester panel, but it must not open the panel, run a strategy, infer metrics, or replacetv data strategy,tv data trades, ortv data equity. - Keep screenshot render waiting opt-in, sequential, and bounded by one absolute deadline. Read only selected-chart context and requested-region geometry, require three stable observations, and capture nothing after a readiness timeout. Do not broaden scoped loading checks into arbitrary global class-fragment selectors.
- Keep Strategy Tester selection shared across metrics, trades, and equity.
Their additive
strategy_contextmust use current strategy metadata, explain report-based selection, reject unresolved multiple candidates, and report hidden or unready state without opening the panel or changing study visibility. - Once an adapter split exposes CDP-free request interpretation or validation,
move that logic into
crates/model/if it is reusable and not tied to clap or live page state. Drawing is the request-boundary example:tradingview_model::drawingowns the request structs and position validation, whileops/drawingowns shape creation, entity post-checks, reads, and cleanup. - Keep generic UI automation safety-aware.
crates/cli/src/ops/ui.rsis a facade overdom,input,selectors, andeval; do not move theTV_ALLOW_UNSAFE_UI_EVALgate out of the application safety/dispatch layer or hide new unsafe behavior inside the adapter. - Keep
crates/cli/src/main.rsas a thin process entrypoint. Put CLI parsing, command dispatch, JSON envelope output, stream loops, input conversion, and target connection orchestration undercrates/cli/src/app/. - Put reusable command logic and transport helpers in root library modules
rather than adding binary-only code to
crates/cli/src/main.rs. - Put cross-crate contract types in
crates/core/only when they are small, low-dependency, and broadly shared. Current examples are typed errors, JSON envelopes, and exit-code mapping. - Put shared I/O-free request models, validation, normalization, target
resolution, and public-safe payload shaping in
crates/model/. The model crate may usetradingview-coreandserde_json, but it must stay free of network, CDP, clap, and UI dependencies. - Put credential-free, Desktop-free market reads in
crates/market/when they do not depend on CDP, chart state, or UI automation. Prefer typed result structs for reusable Rust APIs; keep JSON wrappers only for CLI payload compatibility. Document reusable typed APIs in rustdoc anddocs/rust-api.md. Browserless historicaltv barsis part of this boundary: the WebSocket read, request validation, payload shaping, and source-availability details live intradingview-market, while CLIopsremains a thin command adapter. Keep the publicbars_symbolfacade stable and place internal bars responsibilities in same-named private modules such as validation, protocol, transport, payload, and types. - Put credential-free, Desktop-free scanner reads in
crates/scanner/when they can be exercised without TradingView Desktop. Prefer typed result structs for reusable Rust APIs; keep JSON wrappers only for CLI payload compatibility. Document reusable typed APIs in rustdoc anddocs/rust-api.md. - Put Desktop-free Pine helpers in
crates/pine/when they are local source analysis or Pine facade checks. Keep Pine Editor operations in the CLI package because they depend on CDP, Monaco, and visible TradingView UI state. - Put shared TradingView Desktop CDP connection code in
crates/cdp/. Do not duplicate target discovery,RuntimeEvaluator, screenshot/input event primitives, or target handoff helpers inside operation modules. - Keep TradingView process launch policy in
crates/cli/src/ops/launch.rs. Direct spawn and the macOSopencommand must remove onlyELECTRON_RUN_AS_NODE, preserve unrelated inherited environment entries, and retain the no-kill default. Observe a direct child only after CDP and any macOS fallback both fail; a successful fallback is not evidence about the original child's final state. - Put shared TradingView Desktop app-window helpers in
crates/cli/src/ops/desktop.rswhen multiple operation adapters need the same Desktop shell behavior, such as app-tab reads or new-tab launcher clicks. Keep product-specific launch behavior, such as opening the Screener tile from the Desktop new-tab page, in the owning operation adapter. - Keep each library crate's
lib.rsas a facade. When implementation grows, split into same-directory modules rather than gathering everything inlib.rs. - For Desktop-free read crates such as
tradingview-marketandtradingview-scanner, prefer splitting a grown read surface into field or request selection, endpoint request construction, and response normalization modules before release. Keep the crate-level public API stable and expose the split modules only when a later plan intentionally makes them reusable. - Do not move chart-dependent market reads, Screener code, account mutation, or UI automation into another workspace crate merely because they are reusable in theory. Extract them only when a concrete follow-up plan proves the boundary and dependency set are useful.
- Before extracting more
opscode, consultdocs/operation-adapter-boundaries.md. Keep executable TradingView work inopswhen it needs CDP/runtime access, page-session APIs, storage fetch/save, DOM/UI fallback, live chart state, or post-checks. - Do not create a generic
opscrate just to move files. Currentopsmodules are operation adapters inside the CLI package. Split large modules internally first, then extract domain-specific crates only when their dependency boundary is clear. - Treat the workspace library crates as internal and unstable until a future plan explicitly defines a stable Rust API.
- Keep helpers as private as possible; use
pub(super)for sibling operation modules when needed. - Avoid unrelated cleanup while migrating commands or fixing behavior.
tv --version prints the cargo/rustc shape:
tv <version> (<commit> <date>)
crates/cli/build.rs derives the provenance fields from git at build time.
The two fields of that line are passed through TV_VERSION_COMMIT and
TV_VERSION_DATE, and crates/cli/src/build_info.rs renders them.
- A clean build prints the short commit hash and that commit's date, so one commit always produces one identical string.
- A build with uncommitted executable-source changes prints
<commit>-dirtyand the build date, because the commit date no longer describes the binary. Treat-dirtyas "this binary cannot be identified by a commit". - Both dates are local dates. The build date reuses the time-zone offset that
gitreports for this machine, so it cannot drift a day away from the commit datesgitrenders. - Dirty detection is limited to paths that can change the executable:
crates/,Cargo.toml,Cargo.lock, andrust-toolchain*. Documentation edits must never set the marker, otherwise local builds are always-dirtyand the marker stops carrying information. - Without
gitor a repository, such as a build from an unpacked source archive, both fields fall back toUNKNOWN. The build still succeeds.
The build script watches those same paths plus HEAD, the whole refs
directory, packed-refs, the git index, and logs/HEAD, so committing
refreshes the stamp even when no file content changes. Watching the checked-out
branch ref by name is not enough: on a branch whose ref is packed the loose ref
does not exist yet, so the watch would cover nothing and the commit that creates
it would leave a stale -dirty stamp behind. logs/HEAD is watched as well
because it is an existing file that a commit appends to, which does not share a
failure mode with noticing a new file inside a watched directory. Downstream consumers parse this line, so keep the
package version first and keep the parenthesized suffix stable.
The derivation lives in crates/cli/build/provenance.rs, which both
crates/cli/build.rs and crates/cli/tests/build_provenance.rs include. A
build script cannot be imported as a library, and comparing the binary's output
against the same environment variables the build script emitted only proves the
rendering, not the derivation. The included tests therefore drive the
derivation against temporary repositories, including the packed-ref lifecycle,
and check the date arithmetic against known dates.
tv --version --verbose prints the unreduced fields in the
rustc --version --verbose shape, from TV_BUILD_COMMIT_HASH,
TV_BUILD_COMMIT_DATE, TV_BUILD_BUILT_AT, TV_BUILD_DIRTY, and
TV_BUILD_TARGET:
tv <version> (<commit> <date>)
binary: tv
release: <version>
commit-hash: <full hash>
commit-date: <YYYY-MM-DD>
built-at: <RFC 3339 local timestamp>
dirty: true|false
target: <target triple>
commit-dateandbuilt-atare both always reported. The short line pickscommit-datefor a clean build and the date ofbuilt-atfor a dirty one; the verbose report does not reduce them, so a clean build can still show when it was built.built-atcarries the time of day and the UTC offset, such as2026-08-21T07:15:01+09:00, because two builds of the same dirty tree on one day are otherwise indistinguishable.commit-datestays a plain date, which matchesrustcand the version line.dirtyisUNKNOWNwhen there is no commit to compare against.built-atis reported even withoutgit, but the local offset is then unknown and the timestamp falls back to UTC. The printed+00:00says so, so the instant is correct either way.SOURCE_DATE_EPOCHoverrides the clock and is rendered as UTC, so a reproducible build can pinbuilt-atinstead of stamping wall-clock time. A malformed value fails the build rather than falling back to the wall clock, which would make a build that asked to be reproducible silently non-deterministic.targetcarries Cargo'sTARGET, the platform the produced binary runs on.rustc --version --verbosecalls the same thinghost, because a compiler distinguishes the platform it runs on from the one it compiles for.tvhas no such pair, so borrowinghostwould import the distinction without its context. Cargo'sHOST, the host platform of the Rust compiler running the build, is deliberately not reported; it would need its own field.
The root command owns -V/--version instead of clap's automatic flag
(disable_version_flag), because clap prints its version string during parsing
and cannot see --verbose. Two consequences are load-bearing:
Cli::commandisOption<Command>, sotv --versionparses without a subcommand. When both the version flag and the subcommand are absent, the runner reports clap's rendered help as a validation error, which is what clap's own parse failure produced before.- Root
--verboseis notglobalandrequiresthe version flag, so the existing per-subcommand--verboseflags such astv data lines --verbosekeep their own meaning.
Large CLI contract suites should be split by command family. Keep shared
integration-test helpers under crates/cli/tests/support/, keep root-level
CLI contracts in cli_contract.rs, and add focused cli_contract_* test
targets when a command family grows.
Many operations evaluate JavaScript through CDP. Treat user-provided strings as data, not source code.
- Use JSON serialization helpers instead of hand-written quote escaping.
- Validate numeric inputs before embedding them in JavaScript or request payloads.
- Reject non-finite numeric input before connecting to CDP where possible.
- Centralize private TradingView API paths inside operation helpers.
- When TradingView internals change, report
internal_api_unavailablerather than manufacturing a success payload.
Tracked docs must not contain live account-local identifiers or private operational metadata. Scrub saved-script ids, saved-script names, alert ids, layout ids, chart target ids, usernames, emails, account names, machine-local paths, cookies, tokens, and raw live payloads unless they are intentionally public example data.
Operation unit tests should live next to the module they verify under
#[cfg(test)]. They must use fake runtime evaluators and must not require a
running TradingView Desktop.
CLI contract tests belong under crates/cli/tests/cli_contract.rs. They should
cover argument parsing, structured connection errors, validation errors, and
public command shape.
Live CDP smoke checks are useful but environment-dependent. Keep them separate from automated tests and record meaningful results in the relevant ExecPlan or note without account-local identifiers.
The ignored CDP transport measurement checks stage latency and failure classification without retrying a failed operation:
TV_LIVE_TRANSPORT_MEASUREMENT=1 \
TV_LIVE_TRANSPORT_MEASUREMENT_TARGET_ID=<TARGET_ID> \
TV_LIVE_TRANSPORT_MEASUREMENT_ITERATIONS=10 \
TV_LIVE_TRANSPORT_MEASUREMENT_DEADLINE_MS=120000 \
cargo test -p tradingview-cdp live_transport_measurement -- --ignored --nocaptureThe target ID is required and must remain a local environment value. Iterations
are bounded to 1..=100, and the single run deadline is bounded to
1000..=300000 milliseconds. Output contains only aggregate counts, stage
p50/p95 timing, deadline status, and stale-target diagnosis labels. Because the
probe uses explicit target selection, its target-selection timing does not
represent ordinary heuristic selection; deterministic fixtures cover the
heuristic and ambiguity paths. Do not copy raw probe output or target values
into tracked files.
Some live checks are available as ignored integration tests. They are opt-in only and must not become CI requirements. For chart-source quote endurance checks, build the CLI and run:
TV_LIVE_CHART_QUOTE_SMOKE=1 cargo test -p tradingview-cli --test live_chart_quote -- --ignored --nocaptureOptional environment variables:
TV_LIVE_CHART_QUOTE_SYMBOLS: comma-separated public symbols, defaulting toPLUG,AAPL,MSFT,IONQ,MU,PLUG.TV_LIVE_CHART_QUOTE_RUNS: positive repeat count, defaulting to1.TV_LIVE_CHART_QUOTE_TARGET_ID: explicit CDP target id when multiple chart targets are open. Do not paste live target ids into tracked docs.
The ignored test validates public-safe summary fields only: requested symbol,
observed quote symbol, chart symbol, freshness_check, stable sample count,
and restore status. Switched-symbol reads require at least two stable samples;
same-symbol fast-path reads may report one stable sample because no chart
switch occurred.
For Desktop quote-session extended-hours evidence checks, run this only during the relevant market phase:
TV_LIVE_QUOTE_SESSION_SMOKE=1 TV_LIVE_QUOTE_SESSION_EXPECT_PHASE=postmarket cargo test -p tradingview-cli --test live_quote_session_extended_hours -- --ignored --nocaptureDo not run the postmarket or premarket smoke early and treat the result as
evidence. If the observed quote-session phase does not match
TV_LIVE_QUOTE_SESSION_EXPECT_PHASE, the test prints
phase_result=not_yet_in_expected_phase; that is only a timing guard telling
you to wait for the relevant U.S. session.
Optional environment variables:
TV_LIVE_QUOTE_SESSION_TARGET_ID: explicit CDP target id when multiple chart targets are open. Do not paste live target ids into tracked docs.TV_LIVE_QUOTE_SESSION_SYMBOL: scanner quote symbol, defaulting toOKLO.TV_LIVE_QUOTE_SESSION_QUALIFIED_SYMBOL: quote-session symbol, defaulting toNYSE:OKLO.TV_LIVE_QUOTE_SESSION_CHART_SYMBOL: optional current-chart symbol for quote-session variants. If omitted, the test tries to read the current chart symbol through chart-source quote.TV_LIVE_QUOTE_SESSION_EXPECT_PHASE: optional expectedmarket-status.phase, such aspostmarketorpremarket. The test treats TradingView's hyphenated phase namespost-marketandpre-marketas aliases for those expected values.
The ignored test compares scanner-backed extended-hours fields with selected TradingView Desktop quote-session fields. Scanner equality is not required: scanner reads may be delayed while Desktop quote-session values may be streaming or entitlement-dependent. The test prints only public-safe selected field summaries and must not become a CI requirement.
For explicit Desktop quote-data source contract checks, run:
TV_LIVE_QUOTE_DATA_SMOKE=1 cargo test -p tradingview-cli --test live_quote_data_source -- --ignored --nocaptureOptional environment variables:
TV_LIVE_QUOTE_DATA_TARGET_ID: explicit CDP target id when multiple chart targets are open. Usetv tab listto choose the target, but do not paste live target ids into tracked docs.TV_LIVE_QUOTE_DATA_SYMBOL: public symbol to pass totv quote <SYMBOL> --source quote-data, defaulting toNASDAQ:RKLB.TV_LIVE_QUOTE_DATA_RUNS: positive repeat count, defaulting to1.TV_LIVE_QUOTE_DATA_EXPECT_PHASE: optional reporting hint such aspostmarketorpremarket. The test reports observed quote-data phase fields when a success payload is available; phase equality is not a scanner comparison.TV_LIVE_QUOTE_DATA_ALLOW_UNAVAILABLE: defaults to1. Set to0only when you expect a matchingqsd.rtcframe during the bounded window.
The ignored test validates public contract fields only. A bounded no-frame
result is acceptable by default when it returns structured
internal_api_unavailable details with raw_frame_included: false. Do not
paste raw WebSocket frames, live payloads, or target ids into tracked docs.
For a multi-target premarket check, keep the target id as a local environment value and record only public-safe summaries:
TV_LIVE_QUOTE_DATA_SMOKE=1 \
TV_LIVE_QUOTE_DATA_TARGET_ID=<ID> \
TV_LIVE_QUOTE_DATA_EXPECT_PHASE=premarket \
TV_LIVE_QUOTE_DATA_SYMBOL=NASDAQ:RKLB \
TV_LIVE_QUOTE_DATA_ALLOW_UNAVAILABLE=1 \
cargo test -p tradingview-cli --test live_quote_data_source -- --ignored --nocaptureFor chart-source quote concurrency checks, run:
TV_LIVE_CHART_QUOTE_CONCURRENCY_SMOKE=1 cargo test -p tradingview-cli --test live_chart_quote_concurrency -- --ignored --nocaptureOptional environment variables:
TV_LIVE_CHART_QUOTE_CONCURRENCY_SYMBOLS: comma-separated public symbols, defaulting toPLUG,AAPL,MSFT,IONQ,MU,PLUG.TV_LIVE_CHART_QUOTE_CONCURRENCY_RUNS: positive repeat count, defaulting to1.TV_LIVE_CHART_QUOTE_CONCURRENCY_TARGET_ID: explicit CDP target id when multiple chart targets are open. Do not paste live target ids into tracked docs.TV_LIVE_CHART_QUOTE_CONCURRENCY_WIDTH: number of near-concurrent childtv quote <SYMBOL> --source chartprocesses per batch, defaulting to2.
The ignored test checks whether near-concurrent chart-source quote processes serialize cleanly or expose mismatch/restore failures. It validates public-safe summary fields only and must not become a CI requirement.
For tv observe chart JSONL contract checks, run:
TV_LIVE_OBSERVE_CHART_SMOKE=1 cargo test -p tradingview-cli --test live_observe_chart -- --ignored --nocaptureOptional environment variables:
TV_LIVE_OBSERVE_CHART_TARGET_ID: explicit CDP target id when multiple chart targets are open. Do not paste live target ids into tracked docs.TV_LIVE_OBSERVE_CHART_DURATION_MS: bounded observation duration, defaulting to3000.TV_LIVE_OBSERVE_CHART_HEARTBEAT_MS: heartbeat interval, defaulting to1000.TV_LIVE_OBSERVE_CHART_MAX_EVENTS: optional sample event cap.
The ignored test validates public-safe JSONL summaries only: the first event is
readiness, later events use command: "observe", sample events are bar stream
samples, heartbeat events preserve sample counts, the final summary event
reports counts and end reason, and source metadata marks the events as
Desktop-backed non-mutating reads.
The v0.18 JSONL observation contract keeps these events additive and
public-safe: observe_chart.v1 marks observe readiness / sample / heartbeat /
summary events, stream.v1 marks lower-level stream sample / heartbeat /
summary events, and source metadata plus bounded controls stay intact. Summary
events are observation-window readbacks, not market-data samples. Do not paste
raw JSONL live output, target ids, raw WebSocket frames, account-local
metadata, or local validation paths into tracked docs.
Study-value rows use one shared identity contract across tv values and
tv stream values: entity_id, short_name, study_kind, bounded inputs,
and visible. Preserve each command's existing value reader, row inclusion,
formatting, and order. Identity must come from the same study instance as the
value; never join independently enumerated collections by display name or
index. Compact inputs omit source/script text, nested objects, oversized
strings, and entries beyond the fixed bounds before output.
For bounded Desktop-free watch compare contract checks, run:
cargo test -p tradingview-cli watch -- --nocapture
cargo test -p tradingview-cli --test cli_contract_quote watch -- --nocapturetv watch compare <SYMBOL>... emits JSONL readiness, sample, heartbeat, and
summary events with contract_version: "watch_compare.v1" and scanner-backed
source metadata. If live smoke is attempted, record only public-safe summary
counts such as symbols, sample count, heartbeat count, poll count, end reason,
and source marker. Do not paste raw JSONL output into tracked docs.
For Replay extraction feasibility checks, treat tv replay status as a
Desktop-backed read and Replay controls as Desktop-backed operations. Payloads
should expose replay_context, selected-chart chart_context when available,
source metadata, and operation metadata without creating a stable export
command. If live smoke is attempted, record only public-safe fields such as
Replay started state, current date, operation, and whether Replay was stopped.
Do not paste raw DOM, raw payloads, target ids, account-local metadata, or
local absolute paths into tracked docs.
Replay step-log checks should stay bounded and public-safe. tv replay log --steps <N> should make the step limit, initial state, per-step
previous_date / current_date, replay_context, chart_context, final end
reason, and failure details testable before any stable Replay export command is
considered. --attach-ohlcv-summary [--ohlcv-count <N>] is the explicit
selected-chart OHLCV summary attachment path and must report
replay_log_ohlcv_summary_attachment.v1 metadata plus attachment counters.
Use --attach-chart-screenshot --screenshot-output-dir <DIR> only when every
successful step needs a local chart PNG. Filenames are deterministic and never
overwrite existing files. Screenshot attachment failure is counted separately
and does not turn an already successful Replay step into a step failure.
Chart-backed compare uses tv chart compare <SYMBOL>.... Keep tv compare
and tv watch compare Desktop-free and scanner-backed. tv chart compare
uses selected-chart quote evidence, may temporarily switch the visible chart,
and returns chart_compare.v1 with ordered item status, before/after chart
context, and restore readback. If live checks are attempted, record only
public-safe fields such as command, contract marker, source category, symbol
count, ok/error count, and restore status. Do not paste raw target ids, raw
DOM, raw payloads, account-local metadata, or local absolute paths into
tracked docs.
tv events keeps event-shaped evidence under the scanner fundamentals
boundary. tv events <SYMBOL> returns events.v1 from earnings and dividends
fields, while tv events compare <SYMBOL>... returns events_compare.v1 for
2 to 25 symbols. Neither command is a complete event calendar. If live checks
are attempted, record public-safe symbol count, event type, event count,
source category, availability state, and missing / unavailable reasons only.
Do not paste raw event payloads, account-local metadata, credentials, session
ids, or local absolute paths into tracked docs.
For selected-chart export checks, use tv export chart-bars --from <UNIX_SECONDS> --to <UNIX_SECONDS> [--count 500] [--summary]. The command is a
Desktop-backed operation because it moves the visible chart range before
reading bars. If live smoke is attempted, record only public-safe fields such
as command, symbol, timeframe, requested visible range, returned bars range,
range-match status, and contract marker. Do not paste raw bars, raw target ids,
raw chart payloads, account-local metadata, or local absolute paths into
tracked docs.
For tv snapshot <SYMBOL> live contract checks, run:
TV_LIVE_SNAPSHOT_SMOKE=1 cargo test -p tradingview-cli --test live_snapshot -- --ignored --nocaptureOptional environment variables:
TV_LIVE_SNAPSHOT_SYMBOLS: comma-separated public symbols, defaulting toNASDAQ:AAPL,NYSE:IONQ.TV_LIVE_SNAPSHOT_GROUPS: comma-separated fundamentals groups to pass as repeated--groupoptions.TV_LIVE_SNAPSHOT_FIELDS: comma-separated fundamentals fields to pass as repeated--fieldoptions.TV_LIVE_SNAPSHOT_RUNS: positive repeat count, defaulting to1.
The ignored test validates only public contract fields: source metadata, requested symbol, section success/error shape, top-level error summaries, follow-up hint metadata, and next-action hints. Do not paste raw snapshot output or live response payloads into tracked docs.
For tv compare <SYMBOL>... live contract checks, run:
TV_LIVE_COMPARE_SMOKE=1 cargo test -p tradingview-cli --test live_compare -- --ignored --nocaptureOptional environment variables:
TV_LIVE_COMPARE_SYMBOLS: comma-separated public symbols, defaulting toNASDAQ:AAPL,NYSE:IONQ.TV_LIVE_COMPARE_RUNS: positive repeat count, defaulting to1.
The ignored test validates only public contract fields: source metadata, requested count, ordered items, section success/error shape, top-level error summaries, follow-up hint metadata, and next-action hints. Do not paste raw compare output or live response payloads into tracked docs.
The deterministic Gate 6 scheduling measurement is opt-in and uses only a loopback HTTP fixture:
cargo test -p tradingview-market measure_sequential_and_bounded_http_workloads -- --ignored --nocaptureIt compares five-run medians at 1, 2, 5, 10, and 25 symbols for quote-like, events-like, and compare-like workloads. Ordinary tests enforce input order and the maximum of four active symbol operations without depending on elapsed-time thresholds.
For tv bars WebSocket contract evidence checks, run:
TV_LIVE_BARS_SMOKE=1 cargo test -p tradingview-cli --test live_bars -- --ignored --nocaptureOptional environment variables:
TV_LIVE_BARS_SYMBOLS: comma-separated public symbols, defaulting toNASDAQ:AAPL,NYSE:IONQ. Exchange-qualified symbols keep the requested exchange explicit; bare symbols may resolve through symbol search.TV_LIVE_BARS_TIMEFRAME: timeframe passed totv bars, defaulting to1D.TV_LIVE_BARS_COUNT: positive bounded bar count, defaulting to5.TV_LIVE_BARS_RUNS: positive repeat count, defaulting to1.
The ignored test validates only public contract fields: bars.v1 source
metadata, requested symbol, resolved symbol, timeframe, bounded count,
non-empty bars, summary, range, symbol_resolution,
range_alignment, range_fetch_summary,
source_availability, public-safe wait_summary, and data_quality. Do not
paste raw WebSocket output or live response payloads into tracked docs.
Tracked text files must not contain machine-specific user-home paths. Run the deterministic detector tests and then scan the tracked tree:
python scripts/check-public-hygiene.py --self-test
python scripts/check-public-hygiene.pyThe guard runs in both Ubuntu and Windows CI jobs. It skips binary files and prints only a repository-relative file, line number, and detector category for each violation. Its only exceptions are the exact path-and-line pairs for two synthetic app-window URL fixtures. The same value in another file, or another user-home value in an allowed file, is rejected.
For code changes, run:
cargo fmt --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace
git diff --checkThe regular Cargo baseline remains Rust-only. Changes to the production study-value JavaScript identity helper, saved-script binding helper, indicator-insertion expression, or native three-point drawing probe/production expression also require separate executable contract gates:
mise run check:study-values-js
mise run check:pine-open-js
mise run check:indicator-insertion-js
mise run check:three-point-drawing-jsThese gates use Node.js 24.18.0, pinned in mise.toml. The study-value gate
executes the exact helper with synthetic sources and throwing Proxy fixtures.
The Pine-open gate executes the generated asynchronous page expression against
synthetic Pine facade, Pine-owned Monaco, overlay-menu, and Save-bound store
objects, including hidden stale editors, ambiguous visible editors, missing
store/menu state, unrelated same-name menu rows, cross-registry editor
ambiguity, and identity mismatch. Rust tests separately verify the single
readiness deadline, sanitized runtime-evaluation failure boundary, and removal
of account-local script IDs from successful output. Pine source setter tests
also verify that Monaco CRLF/LF/lone-CR normalization is accepted while all
other source differences remain fail-closed verification errors.
The same pinned Pine gate executes the generated save preflight and
post-shortcut inspection expressions so syntax drift cannot hide behind fake
Runtime payloads; Rust tests verify public-safe save evaluation failures.
The three-point drawing gate executes the exact Rust-generated probe and stable
production expressions. It verifies Promise-independent polling, the absolute
observation deadline, sticky multi-entity ambiguity, exact native identity and
point readback, verified-only probe cleanup, and the production path's
no-auto-cleanup boundary. It does not connect to TradingView Desktop or perform
a live drawing mutation.
The corresponding executable JavaScript tests are
ignored during ordinary cargo test --workspace; CI and the release workflow
install the pinned Node version and run each gate explicitly. Node.js is not a
runtime dependency of tv.
For focused command work, also run the relevant module or contract tests. For example:
cargo test screener -- --nocapture
cargo test -p tradingview-cli --test cli_contract screener -- --nocaptureFor docs-only changes, at minimum run:
git diff --check
python scripts/check-public-hygiene.py --self-test
python scripts/check-public-hygiene.py
git grep -nE '(USER;|sessionid|cookie|authorization|bearer)' -- README.md CHANGELOG.md docs .agents/skills packaging scripts || trueIf the credential grep finds only validation-command examples or public-safe policy language, record that as acceptable. Remove any new local path, account id, credential, or raw live payload before committing.
Git 2.54 config-based hooks are available as optional local guardrails.
Install with mise:
mise run hooks:installOr run the platform script directly:
scripts/install-config-hooks.shOn Windows:
./scripts/install-config-hooks.ps1These hooks are convenience checks. They do not replace the validation baseline or GitHub Actions.
Use Conventional Commits with sentence-case subjects.
Keep command migration, refactors, documentation cleanup, release packaging, and downstream workflow changes in separate commits unless they are inseparable for one behavior.
Never push unless the user explicitly asks in the current turn.
Use an ExecPlan for complex features and significant refactors. Keep the plan current while implementing, and record discoveries or changed decisions there rather than leaving them only in chat history.