fix(answer-commit): infer column scale and accept finite decimals
Issue for this PR
Part of #1481
Partially fixes the issue: the two numeric rendering/validation defects are fixed here. The third reported defect (an answer element stored only in basis is dropped when answer_only is true) is deferred, so the issue should stay open.
Stacked on feat/analyst-v10-commit (#1449, itself stacked on #1441).
Type of change
- Bug fix
- New feature
- Refactor / code improvement
- Documentation
What does this PR do?
Why: answer_commit is the terminal tool that renders a final answer. Numeric tables came out with inconsistent scale, and valid finite decimals were rejected, forcing models to retry or stringify values.
Root cause:
- Fixed decimals were applied only for an explicitly supplied
rows.precisionentry; other numeric cells went through plain string conversion, so a column holding5and4.18rendered with mixed scale. - The numeric validator rejected anything over 15 significant digits, including representable fractions (e.g.
18.440000534057617) and safe 16-digit integers.
Before -> after:
- Column with
5and4.18, no precision:5/4.18->5.00/4.18(markdown, csv, tsv, lines). Scale is inferred per column position, so duplicate labels work; strings and nulls are unchanged. - Explicit
rows.precisionstill overrides inference (existing rounding behavior). 18.440000534057617in fields or rows: rejected -> accepted (renders18.44with precision 2). Unsafe integers, NaN and infinities are still rejected; exact decimals beyond JS precision still need strings.- Behavior deltas to note: a parsed JSON number no longer carries original trailing zeros, so all-integer columns still need explicit precision; inferred table formatting uses fixed notation (very small values no longer print in scientific notation); the existing rendered-length cap still applies. Scalar formatting is unchanged.
Compound requests (follow-up commit): a model that found both an entity and a related item/count could keep the second only in basis/prose, which answer_only hides, so it was dropped. The answer_commit description and analyst prompt now state that every named item must be its own field because basis and prose are not shown under answer-only output. This is guidance, not runtime enforcement: a generic validator would need a structured request-target contract (counting conjunctions or scanning basis for values is unreliable and would reject valid exact-format answers), so the issue stays open for that.
How did you verify your code works?
cd packages/opencode && bun test test/altimate/tools/answer-commit.test.ts: re-run on the final head withtest/agent/answer-commit.test.tsand the wholetest/altimate/toolsdirectory: 931 pass, 0 fail.bun test test/altimate/tools(whole directory): 836 pass, 1 fail, 1 error. The failure isdatamate-list-integrations.test.ts(a 5s timeout case), which does not import or touchanswer-commitand fails identically when run alone; this PR changes onlyanswer-commit.tsand its test.bun run typecheckinpackages/opencode: passes, 0 diagnostics.bun run script/upstream/analyze.ts --markers --base feat/analyst-v10-commit --strict: passes, no upstream-shared files modified.git diff --check: clean.- Independent Codex review of the diff: no P0/P1/P2 findings. Non-blocking test-gap suggestions (
Number.MIN_VALUE, mixed safe-integer boundary with fractions, inferred padding exceeding the length cap); probed in isolation and they pass.
Screenshots / recordings
N/A (no UI change).
Known limitation (follow-up)
Float-noise detection cannot tell a genuine double that is within two ulps of a six-decimal value (e.g. 1.0000000000000002) from an arithmetic artifact such as 0.1 + 0.2; such a cell is excluded from column-scale inference and shown at the column scale. Artifacts below 1e-6 are not recognized as noise. Neither affects values that are exactly representable at six decimals or fewer. Callers who need exact digits pass strings.
Checklist
- I have tested my changes locally
- I have not included unrelated changes in this PR
🤖 Generated with Claude Code
[!NOTE]
Medium Risk
Changes terminal answer rendering and numeric validation for the analyst agent; wrong inference could misformat tables, but scope is limited to answer_commit output paths with broad test coverage.
Overview
answer_commit now infers per-column decimal scale for tabular answers when rows.precision is omitted, using decimal.js so mixed cells (e.g. 5 and 4.18) render with consistent fixed decimals across markdown/csv/tsv/lines. Float-noise detection treats common IEEE artifacts (e.g. 0.30000000000000004) as shorter values so they do not inflate column scale; genuine long decimals are preserved.
Numeric validation drops the 15-significant-digit cap and only requires safe integers and finite numbers—values like 18.440000534057617 are accepted; exact trailing zeros or beyond-JS precision still belong in strings. Tool/schema copy documents inferred scale and updated number rules.
Analyst guidance (prompt + answer_commit description) stresses that every named item in a compound question must be its own field, because answer_only hides basis—this is documentation only, not runtime enforcement.
Tests expand coverage for inference, float noise, duplicate column labels by position, and the revised validation behavior.
Reviewed by Cursor Bugbot for commit ccc03c9e111aa103d45b710829933303baa2380e. Bugbot is set up for automated code reviews on this repo. Configure here.